Recent

Author Topic: Snaking ZigZag Curve: Finally worked it out !  (Read 1205 times)

Boleeman

  • Hero Member
  • *****
  • Posts: 1001
Snaking ZigZag Curve: Finally worked it out !
« on: April 23, 2025, 02:46:10 pm »
Hi All.

I tried making a Snaking ZigZag curve using Lazarus but the curve does not seem to be working consistently to give a ZigZag geometry (As shown in the 1st attached png).

Not sure why it is happening? Wonder if anyone can figure out why?
(sort of seems to rest when both TSpinEdits are the same value)


Finally worked it out. See last reply.
« Last Edit: October 14, 2025, 02:11:42 pm by Boleeman »

Handoko

  • Hero Member
  • *****
  • Posts: 5485
  • My goal: build my own game engine using Lazarus
Re: Snaking ZigZag Curve: PROBLEMS with shape consistency !
« Reply #1 on: April 23, 2025, 10:12:04 pm »
Bugs fixed.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
  9.   Spin, BGRABitmap, BGRABitmapTypes, Math;
  10.  
  11. type
  12.  
  13.   { TForm1 }
  14.  
  15.   TForm1 = class(TForm)
  16.     btnRefresh: TButton;
  17.     CheckBox1: TCheckBox;
  18.     Label1: TLabel;
  19.     Label2: TLabel;
  20.     PaintBox1: TPaintBox;
  21.     Panel1: TPanel;
  22.     seiAcross: TSpinEdit;
  23.     sejDown: TSpinEdit;
  24.     procedure btnRefreshClick(Sender: TObject);
  25.     procedure PaintBox1Paint(Sender: TObject);
  26.     procedure SpinChange(Sender: TObject);
  27.   private
  28.     procedure DrawZigZagPath(across, down: Integer; bmp: TBGRABitmap);
  29.   public
  30.  
  31.   end;
  32.  
  33. var
  34.   Form1: TForm1;
  35.  
  36. implementation
  37.  
  38. {$R *.lfm}
  39.  
  40. procedure TForm1.DrawZigZagPath(across, down: Integer; bmp: TBGRABitmap);
  41. var
  42.   points, rotated: array of TPointF;
  43.   x, y, stepCount, dx, dy: Integer;
  44.   gridX, gridY: Single;
  45.   i: Integer;
  46.   minX, maxX, minY, maxY, cx, cy: Single;
  47.   Alternatif: Boolean;
  48.   Temp: TPointF;
  49. begin
  50.   bmp.Fill(BGRA(0, 0, 0));
  51.  
  52.   //across := seiAcross.Value;
  53.   //down := sejDown.Value;
  54.  
  55.   gridX := bmp.Width / (across + 2);
  56.   gridY := bmp.Height / (down + 2);
  57.  
  58.   SetLength(points, 0);
  59.   x := 0; y := 0;
  60.   stepCount := 1;
  61.  
  62.   SetLength(points, Length(points) + 1);
  63.   points[High(points)] := PointF((x + 1) * gridX, (y + 1) * gridY);
  64.   Inc(y);
  65.   SetLength(points, Length(points) + 1);
  66.   points[High(points)] := PointF((x + 1) * gridX, (y + 1) * gridY);
  67.  
  68.   while True do
  69.   begin
  70.     if stepCount mod 2 = 1 then
  71.     begin
  72.       dx := stepCount;
  73.       dy := -stepCount;
  74.     end
  75.     else
  76.     begin
  77.       dx := -stepCount;
  78.       dy := stepCount;
  79.     end;
  80.  
  81.     Inc(x, dx);
  82.     Inc(y, dy);
  83.     if (x < 0) or (x > across) or (y < 0) or (y > down) then Break;
  84.  
  85.     SetLength(points, Length(points) + 1);
  86.     points[High(points)] := PointF((x + 1) * gridX, (y + 1) * gridY);
  87.  
  88.     if stepCount mod 2 = 1 then
  89.       Inc(x)
  90.     else
  91.       Inc(y);
  92.  
  93.     if (x < 0) or (x > across) or (y < 0) or (y > down) then Break;
  94.  
  95.     SetLength(points, Length(points) + 1);
  96.     points[High(points)] := PointF((x + 1) * gridX, (y + 1) * gridY);
  97.  
  98.     Inc(stepCount);
  99.   end;
  100.  
  101.   // Rotate the path 180° about its center
  102.   if Length(points) > 0 then
  103.   begin
  104.     minX := points[0].X; maxX := minX;
  105.     minY := points[0].Y; maxY := minY;
  106.  
  107.     for i := 1 to High(points) do
  108.     begin
  109.       if points[i].X < minX then minX := points[i].X else if points[i].X > maxX then maxX := points[i].X;
  110.       if points[i].Y < minY then minY := points[i].Y else if points[i].Y > maxY then maxY := points[i].Y;
  111.     end;
  112.  
  113.     cx := (minX + maxX) / 2;
  114.     cy := (minY + maxY) / 2;
  115.  
  116.     SetLength(rotated, Length(points));
  117.     for i := 0 to High(points) do
  118.       rotated[i] := PointF(2 * cx - points[i].X, 2 * cy - points[i].Y);
  119.  
  120.     Alternatif := False;
  121.     if Odd(seiAcross.Value+1) and (sejDown.Value>seiAcross.Value) then
  122.       Alternatif := True;
  123.     if Odd(seiAcross.Value+1) and (sejDown.Value<seiAcross.Value) and Odd(sejDown.Value) then
  124.       Alternatif := True;
  125.     if Odd(seiAcross.Value) and odd(sejDown.Value) and (seiAcross.Value > sejDown.Value) then
  126.       Alternatif := True;
  127.  
  128.     for i := 1 to High(rotated) do
  129.     begin
  130.       if rotated[i].X = rotated[i+1].X-100 then rotated[i].X := rotated[i+1].X;
  131.       if not(Alternatif) then Continue;
  132.       if i mod 4 = 2 then
  133.       begin
  134.         Temp := rotated[i];
  135.         rotated[i] := rotated[i-1];
  136.         rotated[i-1] := Temp;
  137.       end;
  138.       if i mod 4 = 3 then
  139.       begin
  140.         Temp := rotated[i];
  141.         rotated[i] := rotated[i+1];
  142.         rotated[i+1] := Temp;
  143.       end;
  144.     end;
  145.   end;
  146.  
  147.   for i := 0 to High(points) - 1 do
  148.     bmp.DrawLineAntialias(points[i].X, points[i].Y, points[i + 1].X, points[i + 1].Y, BGRA(255, 255, 0), 2);
  149.  
  150.   for i := 0 to High(rotated) - 2 do
  151.     bmp.DrawLineAntialias(rotated[i].X, rotated[i].Y, rotated[i + 1].X, rotated[i + 1].Y, BGRA(0, 255, 255), 2);
  152. end;
  153.  
  154.  
  155. procedure TForm1.btnRefreshClick(Sender: TObject);
  156. begin
  157.  Paintbox1.Invalidate;
  158. end;
  159.  
  160. procedure TForm1.PaintBox1Paint(Sender: TObject);
  161. var
  162.   bmp: TBGRABitmap;
  163. begin
  164.   bmp := TBGRABitmap.Create(PaintBox1.Width, PaintBox1.Height);
  165.   try
  166.     DrawZigZagPath(seiAcross.Value, sejDown.Value, bmp);
  167.     bmp.Draw(PaintBox1.Canvas, 0, 0, True);
  168.   finally
  169.     bmp.Free;
  170.   end;
  171. end;
  172.  
  173. procedure TForm1.SpinChange(Sender: TObject);
  174. begin
  175.   if CheckBox1.Checked then
  176.     PaintBox1.Invalidate;
  177. end;
  178.  
  179. end.


The edited code were lines #120 - #144 and #150.

Boleeman

  • Hero Member
  • *****
  • Posts: 1001
Re: Snaking ZigZag Curve: PROBLEMS with shape consistency !
« Reply #2 on: April 24, 2025, 12:42:14 pm »
Thanks Handoko for help me out.

The curved path is now uniform, but the logic in my code is not quite correct.

When increasing the across TSpinEdit to say 17, only 10 grid spaces are used to draw the pattern but what should happen is that the remaining 7 grid spaces also need to be used to draw the remaining lines.

Also for some reason on my AMD CPU laptop I kept on getting an error when having Checkbox1.checked.


After reflecting a bit today I realized I was using using grid spaces, rather than using points across and down (like in the JavaScript version. Need to go back to the drawing board and rethink the algorithm.

Thanks Handoko for that updated code. Much appreciated.

Boleeman

  • Hero Member
  • *****
  • Posts: 1001
Re: Snaking ZigZag Curve: PROBLEMS with shape consistency !
« Reply #3 on: October 14, 2025, 02:09:45 pm »
Ah, I finally worked it out:

« Last Edit: October 15, 2025, 10:47:15 am by Boleeman »

Josh

  • Hero Member
  • *****
  • Posts: 1424
Re: Snaking ZigZag Curve: Finally worked it out !
« Reply #4 on: October 17, 2025, 11:03:39 am »
Hi

Here is a small version, i am painting on tpanel using on paint, should be simple to mod for anything.
using various properties of tpanel for parameters.

font.color = line color
borderwidth= line width
tag = stepx,stepy
bevelwidth= border offset

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Panel1Paint(Sender: TObject);
  2. var StepX,StepY,MaxX,MaxY,Phase:Integer;
  3.     FinishedDrawing:Boolean=false;
  4.     DrawPoints:array[0..3] of TPoint;
  5.     BorderOffset:Integer;
  6.  
  7. procedure PointCalculator;
  8. begin
  9.   case Phase of
  10.     {Vertical down phases}
  11.     0,3:begin
  12.           if DrawPoints[Phase].y+StepY+BorderOffset>MaxY then inc(DrawPoints[Phase].x,StepX) else inc(DrawPoints[Phase].y,StepY);
  13.           if Phase=0 then FinishedDrawing:=DrawPoints[Phase].x+BorderOffset>MaxX;
  14.         end;
  15.     {Horizontal right phases}
  16.     1,2:if DrawPoints[Phase].x+StepX+BorderOffset>MaxX then inc(DrawPoints[Phase].y,StepY) else inc(DrawPoints[Phase].x,StepX);
  17.     end;
  18. end;
  19.  
  20. begin
  21.   if sender is TPanel then
  22.   begin
  23.     with Sender as TPanel do
  24.     begin
  25.       BorderOffset:=BevelWidth;
  26.       StepX:=Tag;StepY:=Tag; {Uses tag for step amount}
  27.       DrawPoints[0]:=point(BorderOffset,BorderOffset);DrawPoints[1]:=point(BorderOffset,BorderOffset);
  28.       DrawPoints[2]:=point(StepX+BorderOffset,BorderOffset);DrawPoints[3]:=point(BorderOffset,StepY+BorderOffset);
  29.       MaxX:=width;
  30.       MaxY:=height;
  31.       Canvas.MoveTo(BorderOffset,BorderOffset); {Initial Point}
  32.       Canvas.Pen.Color:=Font.Color; {line color from font color}
  33.       Canvas.Pen.Width:=BorderWidth; {pen width from border width}
  34.       Phase:=0;
  35.       while not FinishedDrawing do
  36.       begin
  37.         PointCalculator;
  38.         if not FinishedDrawing then Canvas.LineTo(DrawPoints[Phase].X,DrawPoints[Phase].Y);
  39.         PointCalculator;
  40.         inc(Phase);
  41.         Phase:=Phase mod 4;
  42.       end;
  43.     end;
  44.   end;
  45. end;
« Last Edit: October 17, 2025, 11:35:17 am by Josh »
The best way to get accurate information on the forum is to post something wrong and wait for corrections.

Josh

  • Hero Member
  • *****
  • Posts: 1424
Re: Snaking ZigZag Curve: Finally worked it out !
« Reply #5 on: October 17, 2025, 01:50:09 pm »
moded the panel paint to buffer drawing and optional support bgrabitmap,
if you create a define in code {$define usingbgrabitmap}   
then it uses tbgrabitmap for buffering

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Panel1Paint(Sender: TObject);
  2. var StepX,StepY,MaxX,MaxY,Phase:Integer;
  3.     FinishedDrawing:Boolean=false;
  4.     DrawPoints:array[0..3] of TPoint;
  5.     BorderOffset:Integer;
  6.     {$ifdef usingbgrabitmap}bmp:tbgrabitmap;
  7.     px,py:Integer;
  8.     {$else}
  9.     bmp:tbitmap;
  10.     {$endif}
  11.  
  12. procedure PointCalculator;
  13. begin
  14.   case Phase of
  15.     {Vertical down phases}
  16.     0,3:begin
  17.           if DrawPoints[Phase].y+StepY+BorderOffset>MaxY then inc(DrawPoints[Phase].x,StepX) else inc(DrawPoints[Phase].y,StepY);
  18.           if Phase=0 then FinishedDrawing:=DrawPoints[Phase].x+BorderOffset>MaxX;
  19.         end;
  20.     {Horizontal right phases}
  21.     1,2:if DrawPoints[Phase].x+StepX+BorderOffset>MaxX then inc(DrawPoints[Phase].y,StepY) else inc(DrawPoints[Phase].x,StepX);
  22.     end;
  23. end;
  24.  
  25. begin
  26.   if sender is TPanel then
  27.   begin
  28.     with Sender as TPanel do
  29.     begin
  30.       BorderOffset:=BevelWidth;
  31.       StepX:=Tag;StepY:=Tag; {Uses tag for step amount}
  32.       DrawPoints[0]:=point(BorderOffset,BorderOffset);DrawPoints[1]:=point(BorderOffset,BorderOffset);
  33.       DrawPoints[2]:=point(StepX+BorderOffset,BorderOffset);DrawPoints[3]:=point(BorderOffset,StepY+BorderOffset);
  34.       MaxX:=width;
  35.       MaxY:=height;
  36.       {$ifdef usingbgrabitmap}
  37.       bmp:=tbgrabitmap.Create(MaxX,MaxY,color);
  38.       bmp.AntialiasingDrawMode:=dmSet;
  39.       px:=BorderOffset;py:=BorderOffset;
  40.       {$else}
  41.       bmp:=tbitmap.Create;
  42.       bmp.SetSize(maxX,MaxY);
  43.       bmp.Canvas.Brush.Color:=Color;
  44.       bmp.Canvas.Brush.Style:=bsSolid;
  45.       bmp.Canvas.FillRect(0,0,maxx,maxy);
  46.       bmp.Canvas.MoveTo(BorderOffset,BorderOffset); {Initial Point}
  47.       bmp.Canvas.Pen.Color:=Font.Color; {line color from font color}
  48.       bmp.Canvas.Pen.Width:=BorderWidth; {pen width from border width}
  49.       {$endif}
  50.       Phase:=0;
  51.       while not FinishedDrawing do
  52.       begin
  53.         PointCalculator;
  54.         if not FinishedDrawing then
  55.         begin
  56.           {$ifdef usingbgrabitmap}
  57.           bmp.DrawLineAntialias(px,py,DrawPoints[Phase].X,DrawPoints[Phase].Y,Font.Color,BorderWidth);
  58.           px:=DrawPoints[Phase].X;
  59.           py:=DrawPoints[Phase].y;
  60.           {$else}
  61.           bmp.Canvas.LineTo(DrawPoints[Phase].X,DrawPoints[Phase].Y);
  62.           {$endif}
  63.         end;
  64.         PointCalculator;
  65.         inc(Phase);
  66.         Phase:=Phase mod 4;
  67.       end;
  68.       {$ifdef usingbgrabitmap}
  69.       bmp.Draw(canvas,0,0);
  70.       {$else}
  71.       canvas.Draw(0,0,bmp);
  72.       {$endif}
  73.       bmp.Free;
  74.     end;
  75.   end;
  76. end;                  
The best way to get accurate information on the forum is to post something wrong and wait for corrections.

Josh

  • Hero Member
  • *****
  • Posts: 1424
Re: Snaking ZigZag Curve: Finally worked it out !
« Reply #6 on: October 17, 2025, 03:48:15 pm »
final mod, added ability to makethe line drawn from ink nib
setting tpanel wordwrap to true draws pen nib mode..

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Panel1Paint(Sender: TObject);
  2. var StepX,StepY,MaxX,MaxY,Phase:Integer;
  3.     FinishedDrawing:Boolean=false;
  4.     DrawPoints:array[0..3] of TPoint;
  5.     BorderOffset:Integer;
  6.     {$ifdef usingbgrabitmap}bmp:tbgrabitmap;
  7.     px,py:Integer;
  8.     darkColor,midColor,lightColor,blended:TBGRAPixel;
  9.     i:Integer;
  10.     t,brightness:Single;
  11.     {$else}
  12.     bmp:tbitmap;
  13.     {$endif}
  14.  
  15. procedure PointCalculator;
  16. begin
  17.   case Phase of
  18.     {Vertical down phases}
  19.     0,3:begin
  20.           if DrawPoints[Phase].y+StepY+BorderOffset>MaxY then inc(DrawPoints[Phase].x,StepX) else inc(DrawPoints[Phase].y,StepY);
  21.           if Phase=0 then FinishedDrawing:=DrawPoints[Phase].x+BorderOffset>MaxX;
  22.         end;
  23.     {Horizontal right phases}
  24.     1,2:if DrawPoints[Phase].x+StepX+BorderOffset>MaxX then inc(DrawPoints[Phase].y,StepY) else inc(DrawPoints[Phase].x,StepX);
  25.     end;
  26. end;
  27.  
  28. {$ifdef usingbgrabitmap}
  29. function BlendColor(const c1,c2:TBGRAPixel;t:Single):TBGRAPixel;
  30. begin
  31.   Result.red:=round(c1.red+(c2.red-c1.red)*t);
  32.   Result.green:=round(c1.green+(c2.green-c1.green)*t);
  33.   Result.blue:=round(c1.blue+(c2.blue-c1.blue)*t);
  34.   Result.alpha:=round(c1.alpha+(c2.alpha-c1.alpha)*t);
  35. end;
  36. {$endif}
  37.  
  38. begin
  39.   if sender is TPanel then
  40.   begin
  41.     with Sender as TPanel do
  42.     begin
  43.       BorderOffset:=BevelWidth;
  44.       StepX:=Tag;StepY:=Tag; {Uses tag for step amount}
  45.       DrawPoints[0]:=point(BorderOffset,BorderOffset);DrawPoints[1]:=point(BorderOffset,BorderOffset);
  46.       DrawPoints[2]:=point(StepX+BorderOffset,BorderOffset);DrawPoints[3]:=point(BorderOffset,StepY+BorderOffset);
  47.       MaxX:=width;
  48.       MaxY:=height;
  49.       {$ifdef usingbgrabitmap}
  50.       bmp:=tbgrabitmap.Create(MaxX,MaxY,color);
  51.       bmp.AntialiasingDrawMode:=dmSet;
  52.       px:=BorderOffset;py:=BorderOffset;
  53.       {$else}
  54.       bmp:=tbitmap.Create;
  55.       bmp.SetSize(maxX,MaxY);
  56.       bmp.Canvas.Brush.Color:=Color;
  57.       bmp.Canvas.Brush.Style:=bsSolid;
  58.       bmp.Canvas.FillRect(0,0,maxx,maxy);
  59.       bmp.Canvas.MoveTo(BorderOffset,BorderOffset); {Initial Point}
  60.       bmp.Canvas.Pen.Color:=Font.Color; {line color from font color}
  61.       bmp.Canvas.Pen.Width:=BorderWidth; {pen width from border width}
  62.       {$endif}
  63.       Phase:=0;
  64.       while not FinishedDrawing do
  65.       begin
  66.         PointCalculator;
  67.         if not FinishedDrawing then
  68.         begin
  69.           {$ifdef usingbgrabitmap}
  70.           if wordwrap then
  71.           begin
  72.             darkColor:=BGRA(60,60,60);
  73.             midColor:=ColorToBGRA(Font.Color);
  74.             lightColor:=BGRA(255,255,255);
  75.             for i:=-BorderWidth div 2 to BorderWidth div 2 do
  76.             begin
  77.               t:=(i+BorderWidth/2)/BorderWidth;
  78.               brightness:=Power(t,0.6); // non-linear highlight
  79.               blended:=BlendColor(BlendColor(darkColor,lightColor,brightness),midColor, 0.4);
  80.               bmp.DrawLineAntialias(px+i,py+i,DrawPoints[Phase].X+i,DrawPoints[Phase].Y+i,blended,1);
  81.             end;
  82.           end else bmp.DrawLineAntialias(px,py,DrawPoints[Phase].X,DrawPoints[Phase].Y,Font.Color,BorderWidth);
  83.           px:=DrawPoints[Phase].X;
  84.           py:=DrawPoints[Phase].y;
  85.           {$else}
  86.           bmp.Canvas.LineTo(DrawPoints[Phase].X,DrawPoints[Phase].Y);
  87.           {$endif}
  88.         end;
  89.         PointCalculator;
  90.         inc(Phase);
  91.         Phase:=Phase mod 4;
  92.       end;
  93.       {$ifdef usingbgrabitmap}
  94.       bmp.Draw(canvas,0,0);
  95.       {$else}
  96.       canvas.Draw(0,0,bmp);
  97.       {$endif}
  98.       bmp.Free;
  99.     end;
  100.   end;
  101. end;                  
« Last Edit: October 17, 2025, 03:53:08 pm by Josh »
The best way to get accurate information on the forum is to post something wrong and wait for corrections.

Boleeman

  • Hero Member
  • *****
  • Posts: 1001
Re: Snaking ZigZag Curve: Finally worked it out !
« Reply #7 on: October 17, 2025, 04:37:11 pm »
Josh I love the way you got it to resize by resizing the panel. Impressive.

Was so curious that I experimented with your {$define usingbgrabitmap}  version.

Wow that 3D ink line is also a nice feature.

Thought it might be nice to somehow add a glow effect to the line like in my glow fractal tree:
https://forum.lazarus.freepascal.org/index.php/topic,70280.msg547679.html#msg547679

Blown away with your coding.
« Last Edit: October 17, 2025, 04:46:39 pm by Boleeman »

Josh

  • Hero Member
  • *****
  • Posts: 1424
Re: Snaking ZigZag Curve: Finally worked it out !
« Reply #8 on: October 17, 2025, 05:58:55 pm »
zigy glow, setting tpanel enabled will generate glow and only if glow intensity>0
The best way to get accurate information on the forum is to post something wrong and wait for corrections.

Josh

  • Hero Member
  • *****
  • Posts: 1424
Re: Snaking ZigZag Curve: Finally worked it out !
« Reply #9 on: October 18, 2025, 12:58:53 am »
Moded it so its a self contained procedure

Code: Pascal  [Select][+][-]
  1. Procedure DrawigZagOnCanvas(Const ACanvas:TCanvas;AWidth,AHeight:Integer;ABackGroundColor,APenColor:TColor;
  2.                             APenWidth:Integer;StepX,StepY:Integer;BorderSize:Integer;
  3.                             GlowEffect:Boolean;GlowStrength:Single;ANibPen:Boolean);

You can then put it in onpaint event

Demo left is tpanel, right is paintbox,with different StepX and StepY values,which creates a distorted diag.
The best way to get accurate information on the forum is to post something wrong and wait for corrections.

 

TinyPortal © 2005-2018