Curling Pythagoras Tree: Ready for download lazarus / 39-Graphics ---------------------------------------------------------------------- Boleeman · 2024-01-26 11:19 Got the Extra Feature done, so go down to the thread below to download it. Hi all. I was trying to convert the CSharp Pythagoras Tree source from the CSharp helper site (at http://www.csharphelper.com/howtos/howto_pythagorean_tree.html). The code uses vectors to curl the branches. Changing the Alpha makes it curl. Uploaded pic in green shows the levels. The converted Lazarus tree code sort of works for 1st level and then goes a bit silly after that. CSharp source: 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 private void picCanvas_Paint(object sender, PaintEventArgs e){    e.Graphics.Clear(picCanvas.BackColor);    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;     try    {        int depth = (int)nudDepth.Value;        int length = (int)nudLength.Value;        float alpha =            (float)((double)nudAlpha.Value * Math.PI / 180.0);        float root_x = picCanvas.ClientSize.Width / 2;        float root_y = picCanvas.ClientSize.Height * 0.9f;        VectorF v_base = new VectorF(length, 0);        PointF ll_corner = new PointF(root_x, root_y) - v_base / 2;         Brush brush = null;      if (chkFill.Checked) brush = Brushes.Green;                DrawBranch(e.Graphics, Pens.Black, brush,            depth, ll_corner, v_base, alpha);    }    catch    {    }}  private void DrawBranch(Graphics gr, Pen pen, Brush brush,    int depth, PointF ll_corner, VectorF v_base, float alpha){    // Find the box's corners.    VectorF v_height = v_base.PerpendicularCCW();    PointF[] points =    {        ll_corner,        ll_corner + v_base,        ll_corner + v_base + v_height,        ll_corner + v_height,    };     // Draw this box.    if (brush != null) gr.FillPolygon(brush, points);    gr.DrawPolygon(pen, points);     // If depth > 0, draw the attached branches.    if (depth > 0)    {        // ***********      // Left branch        // ***********        // Calculate the new side length.        double w1 = v_base.Length * Math.Cos(alpha);         // Decompose the new base vector in terms of        // v_base and v_height.        float wb1 = (float)(w1 * Math.Cos(alpha));        float wh1 = (float)(w1 * Math.Sin(alpha));        VectorF v_base1 = v_base.Scale(wb1) + v_height.Scale(wh1);         // Find the lower left corner.        PointF ll_corner1 = ll_corner + v_height;         // Draw the left branch.        DrawBranch(gr, pen, brush, depth - 1,            ll_corner1, v_base1, alpha);         // ************        // Right branch        // ************        // Calculate the new side length.        double beta = Math.PI / 2.0 - alpha;        double w2 = v_base.Length * Math.Sin(alpha);         // Decompose the new base vector in terms of        // v_base and v_height.        float wb2 = (float)(w2 * Math.Cos(beta));        float wh2 = (float)(w2 * Math.Sin(beta));        VectorF v_base2 = v_base.Scale(wb2) - v_height.Scale(wh2);         // Find the lower left corner.        PointF ll_corner2 = ll_corner1 + v_base1;         // Draw the right branch.        DrawBranch(gr, pen, brush, depth - 1,            ll_corner2, v_base2, alpha);    }}  public class VectorF{    public float X, Y;     public VectorF(float x, float y)    {        X = x;      Y = y;    }    public VectorF(PointF point1, PointF point2)    {      X = point2.X - point1.X;        Y = point2.Y - point1.Y;    }    public VectorF(VectorF v)    {        X = v.X;        Y = v.Y;    }   public static VectorF operator +(VectorF v1, VectorF v2)    {        return new VectorF(v1.X + v2.X, v1.Y + v2.Y);    }     public static VectorF operator -(VectorF v1, VectorF v2)    {        return new VectorF(v1.X - v2.X, v1.Y - v2.Y);    }     public static PointF operator +(PointF point, VectorF vector)    {        return new PointF(point.X + vector.X, point.Y + vector.Y);    }     public static PointF operator -(PointF point, VectorF vector)    {        return new PointF(point.X - vector.X, point.Y - vector.Y);    }     public static VectorF operator -(VectorF vector)    {        return -1 * vector;    }     public static VectorF operator *(VectorF vector, float scale)  {        return new VectorF(vector.X * scale, vector.Y * scale);    }    public static VectorF operator *(float scale, VectorF vector)    {      return vector * scale;    }     public static VectorF operator /(VectorF vector, float scale)    {        return new VectorF(vector.X / scale, vector.Y / scale);    }     public float Length    {        get        {            return (float)Math.Sqrt(X * X + Y * Y);      }        set        {            float scale = value / Length;          X *= scale;            Y *= scale;        }    }     // Return a scaled version of the vector.    public VectorF Scale(float scale)    {        return this * scale / Length;    }     // Make the vector unit length.    public void Normalize()    {        Length = 1;    }     // Find the perpendicular vector in the    // counterclockwise direction.    public VectorF PerpendicularCCW()    {        return new VectorF(Y, -X);    }     // Find the perpendicular vector in the    // clockwise direction.    public VectorF PerpendicularCW()    {        return new VectorF(-Y, X);    }}  Lazarus 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 PythagorasTree; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,  ExtCtrls, Spin, Math; type   { TForm1 }   TForm1 = class(TForm)    chkFill: TCheckBox;    Label1: TLabel;    Label2: TLabel;    Label3: TLabel;  Label4: TLabel;    PaintBox1: TPaintBox;    nudDepth: TSpinEdit;    nudLength: TSpinEdit;    nudAlpha: TSpinEdit;    procedure chkFillChange(Sender: TObject);    procedure nudAlphaChange(Sender: TObject);    procedure nudDepthChange(Sender: TObject);    procedure nudLengthChange(Sender: TObject);    procedure PaintBox1Paint(Sender: TObject);  private    procedure DrawBranch(ACanvas: TCanvas; APen: TPen; ABrush: TBrush; Depth: Integer;      LL_Corner: TPoint; V_Base: TPoint; Alpha: Single);    function PointLength(const P: TPoint): Double;  public   end; var  Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.PaintBox1Paint(Sender: TObject);var  Pen: TPen;  newBrush: TBrush;begin  PaintBox1.Canvas.Clear;  PaintBox1.Canvas.AntialiasingMode:=amOn;   try    Pen := TPen.Create;    newBrush := TBrush.Create;     if chkFill.Checked then      begin      Paintbox1.Canvas.Brush.Color := clRed;        Paintbox1.Canvas.Brush.Style := bsSolid;      end          else      begin        Paintbox1.Canvas.brush.style := bsClear;      end;     DrawBranch(PaintBox1.Canvas, Pen, newBrush,      nudDepth.Value, Point(PaintBox1.Width div 2, Round(PaintBox1.Height *  0.6)),      Point(nudLength.Value, 0), DegToRad(nudAlpha.Value));  finally    Pen.Free;    newBrush.Free;  end;end; procedure TForm1.chkFillChange(Sender: TObject);begin    PaintBox1.Invalidate;end; procedure TForm1.nudAlphaChange(Sender: TObject);begin    PaintBox1.Invalidate;end; procedure TForm1.nudDepthChange(Sender: TObject);begin    PaintBox1.Invalidate;end; procedure TForm1.nudLengthChange(Sender: TObject);begin    PaintBox1.Invalidate;end; function TForm1.PointLength(const P: TPoint): Double;begin  Result := Hypot(P.X, P.Y);end;  procedure TForm1.DrawBranch(ACanvas: TCanvas; APen: TPen; ABrush: TBrush; Depth: Integer;  LL_Corner: TPoint; V_Base: TPoint; Alpha: Single);var  V_Height: TPoint;  Points: array[0..3] of TPoint;  w1, wb1, wh1: Single;  v_base1: TPoint;  ll_corner1: TPoint;  beta, w2, wb2, wh2: Single;  v_base2: TPoint;  ll_corner2: TPoint;begin  // Find the box's corners.  V_Height := Point(- V_Base.Y, V_Base.X);  Points[0] := LL_Corner;  Points[1] := LL_Corner + V_Base;  Points[2] := LL_Corner + V_Base + V_Height;  Points[3] := LL_Corner + V_Height;   // Draw this box.  if ABrush <> nil then    ACanvas.Polygon(Points);  if APen <> nil then    ACanvas.Polygon(Points);   // If depth > 0, draw the attached branches.  if Depth > 0 then  begin    // Left branch    // Calculate the new side length.    w1 := PointLength(V_Base) * Cos(Alpha);     // Decompose the new base vector in terms of    // V_Base and V_Height.  wb1 := w1 * Cos(Alpha);    wh1 := w1 * Sin(Alpha);    v_base1 := Point(Round(wb1), Round(wh1));     // Find the lower left corner.    ll_corner1 := LL_Corner + V_Height;     // Draw the left branch.    DrawBranch(ACanvas, APen, ABrush, Depth - 1, ll_corner1, v_base1, Alpha);     // Right branch    // Calculate the new side length.    beta := Pi / 2.0 - Alpha;    w2 := PointLength(V_Base) * Sin(Alpha);     // Decompose the new base vector in terms of    // V_Base and V_Height.    wb2 := w2 * Cos(beta);    wh2 := w2 * Sin(beta);    v_base2 := Point(Round(wb2), Round(- wh2));     // Find the lower left corner.    ll_corner2 := ll_corner1 + v_base1;     // Draw the right branch.    DrawBranch(ACanvas, APen, ABrush, Depth - 1,      ll_corner2, v_base2, Alpha);  end;end; end. ---------------------------------------------------------------------- Boleeman · 2024-01-26 20:23 Been comparing the 2nd level picture with the actual result of the CSharp expected result and it looks like the problem is with the rotation angle in the next stage of recursion, as the corners of the squares are lining up (see attached picture). Left branch rotation angle needs to increase and right branch rotation angle needs to decrease (almost looks like by the same amount, in the picture the angle looks like 45 degrees rotation is needed from the 2 aligning corners of the squares in the next stage). Actually I think I have found the problem. In the csharp code, private void DrawBranch(Graphics gr, Pen pen, Brush brush,     int depth, PointF ll_corner, VectorF v_base, float alpha)         PointF ll_corner2 = ll_corner1 + v_base1; In the Lazarus code I have     // Find the lower left corner.     ll_corner2 := ll_corner1 + v_base1; That matches up. But in the picCanvas_Paint which is equivalent to TForm1.PaintBox1Paint(Sender: TObject) private void picCanvas_Paint(object sender, PaintEventArgs e)        PointF ll_corner = new PointF(root_x, root_y) - v_base / 2; so perhaps the procedure TForm1.PaintBox1Paint(Sender: TObject);  needs to be changed to have the PointF(root_x, root_y) - v_base / 2; part in it. ---------------------------------------------------------------------- Boleeman · 2024-02-03 11:35 Will need to go back to basics with this one and work out where I messed up. I think somehow it may be a vector orientation or angle mistake that I must have made somewhere. Anyhow, eventually I will work it out. More to play around with. ---------------------------------------------------------------------- jamie · 2024-02-03 17:41 maybe the window needs to have its World transformation / Orientation set to lower left corner? ---------------------------------------------------------------------- Boleeman · 2024-02-03 20:18 Jamie, you may be correct as what is drawn on the screen seems to be upside down and also mirrored. Thanks for the advice. ---------------------------------------------------------------------- Boleeman · 2024-02-07 04:21 They say "Where there is a will there is a way" Worked out the curling of the branches of the Pythagoras Tree another way (not using vectors). ---------------------------------------------------------------------- Boleeman · 2024-02-10 11:04 I have finally got all the fills to work and the sizing. I will share the code soon. Just a few more little bits to play with. ---------------------------------------------------------------------- lainz · 2024-02-11 01:48 Looks like broccoli ---------------------------------------------------------------------- Boleeman · 2024-02-11 02:07 Lainz, "Virtual broccoli" I am currently working on an extra angle type feature where the corners of squares in each successive depth level do not touch. Keep on getting sidetracked by all the other graphics stuff I am playing around with, so hope to get that extra feature done soon. Thanks for the nice comments, Lainz. ---------------------------------------------------------------------- Boleeman · 2024-02-11 04:19 Here we go. The Curling Pythagoras tree, "with that extra curl feature" The last TSpinedit does the extra variation in the tree shape. The attached pictures should give a good indication of the trees that can be created. I wonder if someone on the forum could convert to BGRAbmp with save to transparent background? (still learning to do this) ---------------------------------------------------------------------- Boleeman · 2024-02-11 05:36 And some examples of the extra curl. ---------------------------------------------------------------------- AlexTP · 2024-03-28 14:04 Tree is built wrong. Look at pic attached above, "screen_extraFeaturePythagTree trans new.png". - bottom square is the base. ok. - above it you make 90grad triangle with 2 squares on sides. ok - on the left square you make another 90grad triangle - wrong. it don't look like 90grad triangle. and 2 squares on side of THIS triangle are wrong! ---------------------------------------------------------------------- AlexTP · 2024-03-28 14:07 I attach 'bad looking part': ---------------------------------------------------------------------- Boleeman · 2024-03-28 23:00 Yes I was aware that they either overlap or they have a gap. That was an extra feature to make the Tree Curl More.