{ Apophysis AV "Phoenix Edition" Copyright (C) 2021 Alice V. Koryagina }

unit varHyperboloid;

interface

uses
  BaseVariation, XFormMan;

type
  TVariationHyperboloid = class(TBaseVariation)
  private
	  kx, ky, kz, maxheight, h, hb: double;
    vkx, vky, vkz: double;
    limit, zero: byte;

    procedure CalcCut;
    procedure CalcCutBoarder;
  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;
    procedure GetCalcFunction(var f: TCalcFunction); override;
  end;

implementation

uses
  Math;

{ TVariationHyperboloid }

///////////////////////////////////////////////////////////////////////////////
procedure TVariationHyperboloid.Prepare;
begin
  vkx := VVAR * kx;
  vky := VVAR * ky;
  vkz := VVAR * kz;
  h := sqrt(1 + sqr(maxheight / ky));
  hb := sqrt(sqr(h) - 1);
end;

procedure TVariationHyperboloid.GetCalcFunction(var f: TCalcFunction);
begin
  if (limit = 0) then
    f := CalcFunction
  else if (zero = 0) then
    f := CalcCutBoarder
  else
    f := CalcCut;
end;

procedure TVariationHyperboloid.CalcFunction;
var
	sn, cn, sh, ch: double;
begin
	SinCos(FTx^, sn, cn);
	SinhCosh(FTy^, sh, ch);

  FPx^ := FPx^ + vkx * ch * sn;
  FPy^ := FPy^ + vky * sh;
  FPz^ := FPz^ + vkz * ch * cn;
end;

procedure TVariationHyperboloid.CalcCut;
var
	sn, cn, sh, ch: double;
begin
	SinhCosh(FTy^, sh, ch);

  if (ch <= h) then   // cut the surface
  begin
    SinCos(FTx^, sn, cn);
	  FPx^ := FPx^ + vkx * ch * sn;
	  FPy^ := FPy^ + vky * sh;
	  FPz^ := FPz^ + vkz * ch * cn;
  end;
end;

procedure TVariationHyperboloid.CalcCutBoarder;
var
	sn, cn, sh, ch: double;
begin
	SinCos(FTx^, sn, cn);
	SinhCosh(FTy^, sh, ch);

  if (ch <= h) then   // cut the surface
  begin
	  FPx^ := FPx^ + vkx * ch * sn;
	  FPy^ := FPy^ + vky * sh;
	  FPz^ := FPz^ + vkz * ch * cn;
  end
  else begin   // place the point on it's boarder
	  FPx^ := FPx^ + vkx * h * sn;
	  FPy^ := FPy^ + vky * sign(sh) * hb;
	  FPz^ := FPz^ + vkz * h * cn;
  end;
end;

///////////////////////////////////////////////////////////////////////////////
constructor TVariationHyperboloid.Create;
begin
  kx  := 1;
  ky := 1;
  kz := 1;
  maxheight := RandomRange(3, 6);
  limit := 1;
  zero := 0;
end;

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

///////////////////////////////////////////////////////////////////////////////
class function TVariationHyperboloid.GetName: string;
begin
  Result := 'hourglass3D';  // AV: hyperbolic hyperboloid :)
end;

///////////////////////////////////////////////////////////////////////////////
function TVariationHyperboloid.GetVariableNameAt(const Index: integer): string;
begin
  case Index Of
  0: Result := 'hourglass3D_kx';
  1: Result := 'hourglass3D_ky';
  2: Result := 'hourglass3D_kz';
  3: Result := 'hourglass3D_maxheight';
  4: Result := 'hourglass3D_uselimit';
  5: Result := 'hourglass3D_zero_edges';
  else
    Result := '';
  end
end;

///////////////////////////////////////////////////////////////////////////////
function TVariationHyperboloid.SetVariable(const Name: string; var value: double): boolean;
begin
  Result := False;
  if Name = 'hourglass3D_kx' then begin
    if (value < 1E-5) then Value := 1E-5;
    kx := Value;
    Result := True;
  end else if Name = 'hourglass3D_ky' then begin
    if (value < 1E-5) then Value := 1E-5;
    ky := Value;
    Result := True;
  end else if Name = 'hourglass3D_kz' then begin
    if (value < 1E-5) then Value := 1E-5;
    kz := Value;
    Result := True;
  end else if Name = 'hourglass3D_maxheight' then begin
    if (value < 0.1) then Value := 0.1;
    maxheight := Value;
    Result := True;
  end else if Name = 'hourglass3D_uselimit' then begin
    if (value < 0) then Value := 0;
    if (value > 1) then Value := 1;
    limit := Round(Value);
    Result := True;
  end else if Name = 'hourglass3D_zero_edges' then begin
    if (value < 0) then Value := 0;
    if (value > 1) then Value := 1;
    zero := Round(Value);
    Result := True;
  end;
end;

function TVariationHyperboloid.ResetVariable(const Name: string): boolean;
begin
  Result := False;
  if Name = 'hourglass3D_kx' then begin
    kx:= 1;
    Result := True;
  end else if Name = 'hourglass3D_ky' then begin
    ky := 1;
    Result := True;
  end else if Name = 'hourglass3D_kz' then begin
    kz := 1;
    Result := True;
  end else if Name = 'hourglass3D_maxheight' then begin
    maxheight := 3;
    Result := True;
  end else if Name = 'hourglass3D_uselimit' then begin
    limit := IfThen(limit = 0, 1, 0);
    Result := True;
  end else if Name = 'hourglass3D_zero_edges' then begin
    zero := IfThen(zero = 0, 1, 0);
    Result := True;
  end;
end;

///////////////////////////////////////////////////////////////////////////////
function TVariationHyperboloid.GetNrVariables: integer;
begin
  Result := 6;
end;

///////////////////////////////////////////////////////////////////////////////
function TVariationHyperboloid.GetVariable(const Name: string; var value: double): boolean;
begin
  Result := False;
  if Name = 'hourglass3D_kx' then begin
    Value := kx;
    Result := True;
  end else if Name = 'hourglass3D_ky' then begin
    Value := ky;
    Result := True;
  end else if Name = 'hourglass3D_kz' then begin
    Value := kz;
    Result := True;
  end else if Name = 'hourglass3D_maxheight' then begin
    Value := maxheight;
    Result := True;
  end else if Name = 'hourglass3D_uselimit' then begin
    Value := limit;
    Result := True;
  end else if Name = 'hourglass3D_zero_edges' then begin
    Value := zero;
    Result := True;
  end;
end;

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