{
	 Apophysis AV "Phoenix Edition" Copyright (C) 2021 Alice V. Koryagina
	 Original nBlur plugin by FractalDesire:
	 http://fractaldesire.deviantart.com/art/nBlur-plugin-190401515,
	 Optimized and translated into Delphi by Alice V. Koryagina
}

unit varNBlur;

interface

uses
  BaseVariation, XFormMan;

type
  TVariationNBlur = class(TBaseVariation)
  private
	numEdges, numStripes: integer;
  circumCircle, adjustToLinear, equalBlur, exactCalc: byte;
  ratioStripes, ratioHole, highlightEdges, ratioComplement, maxStripes, angStripes, midAngle, angStart,
	tan90_m_2, sina, cosa,  arc_tan1, arc_tan2, speedCalc1, speedCalc2, nStripes: double;
	hasStripes, negStripes: boolean;
  public
    constructor Create;

    class function GetName: string; override;
    class function GetInstance: TBaseVariation; override;

    function GetNrVariables: integer; override;
    function GetVariableNameAt(const Index: integer): string; override;

    function SetVariable(const Name: string; var value: double): boolean; override;
    function GetVariable(const Name: string; var value: double): boolean; override;
	function ResetVariable(const Name: string): boolean; override;

    procedure Prepare; override;
	procedure CalcFunction; override;
  end;

implementation

uses
  Math;

{ TVariationNBlur }

///////////////////////////////////////////////////////////////////////////////

procedure TVariationNBlur.Prepare;
begin
    //*********Prepare stripes related stuff*********
  nStripes := numStripes;  // AV: to fix a bug with negative stripes count
	if (nStripes <> 0) then
	begin
		hasStripes := True;
		if (nStripes < 0) then
		begin
			negStripes := True;
			nStripes := -nStripes;
		end else
			negStripes := False;
	end else begin
		hasStripes := False;
		negStripes := False;
	end;

    //**********Prepare angle related stuff**********
	midAngle := 2 * pi / numEdges;
	if hasStripes then
	begin
        angStripes :=  midAngle / (2 * nStripes);
        angStart := angStripes * 0.5;
        ratioComplement := 2 - ratioStripes;
	end;

    //**********Prepare hole related stuff***********
	if (ratioHole > 0.95) and (exactCalc = 1) and (circumCircle = 0) then ratioHole := 0.95;

    //*********Prepare edge calculation related stuff*********
	tan90_m_2 := tan((pi  + midAngle) * 0.5);
	SinCos(midAngle * 0.5, sina, cosa);

    //*********Adjustment of width of shape*********
    if (adjustToLinear = 1) then
    begin
        if((numEdges mod 4) = 0) then
            VVAR := VVAR / (sqrt(2 - 2 * cos(midAngle *(numEdges * 0.5 - 1))) * 0.5)
        else
            VVAR := VVAR / (sqrt(2 - 2 * cos(midAngle * floor(numEdges * 0.5))) * 0.5);
    end;

    //*********Prepare circumCircle-calculation*********
    if (circumCircle = 1) then
    begin
        exactCalc := 0;
        highlightEdges := 0.1;
    end;

    //*********Prepare speed up related stuff*********
    speedCalc1 := ratioComplement * angStart;
    speedCalc2 := ratioStripes * angStart;
    maxStripes := 2 * numStripes;
    if(negStripes = False) then
        arc_tan1 := 13.0 / power(numEdges, 1.3) * highlightEdges
	else
        arc_tan1 := 7.5 / power(numEdges, 1.3) * highlightEdges;
	arc_tan2 := 2 * arctan(arc_tan1 *(-0.5));
end;



procedure TVariationNBlur.CalcFunction;
var
	x, y,  xTmp, yTmp, lenOuterEdges, lenInnerEdges, lenXY: double;

	procedure RandXY;
	var
		angXY, ranTmp, angTmp, angMem, rotTmp,
		ratioTmp, ratioTmpNum, ratioTmpDen, step, speedCalcTmp: double;
		count: integer;
	begin
		if (exactCalc = 1) then
			angXY := random * PI2
		else
			angXY := (arctan(arc_tan1 * (random - 0.5)) / arc_tan2 + 0.5 + Random(numEdges)) * midAngle;
		SinCos(angXY, x, y);
		angMem := angXY;
		while (angXY > midAngle) do angXY := angXY - midAngle;

		//********Begin of xy-calculation of radial stripes********
		if hasStripes then
		begin
			angTmp := angStart;
            count := 0;
            while(angXY > angTmp) do
            begin
                angTmp := angTmp + angStripes;
                if (angTmp > midAngle) then angTmp := midAngle;
                inc(count);
            end;
            if (angTmp <> midAngle) then angTmp := angTmp - angStart;

			if (not negStripes) then
			begin
				if Odd(count) then
				begin
				    if(angXY > angTmp) then
                    begin
                        angXY := angXY + angStart;
                        angMem := angMem + angStart;
                        SinCos(angMem, x, y);
                        angTmp := angTmp + angStripes;
                        inc(count);
                    end
                    else begin
                        angXY  := angXY - angStart;
                        angMem := angMem - angStart;
                        SinCos(angMem, x, y);
                        angTmp := angTmp - angStripes;
                        dec(count);
                    end;
				end;

				if (not Odd(count)) and (ratioStripes > 1.0) then
				begin
                    if (angXY > angTmp) and (count <> maxStripes) then
                    begin
                        angMem := angMem - angXY + angTmp + (angXY - angTmp) / angStart * ratioStripes * angStart;
                        angXY  := angTmp + (angXY - angTmp) / angStart * ratioStripes * angStart;
                        SinCos(angMem, x, y);
                    end
                    else begin
                        angMem := angMem - angXY + angTmp - (angTmp - angXY)/ angStart * ratioStripes * angStart;
                        angXY  := angTmp + (angXY - angTmp)/ angStart * ratioStripes * angStart;
                        SinCos(angMem, x, y);
                    end;
				end;

				if (not Odd(count)) and (ratioStripes < 1.0) then
				begin
                    if (abs(angXY - angTmp) > speedCalc2) and (count <> maxStripes) then
                    begin
                        if (angXY - angTmp) > speedCalc2 then
                        begin
                            ratioTmpNum := (angXY -(angTmp + speedCalc2)) * speedCalc2;
                            ratioTmpDen := angStart - speedCalc2;
                            ratioTmp := ratioTmpNum / ratioTmpDen;
                            SinCos((angMem - angXY + angTmp + ratioTmp), x, y);
                            angXY := angTmp + ratioTmp;
                        end;
                        if (angTmp - angXY) > speedCalc2 then
                        begin
                            ratioTmpNum := (angTmp - speedCalc2 - angXY) * speedCalc2;
                            ratioTmpDen := angStart - speedCalc2;
                            ratioTmp := ratioTmpNum / ratioTmpDen;
                            SinCos((angMem - angXY + angTmp - ratioTmp), x, y);
                            angXY := angTmp - ratioTmp;
                        end;
                    end;

                    if (count = maxStripes) then
                        if (angTmp - angXY) > speedCalc2 then
                        begin
                            ratioTmpNum := (angTmp - speedCalc2 - angXY) * speedCalc2;
                            ratioTmpDen := angStart - speedCalc2;
                            ratioTmp := ratioTmpNum / ratioTmpDen;
                            SinCos((angMem - angXY + angTmp - ratioTmp), x, y);
                            angXY := angTmp - ratioTmp;
                        end;
                end;
			end
			else begin
				//********Change ratio and ratioComplement********
                ratioTmp := ratioStripes;
                ratioStripes := ratioComplement;
                ratioComplement := ratioTmp;
                speedCalcTmp := speedCalc1;
                speedCalc1 := speedCalc2;
                speedCalc2 := speedCalcTmp;
                //************************************************
                if not Odd(count) then
                begin
                    if (angXY > angTmp) and (count <> maxStripes) then
                    begin
					              angXY := angXY + angStart;
                        angMem := angMem + angStart;
                        SinCos(angMem, x, y);
                        angTmp := angTmp + angStripes;
                        inc(count);
                    end
                    else begin
                        angXY  := angXY - angStart;
                        angMem := angMem - angStart;
                        SinCos(angMem, x, y);
                        angTmp := angTmp - angStripes;
                        dec(count);
                    end;
                end;

				if Odd(count) and (ratioStripes > 1.0) then
				begin
                    if (angXY > angTmp) and (count <> maxStripes) then
                    begin
                        angMem := angMem - angXY + angTmp + (angXY - angTmp) / angStart * ratioStripes * angStart;
                        angXY  := angTmp + (angXY - angTmp) / angStart * ratioStripes * angStart;
                        SinCos(angMem, x, y);
                    end
                    else begin
                        angMem := angMem - angXY + angTmp - (angTmp - angXY)/ angStart * ratioStripes * angStart;
                        angXY  := angTmp + (angXY - angTmp)/ angStart * ratioStripes * angStart;
                        SinCos(angMem, x, y);
                    end;
				end;


				if Odd(count) and (ratioStripes < 1.0) then
				begin
					if (abs(angXY - angTmp) > speedCalc2) and (count <> maxStripes) then
                    begin
                        if (angXY - angTmp) > speedCalc2 then
                        begin
                            ratioTmpNum := (angXY -(angTmp + speedCalc2)) * speedCalc2;
                            ratioTmpDen := angStart - speedCalc2;
                            ratioTmp := ratioTmpNum / ratioTmpDen;
                            SinCos((angMem - angXY + angTmp + ratioTmp), x, y);
                            angXY := angTmp + ratioTmp;
                        end;
                        if (angTmp - angXY) > speedCalc2 then
                        begin
                            ratioTmpNum := (angTmp - speedCalc2 - angXY) * speedCalc2;
                            ratioTmpDen := angStart - speedCalc2;
                            ratioTmp := ratioTmpNum / ratioTmpDen;
                            SinCos((angMem - angXY + angTmp - ratioTmp), x, y);
                            angXY := angTmp - ratioTmp;
                        end;
                    end;

                    if (count = maxStripes) then
					begin
						angTmp := midAngle;
                        if (angTmp - angXY) > speedCalc2 then
                        begin
                            ratioTmpNum := (angTmp - speedCalc2 - angXY) * speedCalc2;
                            ratioTmpDen := angStart - speedCalc2;
                            ratioTmp := ratioTmpNum / ratioTmpDen;
                            SinCos((angMem - angXY + angTmp - ratioTmp), x, y);
                            angXY := angTmp - ratioTmp;
                        end;
					end;
                end;

                //********Restore ratio and ratioComplement*******
                ratioTmp := ratioStripes;
                ratioStripes := ratioComplement;
                ratioComplement := ratioTmp;
                speedCalcTmp := speedCalc1;
                speedCalc1 := speedCalc2;
                speedCalc2 := speedCalcTmp;
                //************************************************
			end;
		end;
		//********End of xy-calculation of radial stripes********

        //********Begin of calculation of edge limits********
        xTmp := tan90_m_2 / (tan90_m_2 - tan(angXY));
        yTmp := xTmp * tan(angXY);
        lenOuterEdges := hypot(xTmp, yTmp);
         //*********End of calculation of edge limits********

        //********Begin of radius-calculation (optionally hole)********
        if(exactCalc = 1) then
            if (equalBlur = 1) then ranTmp := sqrt(random)
				      else  ranTmp := random
        else begin
            if(circumCircle = 1) then
                if (equalBlur = 1) then ranTmp := sqrt(random)
					        else  ranTmp := random
            else
                if (equalBlur = 1) then ranTmp := sqrt(random) * lenOuterEdges
					          else  ranTmp := random * lenOuterEdges;
        end;
        lenInnerEdges := ratioHole * lenOuterEdges;

        if  (exactCalc = 0) then
        begin
            if(ranTmp < lenInnerEdges) then
            begin
                if (circumCircle = 1) then
                    if (equalBlur = 1) then ranTmp := lenInnerEdges + sqrt(random) * (1.0 - lenInnerEdges + 1e-300)
						          else  ranTmp := lenInnerEdges + random * (1.0 - lenInnerEdges + 1e-300)
                else
                    if (equalBlur = 1) then ranTmp := lenInnerEdges + sqrt(random) * (lenOuterEdges - lenInnerEdges)
						            else  ranTmp := lenInnerEdges + random * (lenOuterEdges - lenInnerEdges);
            end;
        end;

        x := x * ranTmp;
        y := y * ranTmp;
        lenXY := hypot(x, y);
        //*********End of radius-calculation (optionally hole)*********
	end;  // of procedure

begin

    RandXY;

    //********Exact calculation slower - interpolated calculation faster********
    if (exactCalc = 1) and (circumCircle = 0) then
        while ((lenXY < lenInnerEdges) or (lenXY > lenOuterEdges)) do RandXY;
    if (exactCalc = 1) and (circumCircle = 1) then
        while (lenXY < lenInnerEdges) do RandXY;
    xTmp := x;
    yTmp := y;
    //**************************************************************************

    //********Begin of horizontal adjustment (rotation)********
    x := cosa * xTmp - sina * yTmp;
    y := sina * xTmp + cosa * yTmp;
    //*********End of horizontal adjustment (rotation)*********

	FPx^ := FPx^ + VVAR * x;
	FPy^ := FPy^ + VVAR * y;
end;


///////////////////////////////////////////////////////////////////////////////
constructor TVariationNBlur.Create;
begin
  numEdges  := 3;
  numStripes := 0;
  ratioStripes := 1.0;
  ratioHole := 0;
  circumCircle := 0;
  adjustToLinear := 1;
  equalBlur := 1;
  exactCalc := 0;
  highlightEdges := 1.0;
end;

///////////////////////////////////////////////////////////////////////////////
class function TVariationNBlur.GetInstance: TBaseVariation;
begin
  Result := TVariationNBlur.Create;
end;

///////////////////////////////////////////////////////////////////////////////
class function TVariationNBlur.GetName: string;
begin
  Result := 'nBlur';
end;

///////////////////////////////////////////////////////////////////////////////
function TVariationNBlur.GetVariableNameAt(const Index: integer): string;
begin
  case Index Of
  0: Result := 'nb_numEdges';
  1: Result := 'nb_numStripes';
  2: Result := 'nb_ratioStripes';
  3: Result := 'nb_ratioHole';
  4: Result := 'nb_circumCircle';
  5: Result := 'nb_adjustToLinear';
  6: Result := 'nb_equalBlur';
  7: Result := 'nb_exactCalc';
  8: Result := 'nb_highlightEdges';
  else
    Result := '';
  end
end;

///////////////////////////////////////////////////////////////////////////////
function TVariationNBlur.SetVariable(const Name: string; var value: double): boolean;
begin
  Result := False;
  if Name = 'nb_numEdges' then begin
    if (value < 3) then Value := 3;
    numEdges := Round(Value);
    Result := True;
  end else if Name = 'nb_numStripes' then begin
    numStripes := Round(Value);
    Result := True;
  end else if Name = 'nb_ratioStripes' then begin
    if (value < 0) then Value := 0;
	  if (value > 2) then Value := 2;
    ratioStripes := Value;
    Result := True;
  end else if Name = 'nb_ratioHole' then begin
    if (value < 0) then Value := 0;
	  if (value > 1) then Value := 1;
    ratioHole := Value;
    Result := True;
  end else if Name = 'nb_circumCircle' then begin
    if (value < 0) then Value := 0;
	  if (value > 1) then Value := 1;
    circumCircle := Round(Value);
    Result := True;
  end else if Name = 'nb_adjustToLinear' then begin
    if (value < 0) then Value := 0;
	  if (value > 1) then Value := 1;
    adjustToLinear := Round(Value);
    Result := True;
  end else if Name = 'nb_equalBlur' then begin
    if (value < 0) then Value := 0;
	  if (value > 1) then Value := 1;
    equalBlur := Round(Value);
    Result := True;
  end else if Name = 'nb_exactCalc' then begin
    if (value < 0) then Value := 0;
	  if (value > 1) then Value := 1;
    exactCalc := Round(Value);
    Result := True;
  end else if Name = 'nb_highlightEdges' then begin
    if (value < 0.1) then Value := 0.1;
	  highlightEdges := Value;
    Result := True;
  end
end;

function TVariationNBlur.ResetVariable(const Name: string): boolean;
begin
  Result := False;
 if Name = 'nb_numEdges' then begin
    numEdges:= 3;
    Result := True;
  end else if Name = 'nb_numStripes' then begin
    numStripes := 0;
    Result := True;
  end else if Name = 'nb_highlightEdges' then begin
    highlightEdges := 1;
    Result := True;
  end else if Name = 'nb_ratioStripes' then begin
    ratioStripes := 1;
    Result := True;
  end else if Name = 'nb_ratioHole' then begin
    ratioHole := 0;
    Result := True;
  end else if Name = 'nb_circumCircle' then begin
    circumCircle := 0;
    Result := True;
  end else if Name = 'nb_adjustToLinear' then begin
    adjustToLinear := 1;
    Result := True;
  end else if Name = 'nb_equalBlur' then begin
    equalBlur := 1;
    Result := True;
  end else if Name = 'nb_exactCalc' then begin
    exactCalc := 0;
    Result := True;
  end;
end;

///////////////////////////////////////////////////////////////////////////////
function TVariationNBlur.GetNrVariables: integer;
begin
  Result := 9;
end;

///////////////////////////////////////////////////////////////////////////////
function TVariationNBlur.GetVariable(const Name: string; var value: double): boolean;
begin
  Result := False;
  if Name = 'nb_numEdges' then begin
    Value := numEdges;
    Result := True;
  end else if Name = 'nb_numStripes' then begin
    Value := numStripes;
    Result := True;
  end else if Name = 'nb_ratioStripes' then begin
    Value := ratioStripes;
    Result := True;
  end else if Name = 'nb_ratioHole' then begin
    Value := ratioHole;
    Result := True;
  end else if Name = 'nb_circumCircle' then begin
    Value := circumCircle;
    Result := True;
  end else if Name = 'nb_adjustToLinear' then begin
    Value := adjustToLinear;
    Result := True;
  end else if Name = 'nb_equalBlur' then begin
    Value := equalBlur;
    Result := True;
  end else if Name = 'nb_exactCalc' then begin
    Value := exactCalc;
    Result := True;
  end else if Name = 'nb_highlightEdges' then begin
    Value := highlightEdges;
    Result := True;
  end;
end;

///////////////////////////////////////////////////////////////////////////////
initialization
  RegisterVariation(TVariationClassLoader.Create(TVariationNBlur), false, false);
end.

