unit MathUnit;

interface

{$OPTIMIZATION OFF}

uses
  LibV, OpenGL, SysUtils;

function LoadCoords(fname : PChar):vboolean;
function SaveCoords(fname : PChar):vboolean;
function HaveData:vboolean;
procedure DrawAll(win_width,win_height:integer);

implementation

var
  x, y : vector_p;
  sdx, sdy : vector_p;

  minx, maxx, miny, maxy : vec_t;

(*
 * This routine does all actual work.
 *)

procedure SmoothData(x,y:vector_p;var sdx,sdy:vector_p;subdiv:u32_t);
var
  at          : vector_p;
  next_point  : array[0..1] of vec_t;
  t, dt       : vec_t;
  count, total_points : u32_t;
  avx         : array[0..1] of vector_p;
  spline      : vhandle_t;
begin
  avx[0] := x;
  avx[1] := y;

  at := vecAlloc(x^.size);
  t := 0;
  dt := 1;
  count :=0;

  while count < x^.size do
  begin
    at^.elements^[count] := t;
    count := count+1;
    t := t + dt;
  end;

  spline := cspNdCreate(at,avx,2);
  memFree(at);

  if spline = nil then
    Exit;

	total_points := (x^.size-1) * subdiv;

  sdx := vecAlloc(total_points);
  sdy := vecAlloc(total_points);

	t := 0;
  dt := 1 / subdiv;

  count:=0;
  while count < total_points do
  begin
      next_point[0] := 0;
      next_point[1] := 0;
      cspNdInterpolate(spline,t,next_point);
      sdx^.elements^[count] := next_point[0];
      sdy^.elements^[count] := next_point[1];
      count := count+1;
      t := t + dt;
  end;
  cspNdDestroy(spline);
end;


procedure Clean;
begin
  if x <> nil then
    memFree(x);
  if y <> nil then
    memFree(y);
  if sdx <> nil then
    memFree(sdx);
  if sdy <> nil then
    memFree(sdy);
  x := nil;
  y := nil;
  sdx := nil;
  sdy := nil;
  minx := MAXFLOAT;
  miny := MAXFLOAT;
  maxx := -MAXFLOAT;
  maxy := -MAXFLOAT;
end;

function LoadNextCoordPair(scanner:vhandle_t;var x,y:vec_t):vboolean;
begin
  Result := false;
  try
    (* Try to read in two floats *)
    if lexNext(scanner) <> nil then
      x := StrToFloat(lexLast(scanner))
    else
      Exit;
    if lexNext(scanner) <> nil then
      y := StrToFloat(lexLast(scanner))
    else
      Exit;
    (* All Ok; update min* and max* *)
    if minx > x then
      minx := x;
    if miny > y then
      miny := y;
    if maxx < x then
      maxx := x;
    if maxy < y then
      maxy := y;
    Result := true;
  except
    (* We are fed with garbage; signal it *)
    on EConvertError do
      Exit;
  end;
end;

function LoadCoords(fname : PChar):vboolean;
var
  count, limit : u32_t;
  vx, vy, tx, ty : vector_p;
  cx, cy : vec_t;
  scanner : vhandle_t;
begin
  Result := false;
  Clean;
  scanner := lexParseFile(fname,'');
  if scanner = nil then
    Exit;
  count := 0;
  limit := 256;
  vx := vecAlloc(limit);
  vy := vecAlloc(limit);
  while LoadNextCoordPair(scanner,cx,cy) do
  begin
    if count >= limit then
    begin
      (* Not enought space; reallocate storage *)
      limit := limit * 2;
      tx := vecDuplicateEx(vx,limit);
      ty := vecDuplicateEx(vy,limit);
      memFree(vx);
      memFree(vy);
      vx := tx;
      vy := ty;
    end;
    vx^.elements^[count] := cx;
    vy^.elements^[count] := cy;
    count := count + 1;
  end;
  conlPrintf(LOG_INFO,'LoadCoords(%s) read in %u points'+#13,[fname,count]);
  if count > 0 then
  begin
    x := vecDuplicateEx(vx,count);
    y := vecDuplicateEx(vy,count);

    (*
     * Smooth data
     *)
    SmoothData(x,y,sdx,sdy,32);

    Result := True;
  end;
  lexDestroy(scanner);

  memFree(vx);
  memFree(vy);
end;

function SaveCoords(fname : PChar):vboolean;
var
  fout : vhandle_t;
  i : u32_t;
begin
  if (x=nil) or (y=nil) then
  begin
    Result := false;
    Exit;
  end;
  fout := fileOpenWrite(fname);
  i := 0;
  while i < x^.size do
  begin
    (*
     * We can't use '%g' format because Delphi may expect comma as
     * decimal separator (based on current locale). C RTL always uses dot;
     * we may call 'atof()' from msvcrt.dll, but extra error checking
     * can be easily implemented with Delphi RTL only.
     *)
    filePrintf(fout,'%s %s'+#10,
      [FloatToStr(x^.elements^[i]),FloatToStr(y^.elements^[i])]);
    i := i+1;
  end;
  LibV.fileClose(fout);
  Result := true;
end;

function HaveData:vboolean;
begin
  Result := x <> nil;
end;

(*
 * Draws wireframe box with given corners
 *)
procedure DrawBoundingBox(minx,miny,maxx,maxy:vec_t);
begin
	glColor4f(1,1,1,0.7);
	glBegin(GL_LINE_LOOP);
		glVertex2d(minx,miny);
		glVertex2d(maxx,miny);
		glVertex2d(maxx,maxy);
		glVertex2d(minx,maxy);
	glEnd;
end;

(*
 * Draws polyline and/or vertex ticks
 *)
procedure DrawLineStrip(x,y:vector_p;ticks : vboolean);
var
  i : integer;
begin
  i := 0;
  glBegin(GL_LINE_LOOP);
  while i < x^.size do
  begin
    glVertex2d(x^.elements^[i],y^.elements^[i]);
    i := i+1;
  end;
	glEnd;
  if ticks then
  begin
    i := 0;
    glBegin(GL_POINTS);
    while i < x^.size do
    begin
      glVertex2d(x^.elements^[i],y^.elements^[i]);
      i := i+1;
    end;
  	glEnd;
  end;
end;

procedure DrawCoarse;
begin
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_LINE_SMOOTH);
  glColor3f(1,1,1);
  glLineWidth(2.0);
  glEnable(GL_POINT_SMOOTH);
  glPointSize(4.0);
  DrawLineStrip(x,y,true);
end;

procedure DrawSmooth;
begin
  if sdx=nil then
    Exit;
	glDisable(GL_BLEND);
	glDisable(GL_LINE_SMOOTH);
  glLineWidth(1.0);
  glColor3f(1,1,0);
  DrawLineStrip(sdx,sdy,false);
end;

procedure DrawAll(win_width,win_height:integer);
begin
  glClear(GL_COLOR_BUFFER_BIT);
  if HaveData then
  begin
    (*
     * Logical viewport extents: from -100 to 100
     *)
    glViewport(0,0,win_width,win_height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity;
    glOrtho(-100,100,-100,100,-100,100);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity;
    DrawCoarse;
    DrawSmooth;
  end;
  vglSwapBuffers;
end;

initialization

  x := nil;
  y := nil;
  sdx := nil;
  sdy := nil;
  (* Let Clean() do other job *)
  Clean;

finalization

  Clean;

end.

