Minkowski Island: Fill color/back color Problems lazarus / 39-Graphics ---------------------------------------------------------------------- Boleeman · 2024-04-08 13:22 Created a Minkowski Island fractal Read about it here https://mathworld.wolfram.com/MinkowskiSausage.html  and here https://en.wikipedia.org/wiki/Minkowski_sausage It's basically a square Quadratic Koch curve. Can't figure out why its messing up at recursion levels greater than 3. Thought it may be a round of error, seeings as it happens at higher recursion level but cant trace it. Noticed that when I resize the form at level 4 that gap changes. So perhaps its a scaling problem. Some Graphic GURU advice is needed. ---------------------------------------------------------------------- KodeZwerg · 2024-04-08 13:36 By watching pictures, the left image does do those strokes to compensate the missing space to paint full element so my guess is that at initial the value for length for each stroke is calculated wrong. ---------------------------------------------------------------------- Martin_fr · 2024-04-08 14:49 Maybe rounding errors. E.g. you keep summing up floats, and compare against x. But the floats will never reach x. For example Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program Project1;var  a: double;begin  a := 2.0;  while a <> 0 do begin    a := a - 2 / 5;    writeln(a);  end;end. will continue running. Because 2/5 is rounded in binary, and not exactly 0.4 So, maybe something like that? Use "SameValue()" from unit math. ---------------------------------------------------------------------- tetrastes · 2024-04-08 17:05 Try this Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, Buttons,  StdCtrls, Spin, Math; type   TPointSingle = record    X: single;    Y: single;  end;   { TForm1 }   TForm1 = class(TForm)    Label1: TLabel;    Label2: TLabel;    PaintBox1: TPaintBox;    Panel1: TPanel;    seDepth: TSpinEdit;    seLinewidth: TSpinEdit;    procedure PaintBox1Paint(Sender: TObject);    procedure seDepthChange(Sender: TObject);    procedure seLinewidthChange(Sender: TObject);  private   Initiator: array of TPointSingle;   ScaleFactor: Single;   GeneratorDTheta: array of Single;   procedure DrawSnowflake(ACanvas: TCanvas; depth: Integer);   procedure DrawSnowflakeEdge(ACanvas: TCanvas; depth: Integer; var p1: TPointSingle;  theta: Single; dist: Single);  public   end; var  Form1: TForm1; implementation {$R *.lfm} procedure TForm1.DrawSnowflakeEdge(ACanvas: TCanvas; depth: Integer; var p1: TPointSingle;  theta: Single; dist: Single);var  i: Integer;  p2: TPointSingle;  ip1, ip2: TPoint;begin  if depth = 0 then  begin    p2.X := p1.X + (dist * Cos(theta + Pi / 2));    p2.Y := p1.Y + (dist * Sin(theta + Pi / 2));     ip1.X := Trunc(p1.X + 0.5);    // Because  "banker's rounding" is not what I was taught in University    ip1.Y := Trunc(p1.Y + 0.5);    ip2.X := Trunc(p2.X + 0.5);    ip2.Y := Trunc(p2.Y + 0.5);    ACanvas.Line(ip1, ip2);    p1 := p2;    Exit;  end;   dist := dist / Sqrt(5.0);  for i := 0 to High(GeneratorDTheta) do  begin    theta := theta + GeneratorDTheta[i];    DrawSnowflakeEdge(ACanvas, depth - 1, p1, theta, dist);  end;end; procedure TForm1.DrawSnowflake(ACanvas: TCanvas; depth: Integer);var  i: Integer;  p1, p2: TPointSingle;  dx, dy, length, theta: Single;begin  Canvas.Clear;   for i := 1 to High(Initiator) do  begin    p1 := Initiator[i - 1];    p2 := Initiator[i];     dx := p2.X - p1.X;    dy := p2.Y - p1.Y;    length := Sqrt(dx * dx + dy * dy);    theta := ArcTan2(dy, dx);    DrawSnowflakeEdge(ACanvas, depth, p1, theta, length);  end;end; procedure TForm1.PaintBox1Paint(Sender: TObject);var  MinDimension: Integer;begin   SetLength(Initiator, 0);  SetLength(GeneratorDTheta, 0);   // Ensure Initiator array is properly initialized  if Length(Initiator) < 5 then    SetLength(Initiator, 5);    MinDimension := Min(PaintBox1.Width, PaintBox1.Height);  MinDimension := MinDimension - 300;   if Length(Initiator) >= 5 then // Ensure Initiator array has enough elements  begin    {Initiator[0] := Point(Round((PaintBox1.Width - MinDimension) / 2), Round((PaintBox1.Height - MinDimension) / 2));    Initiator[1] := Point(Round((PaintBox1.Width + MinDimension) / 2), Round((PaintBox1.Height - MinDimension) / 2));    Initiator[2] := Point(Round((PaintBox1.Width + MinDimension) / 2), Round((PaintBox1.Height + MinDimension) / 2));    Initiator[3] := Point(Round((PaintBox1.Width - MinDimension) / 2), Round((PaintBox1.Height + MinDimension) / 2));    Initiator[4] := Initiator[0];}    Initiator[0].X := (PaintBox1.Width - MinDimension) / 2;    Initiator[0].Y := (PaintBox1.Height - MinDimension) / 2;    Initiator[1].X := (PaintBox1.Width + MinDimension) / 2;    Initiator[1].Y := (PaintBox1.Height - MinDimension) / 2;    Initiator[2].X := (PaintBox1.Width + MinDimension) / 2;    Initiator[2].Y := (PaintBox1.Height + MinDimension) / 2;    Initiator[3].X := (PaintBox1.Width - MinDimension) / 2;    Initiator[3].Y := (PaintBox1.Height + MinDimension) / 2;    Initiator[4] := Initiator[0];  end;   ScaleFactor := 1 / Sqrt(5);  GeneratorDTheta := nil;  SetLength(GeneratorDTheta, 3);  GeneratorDTheta[0] := -ArcTan(1 / 2);  GeneratorDTheta[1] := Pi / 2;  GeneratorDTheta[2] := -Pi / 2;   // Redraw the snowflake with the adjusted level  DrawSnowflake(PaintBox1.Canvas, seDepth.Value);end; procedure TForm1.seDepthChange(Sender: TObject);begin  PaintBox1.Invalidate;end; procedure TForm1.seLinewidthChange(Sender: TObject);begin  PaintBox1.Canvas.Pen.Width := seLinewidth.Value;  PaintBox1.Invalidate;end; end. ---------------------------------------------------------------------- Boleeman · 2024-04-09 01:41 Thanks tetrastes. Looks like the issue was using point and rounding. I looked at breaking down into x and y point locations, but I was still rounding to get rid of extended value error. Using Initiator[0].X  and Initiator[0].Y type syntax without the rounding was what I was completely missing. Also was good to know about:     ip1.X := Trunc(p1.X + 0.5);      // Because  "banker's rounding" is not what I was taught in University     ip1.Y := Trunc(p1.Y + 0.5);     ip2.X := Trunc(p2.X + 0.5);     ip2.Y := Trunc(p2.Y + 0.5); Noticed now when resizing the form, the fractal resizes well. Thanks once again for your great help. ---------------------------------------------------------------------- Boleeman · 2024-04-09 03:31 I tried setting the fill color of the fractal with a TColorbutton called cbFillcolor and the backcolor of Paintabox1 with a TColorbutton called cbBackcolor but I could not get that to work. Usually its simple to do for a TPaintbox, but don't know why it did not change colors. Here is the complete code: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, Buttons,  StdCtrls, Spin, Math; type   TPointSingle = record    X: single;    Y: single;  end;   { TForm1 }   TForm1 = class(TForm)    cbLinecolor: TColorButton;    cbBackcolor: TColorButton;    cbFillcolor: TColorButton;    Label1: TLabel;    Label2: TLabel;    Label3: TLabel;    Label4: TLabel;    Label5: TLabel;    PaintBox1: TPaintBox;    Panel1: TPanel;    seDepth: TSpinEdit;    seLinewidth: TSpinEdit;    procedure cbFillcolorColorChanged(Sender: TObject);    procedure cbLinecolorColorChanged(Sender: TObject);    procedure cbBackcolorColorChanged(Sender: TObject);    procedure PaintBox1Paint(Sender: TObject);    procedure seDepthChange(Sender: TObject);    procedure seLinewidthChange(Sender: TObject);  private   Initiator: array of TPointSingle;   ScaleFactor: Single;   GeneratorDTheta: array of Single;   procedure DrawSnowflake(ACanvas: TCanvas; depth: Integer);   procedure DrawSnowflakeEdge(ACanvas: TCanvas; depth: Integer; var p1: TPointSingle;  theta: Single; dist: Single);   procedure FillSnowflake(ACanvas: TCanvas; CenterPoint: TPoint; depth: Integer; p1: TPointSingle;  theta: Single; dist: Single);  public   end; var  Form1: TForm1; implementation {$R *.lfm} procedure TForm1.FillSnowflake(ACanvas: TCanvas; CenterPoint: TPoint; depth: Integer; p1: TPointSingle;  theta: Single; dist: Single);var  i: Integer;  p2: TPointSingle;  Points: array of TPoint;begin  if depth = 0 then    Exit;   SetLength(Points, 5);   for i := 1 to High(Initiator) do  begin    p1 := Initiator[i - 1];    p2 := Initiator[i];     Points[0] := CenterPoint;    Points[1].X := Round(p1.X);    Points[1].Y := Round(p1.Y);    Points[2].X := Round(p2.X);    Points[2].Y := Round(p2.Y);    Points[3] := CenterPoint;    Points[4] := Points[1];     ACanvas.Polygon(Points);  end;end; procedure TForm1.DrawSnowflakeEdge(ACanvas: TCanvas; depth: Integer; var p1: TPointSingle;  theta: Single; dist: Single);var  i: Integer;  p2: TPointSingle;  ip1, ip2: TPoint;begin  if depth = 0 then  begin    p2.X := p1.X + (dist * Cos(theta + Pi / 2));    p2.Y := p1.Y + (dist * Sin(theta + Pi / 2));     ip1.X := Trunc(p1.X + 0.5);    // Because  "banker's rounding" is not what I was taught in University    ip1.Y := Trunc(p1.Y + 0.5);    ip2.X := Trunc(p2.X + 0.5);    ip2.Y := Trunc(p2.Y + 0.5);    ACanvas.Line(ip1, ip2);    p1 := p2;    Exit;  end;   dist := dist / Sqrt(5.0);  for i := 0 to High(GeneratorDTheta) do  begin    theta := theta + GeneratorDTheta[i];    DrawSnowflakeEdge(ACanvas, depth - 1, p1, theta, dist);  end;end; procedure TForm1.DrawSnowflake(ACanvas: TCanvas; depth: Integer);var  i: Integer;  p1, p2: TPointSingle;  dx, dy, length, theta: Single;begin  ACanvas.Pen.Color := cbLinecolor.ButtonColor;   for i := 1 to High(Initiator) do  begin    p1 := Initiator[i - 1];    p2 := Initiator[i];     dx := p2.X - p1.X;    dy := p2.Y - p1.Y;    length := Sqrt(dx * dx + dy * dy);    theta := ArcTan2(dy, dx);    DrawSnowflakeEdge(ACanvas, depth, p1, theta, length);  end;end; procedure TForm1.PaintBox1Paint(Sender: TObject);var  MinDimension: Integer;begin   SetLength(Initiator, 0);  SetLength(GeneratorDTheta, 0);   // Ensure Initiator array is properly initialized  if Length(Initiator) < 5 then    SetLength(Initiator, 5);    MinDimension := Min(PaintBox1.Width, PaintBox1.Height);  MinDimension := MinDimension - 300;   if Length(Initiator) >= 5 then // Ensure Initiator array has enough elements  begin     Initiator[0].X := (PaintBox1.Width - MinDimension) / 2;    Initiator[0].Y := (PaintBox1.Height - MinDimension) / 2;    Initiator[1].X := (PaintBox1.Width + MinDimension) / 2;    Initiator[1].Y := (PaintBox1.Height - MinDimension) / 2;    Initiator[2].X := (PaintBox1.Width + MinDimension) / 2;    Initiator[2].Y := (PaintBox1.Height + MinDimension) / 2;    Initiator[3].X := (PaintBox1.Width - MinDimension) / 2;    Initiator[3].Y := (PaintBox1.Height + MinDimension) / 2;    Initiator[4] := Initiator[0];  end;   ScaleFactor := 1 / Sqrt(5);  GeneratorDTheta := nil;  SetLength(GeneratorDTheta, 3);  GeneratorDTheta[0] := -ArcTan(1 / 2);  GeneratorDTheta[1] := Pi / 2;  GeneratorDTheta[2] := -Pi / 2;   DrawSnowflake(PaintBox1.Canvas, seDepth.Value);end; procedure TForm1.cbLinecolorColorChanged(Sender: TObject);beginPaintBox1.Canvas.Pen.Color := cbLinecolor.ButtonColor;PaintBox1.Invalidate;end; procedure TForm1.cbFillcolorColorChanged(Sender: TObject);begin PaintBox1.Invalidate;end; procedure TForm1.cbBackcolorColorChanged(Sender: TObject);begin  PaintBox1.Invalidate;end; procedure TForm1.seDepthChange(Sender: TObject);begin  PaintBox1.Invalidate;end; procedure TForm1.seLinewidthChange(Sender: TObject);begin  PaintBox1.Canvas.Pen.Width := seLinewidth.Value;  PaintBox1.Invalidate;end; end. I could change the line color of the fractal. ---------------------------------------------------------------------- tetrastes · 2024-04-09 08:21 You call FillSnowflake nowhere, and don't use cbFillcolor.ButtonColor and cbBackcolor.ButtonColor. ---------------------------------------------------------------------- Boleeman · 2024-04-09 10:15 I made fillsnowflake like this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.FillSnowflake(ACanvas: TCanvas; CenterPoint: TPoint; depth: Integer; p1: TPointSingle;  theta: Single; dist: Single);var  i: Integer;  p2: TPointSingle;  Points: array of TPoint;begin  if depth = 0 then    Exit;   SetLength(Points, 5);   for i := 1 to High(Initiator) do  begin    p1 := Initiator[i - 1];    p2 := Initiator[i];     Points[0] := CenterPoint;    Points[1].X := Round(p1.X);    Points[1].Y := Round(p1.Y);    Points[2].X := Round(p2.X);    Points[2].Y := Round(p2.Y);    Points[3] := CenterPoint;    Points[4] := Points[1];     ACanvas.Brush.Color := cbFillcolor.ButtonColor;    ACanvas.Polygon(Points);  end;end;       and then I called it in procedure TForm1.PaintBox1Paint(Sender: TObject); Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.PaintBox1Paint(Sender: TObject);var  MinDimension: Integer;begin   SetLength(Initiator, 0);  SetLength(GeneratorDTheta, 0);   // Ensure Initiator array is properly initialized  if Length(Initiator) < 5 then    SetLength(Initiator, 5);    MinDimension := Min(PaintBox1.Width, PaintBox1.Height);  MinDimension := MinDimension - 300;   if Length(Initiator) >= 5 then // Ensure Initiator array has enough elements  begin     Initiator[0].X := (PaintBox1.Width - MinDimension) / 2;    Initiator[0].Y := (PaintBox1.Height - MinDimension) / 2;    Initiator[1].X := (PaintBox1.Width + MinDimension) / 2;    Initiator[1].Y := (PaintBox1.Height - MinDimension) / 2;    Initiator[2].X := (PaintBox1.Width + MinDimension) / 2;    Initiator[2].Y := (PaintBox1.Height + MinDimension) / 2;    Initiator[3].X := (PaintBox1.Width - MinDimension) / 2;    Initiator[3].Y := (PaintBox1.Height + MinDimension) / 2;    Initiator[4] := Initiator[0];  end;   ScaleFactor := 1 / Sqrt(5);  GeneratorDTheta := nil;  SetLength(GeneratorDTheta, 3);  GeneratorDTheta[0] := -ArcTan(1 / 2);  GeneratorDTheta[1] := Pi / 2;  GeneratorDTheta[2] := -Pi / 2;   DrawSnowflake(PaintBox1.Canvas, seDepth.Value);  FillSnowflake(PaintBox1.Canvas, Point(PaintBox1.Width div 2, PaintBox1.Height div 2), seDepth.Value, Initiator[0], 0, 0);end;                     I also tried calling it recursively: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.FillSnowflake(ACanvas: TCanvas; CenterPoint: TPoint; depth: Integer;  p1: TPointSingle; theta: Single; dist: Single);var  i: Integer;  p2: TPointSingle;  Points: array of TPoint;begin  if depth = 0 then    Exit;   SetLength(Points, 5);   for i := 1 to High(Initiator) do  begin    p1 := Initiator[i - 1];    p2 := Initiator[i];     Points[0] := CenterPoint;    Points[1].X := Round(p1.X);    Points[1].Y := Round(p1.Y);    Points[2].X := Round(p2.X);    Points[2].Y := Round(p2.Y);    Points[3] := CenterPoint;    Points[4] := Points[1];     // Fill the region with the fill color    ACanvas.Brush.Color := cbFillcolor.ButtonColor; // Use the color from cbFillcolor    ACanvas.Polygon(Points);     // Recursively fill the inner segments    p1.X := CenterPoint.X + (p1.X - CenterPoint.X) * ScaleFactor;    p1.Y := CenterPoint.Y + (p1.Y - CenterPoint.Y) * ScaleFactor;    p2.X := CenterPoint.X + (p2.X - CenterPoint.X) * ScaleFactor;    p2.Y := CenterPoint.Y + (p2.Y - CenterPoint.Y) * ScaleFactor;    FillSnowflake(ACanvas, CenterPoint, depth - 1, p1, theta, dist);  end;end; Still get 1 square filled only.