unit dlinegen;

{$MODE FPC}
{$MODESWITCH DEFAULTPARAMETERS}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}

interface

uses
  dstrnum,
  DDrawVertexBuffer,
  dequation,
  dvector2f,
  dmath;

type
TJoinType = (JT_BEVEL, JT_MITER, JT_ROUND);

PLineSegment = ^TLineSegment;
TLineSegment = record
  // Beginning of the segment:
  V: TVec2f;
  Width: Single;
  Color: UInt32;
  // Joint with previous segment:
  JointType: TJoinType;
  MiterLimitFactor: Single; // for JT_MITER
end;

PLineMesh = ^TLineMesh;
TLineMesh = record
  Vertices: PVec2f;
  Colors: PUInt32;
  VerticesCount: SizeUInt;
  Indices: TIndexBuffer.PIndex;
  IndicesCount: SizeUInt;
end;

procedure BuildLineMesh(Segments: PLineSegment; Count: SizeUInt; out Mesh: TLineMesh);
procedure FreeLineMesh(var Mesh: TLineMesh);

implementation

function CrossRays(const A, ADir, B, BDir: TVec2f; out V: TVec2f): Boolean;
begin
  Exit(False);
end;

function ComputeLine(const Origin, Dir: TVec2f; out Normal: TVec2f): Single;
//  Normal.X * X + Normal.Y * Y + Result = 0
begin
  Normal.X :=   Dir.Y;
  Normal.Y := - Dir.X;
  Normal := Norm2f(Normal);
  Result := - (Origin.X * Normal.X + Origin.Y * Normal.Y);
end;

function IntersectRayVsRay(const A, ADir, B, BDir: TVec2f; out T1, T2: Single): Boolean;
begin
  Result := Solve2LinearEquations2f(ADir.X, - BDir.X, B.X - A.X,
                                    ADir.Y, - BDir.Y, B.Y - A.Y,
                                    T1, T2);
end;

procedure BuildLineMesh(Segments: PLineSegment; Count: SizeUInt; out Mesh: TLineMesh);

type
  TSegmentInfo = record
    Edge, Dir, Normal: TVec2f;
    U1, D1, U2, D2: TVec2f;
    HalfWidth: Single;
  end;

  procedure ComputeNormal(I: LongInt; out Normal: TVec2f);
  var
    A, B, Dir: TVec2f;
  begin
    if I <= 0 then begin
      A := Segments[0].V;
      B := Segments[1].V;
    end else if I >= Count - 1 then begin
      A := Segments[Count - 2].V;
      B := Segments[Count - 1].V;
    end else begin
      A := Segments[I - 1].V;
      B := Segments[I + 1].V;
    end;
    Dir := Norm2f(Sub2f(B, A));
    Normal := Vec2f(Dir.Y, - Dir.X);
  end;

  procedure LoadSegmentInfo(I: LongInt; out Info: TSegmentInfo);
  var
    P1, P2: TVec2f;
  begin
    P1 := Segments[I    ].V;
    P2 := Segments[I + 1].V;
    Info.Edge := Sub2f(P2, P1);
    Info.Dir := Norm2f(Info.Edge);
    Info.Normal := Vec2f(Info.Dir.Y, - Info.Dir.X);
    Info.HalfWidth := Segments[I].Width / 2;
    Info.U1 := Add2f(P1, Mul2f(Info.Normal,   Info.HalfWidth));
    Info.D1 := Add2f(P1, Mul2f(Info.Normal, - Info.HalfWidth));
    Info.U2 := Add2f(P2, Mul2f(Info.Normal,   Info.HalfWidth));
    Info.D2 := Add2f(P2, Mul2f(Info.Normal, - Info.HalfWidth));
  end;

var
  I, J: LongInt;
  V: array of TVec2f;
  Colors: array of UInt32;
  F: array of TIndexBuffer.TIndex;
  HalfWidth: Single;
  HalfWidth2: Single;
  A, B, C: TVec2f;
  Dir, Normal, Normal2: TVec2f;
  U, D, U1, D1, U2, D2: TVec2f;
  Coeff: Single;
  PrevUp, PrevDown: LongInt;
  CrossProduct: Single;
  T1, T2: Single;
  Prev, Curr, Next: TSegmentInfo;
  Looped: Boolean;

  procedure AddUp(const P: TVec2f);
  begin
    SetLength(V, Length(V) + 1);
    V[High(V)] := P;
    SetLength(F, Length(F) + 3);
    F[High(F) - 2] := PrevUp;
    F[High(F) - 1] := High(V);
    F[High(F) - 0] := PrevDown;
    PrevUp := High(V);
  end;
  procedure AddDown(const P: TVec2f);
  begin
    SetLength(V, Length(V) + 1);
    V[High(V)] := P;
    SetLength(F, Length(F) + 3);
    F[High(F) - 2] := PrevUp;
    F[High(F) - 1] := High(V);
    F[High(F) - 0] := PrevDown;
    PrevDown := High(V);
  end;
  procedure AddArcUp(const V, A, B: TVec2f; W1, W2: Single);
  var
    I: LongInt;
    AngleA, AngleB, Angle, W: Single;
  begin
    AngleA := ArcTan2(A.Y - V.Y, A.X - V.X);
    AngleB := ArcTan2(B.Y - V.Y, B.X - V.X);
    if AngleB - AngleA > pi then
      AngleB := AngleB - 2 * pi;
    if AngleB - AngleA < -pi then
      AngleB := AngleB + 2 * pi;
    for I := 0 to 10 do begin // TODO compute limit
      Angle := AngleA + (I / 10) * (AngleB - AngleA);
      W := W1 + (I / 10) * (W2 - W1);
      AddUp(Add2f(V, Vec2f(W * Cos(Angle), W * Sin(Angle))));
    end;
  end;
  procedure AddArcDown(const V, A, B: TVec2f; W1, W2: Single);
  var
    I: LongInt;
    AngleA, AngleB, Angle, W, Delta: Single;
  begin
    AngleA := ArcTan2(A.Y - V.Y, A.X - V.X);
    AngleB := ArcTan2(B.Y - V.Y, B.X - V.X);
    if AngleB - AngleA > pi then
      AngleB := AngleB - 2 * pi;
    if AngleB - AngleA < -pi then
      AngleB := AngleB + 2 * pi;
    for I := 0 to 10 do begin // TODO compute limit
      Angle := AngleA + (I / 10) * (AngleB - AngleA);
      W := W1 + (I / 10) * (W2 - W1);
      AddDown(Add2f(V, Vec2f(W * Cos(Angle), W * Sin(Angle))));
    end;
  end;

begin
  V := nil;
  Colors := nil;
  F := nil;
  if Count < 2 then begin
    Mesh.Vertices := nil;
    Mesh.Colors := nil;
    Mesh.VerticesCount := 0;
    Mesh.Indices := nil;
    Mesh.IndicesCount := 0;
    Exit;
  end;
  Looped := (Segments[0].V.X = Segments[Count - 1].V.X) and (Segments[0].V.Y = Segments[Count - 1].V.Y);
  A := Segments[0].V;
  B := Segments[1].V;
  LoadSegmentInfo(0,     Curr);
  ComputeNormal(0, Normal);
  HalfWidth := Segments[0].Width / 2.0;
  U := Vec2f(A.X + HalfWidth * Normal.X, A.Y + HalfWidth * Normal.Y);
  D := Vec2f(A.X - HalfWidth * Normal.X, A.Y - HalfWidth * Normal.Y);

  if not Looped then begin // generating openning cap (squere only for now)
    U := Sub2f(U, Mul2f(Norm2f(Curr.Edge), HalfWidth));
    D := Sub2f(D, Mul2f(Norm2f(Curr.Edge), HalfWidth));
  end;

  SetLength(V, 2);
  V[0] := U;
  V[1] := D;
  PrevUp := 0;
  PrevDown := 1;
  for I := 1 to Count - 2 do begin
    B := Segments[I].V;
    C := Segments[I + 1].V;
    LoadSegmentInfo(I - 1, Prev);
    LoadSegmentInfo(I,     Curr);
    Dir := Norm2f(Sub2f(B, A));
    ComputeNormal(I, Normal);
    HalfWidth := Segments[I].Width / 2.0;
    Coeff := 1 - Dot2f(Dir, Normal);
    CrossProduct := Cross2f(Prev.Edge, Curr.Edge);
    if Abs(CrossProduct) < 0.1 then begin // colinear
      AddUp(Curr.U1);
      AddDown(Curr.D1);
    end else if CrossProduct > 0 then begin // clockwise
      if IntersectRayVsRay(Prev.D1, Prev.Edge, Curr.D1, Curr.Edge, T1, T2) then begin
        if (T1 < 0) or (T2 > 1) then begin
          D := Prev.D1;
        end else if (T1 > 1) or (T2 < 0) then begin
          D := Prev.D2;
        end else
          D := Add2f(Prev.D1, Mul2f(Prev.Edge, T1));
        AddDown(D);
        // AddDown(Prev.D2);
        if Segments[I].JointType = JT_BEVEL then begin
          AddUp(Prev.U2);
          AddUp(Curr.U1);
        end else if Segments[I].JointType = JT_MITER then begin
          if IntersectRayVsRay(Prev.U1, Prev.Edge, Curr.U1, Curr.Edge, T1, T2) then begin
            U := Add2f(Prev.U1, Mul2f(Prev.Edge, T1));
            if Dist2f(Segments[I].V, U) / Segments[I].Width > Segments[I].MiterLimitFactor then begin
              AddUp(Prev.U2);
              AddUp(Curr.U1);
            end else
              AddUp(U);
          end else begin
            AddUp(Prev.U2);
            AddUp(Curr.U1);
          end;
        end else if Segments[I].JointType = JT_ROUND then begin
          AddArcUp(Segments[I].V, Prev.U2, Curr.U1, Prev.HalfWidth, Curr.HalfWidth);
          {for J := 0 to 10 do begin
            AddUp(Add2f(Segments[J].V, Mul2f(Norm2f(Add2f(Mul2f(Prev.Normal, J / 10), Mul2f(Curr.Normal, (10 - J) / 10))), Curr.HalfWidth)));
          end;}
        end;
      end else begin
        // WTF?
        Writeln('WTF?');
        AddUp(Curr.U1);
        AddDown(Curr.D1);
      end;
    end else begin // counter clockwise
      if IntersectRayVsRay(Prev.U1, Prev.Edge, Curr.U1, Curr.Edge, T1, T2) then begin
        if (T1 < 0) or (T2 > 1) then begin
          U := Prev.U1;
        end else if (T1 > 1) or (T2 < 0) then begin
          U := Prev.U2;
        end else
          U := Add2f(Prev.U1, Mul2f(Prev.Edge, T1));
        AddUp(U);
        // AddUp(Prev.U2);
        if Segments[I].JointType = JT_BEVEL then begin
          AddDown(Prev.D2);
          AddDown(Curr.D1);
        end else if Segments[I].JointType = JT_MITER then begin
          if IntersectRayVsRay(Prev.D1, Prev.Edge, Curr.D1, Curr.Edge, T1, T2) then begin
            D := Add2f(Prev.D1, Mul2f(Prev.Edge, T1));
            if Dist2f(Segments[I].V, D) / Segments[I].Width > Segments[I].MiterLimitFactor then begin
              AddDown(Prev.D2);
              AddDown(Curr.D1);
            end else
              AddDown(D);
          end else begin
            AddUp(Prev.D2);
            AddUp(Curr.D1);
          end;
        end else if Segments[I].JointType = JT_ROUND then begin
          AddArcDown(Segments[I].V, Prev.D2, Curr.D1, Prev.HalfWidth, Curr.HalfWidth);
          //for J := 0 to 10 do begin
          //  AddUp(Sub2f(Segments[J].V, Mul2f(Norm2f(Add2f(Mul2f(Prev.Normal, J / 10), Mul2f(Curr.Normal, (10 - J) / 10))), Curr.HalfWidth)));
          //end;
        end;
      end else begin
        // WTF?
        Writeln('WTF?');
        AddUp(Curr.U1);
        AddDown(Curr.D1);
      end;
    end;
    A := B;
  end;

  B := Segments[Count - 1].V;
  HalfWidth := Segments[Count - 1].Width / 2.0;
  LoadSegmentInfo(Count - 2, Prev);
  ComputeNormal(Count - 1, Normal);
  U := Vec2f(B.X + HalfWidth * Normal.X, B.Y + HalfWidth * Normal.Y);
  D := Vec2f(B.X - HalfWidth * Normal.X, B.Y - HalfWidth * Normal.Y);
  if not Looped then begin // generating closing cap (squere only for now)
    U := Add2f(U, Mul2f(Norm2f(Prev.Edge), HalfWidth));
    D := Add2f(D, Mul2f(Norm2f(Prev.Edge), HalfWidth));
  end;
  AddUp(U);
  AddDown(D);

  Mesh.Vertices := GetMem(Length(V) * SizeOf(TVec2f));
  Mesh.VerticesCount := Length(V);
  Mesh.Colors := GetMem(Length(Colors) * SizeOf(UInt32));
  Mesh.Indices := GetMem(Length(F) * SizeOf(TIndexBuffer.TIndex));
  Mesh.IndicesCount := Length(F);
  Move(V[0], Mesh.Vertices[0], Length(V) * SizeOf(TVec2f));
  // Move(Colors[0], Mesh.Colors[0], Length(V) * SizeOf(UInt32));
  Move(F[0], Mesh.Indices[0], Length(F) * SizeOf(TIndexBuffer.TIndex));
end;

procedure FreeLineMesh(var Mesh: TLineMesh);
begin
  FreeMem(Mesh.Vertices);
  FreeMem(Mesh.Colors);
  FreeMem(Mesh.Indices);
end;

end.
