Introduction
Life uses DNA and proteins to create structures. Animals are very complicated but structures of plants and trees can be described in terms of fractals. A simple equation could describe the structure of a plant and trees. But not all plants of the same species look the same. The branches are randomized, so is the appearance of flowers. Hence, fractals with a probabilistic parameters can create dynamic structure of plants and trees.
I started looking at the plants and trees and realized that they can be described as a fractal with simple base shape. Hence, I was looking for ways to convert a simple shape into fractals and realized that there is no such application to make it ease. Hence I used WPF and C# to create a GUI to create fractals of any shape.
In WPF, Canvas can accommodate many FrameworkElements
, hence, in the GUI, I need to be able to create a Path
using PointControl
s. WPF Transforms such as ScaleTransform
, RotateTransform
and TranslateTransform
can be used on FracmeworkElements.
Code and UI
UserControl: PointControl
A draggable Point
, which make up the PointControl
, can be created by using a Thumb
Control which can be dragged in the Canvas
. PointControl
encapsulates the property Point
which are updated when the Thumb
moves, using DragDelta
event handler.
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Canvas.SetLeft(this, Canvas.GetLeft(this) + e.HorizontalChange);
Canvas.SetTop(this, Canvas.GetTop(this) + e.VerticalChange);
Point = new Point(Canvas.GetLeft(this) + 5, Canvas.GetTop(this) + 5);
OnMove(this, new EventArgs());
}
The properties of PointControl
, which are binded are the following.
public ObservableCollection<TransformProperty> Transforms { get; set; }
public Point Point { get; set; }
public double Size { get; set; }
public string SegmentType { get; set; }
PointControl
also has an Move
and Change
event handlers which can be used to update SelectedPath
. The main Path
is made up of ArcSegment
s or LineSegment
s, whose properties are binded to the PointControl
’s Point
and Type
. Hence, real time update of Path
is made possible.
BindingBase sourceBinding = new Binding { Source = this, Path = new PropertyPath(PointControl.PointProperty) };
BindingOperations.SetBinding(arc, ArcSegment.PointProperty, sourceBinding);
sourceBinding = new Binding { Source = this, Converter = new SizeConverter(), ConverterParameter = new { PointControls = MainWindow.Current.shape.PointControls, Self=this } , Path = new PropertyPath(PointControl.SizeProperty) };
BindingOperations.SetBinding(arc, ArcSegment.SizeProperty, sourceBinding);
sourceBinding = new Binding { Source = this, Converter = new SweepSizeConverter(), ConverterParameter = new { PointControls = MainWindow.Current.shape.PointControls, Self = this }, Path = new PropertyPath(PointControl.SizeProperty) };
BindingOperations.SetBinding(arc, ArcSegment.SweepDirectionProperty, sourceBinding);
With that setup, a ContextMenu
is used to display with UI for PointControl
to change the binded parameters.
Transforms
Many Transform
s can be associated with a Point
, Hence an ObservableCollection
of TransformProperty
s, which are the following is included in the PointControl
, which is binded to a ListBox
within the ContextMenu
.
public class TransformProperty
{
public double SizeRatio;
public bool Flip;
public double BaseRotation;
public Color FillBrush;
public Color LineBrush;
public double Probability;
public bool EndHere;
public int StartLevel;
public int EndLevel;
}
Each property is binded to controls in DataTemplate
of the ListBox
. Fractals are rendered iteratively, hence, StartLevel
and EndLevel
can be used to control the activation range in the Fractals. Probability can be used to enable random creation of branches. Don’t Sprout makes the rendered child path final and no more branches will sprout from that.
Code
Once the shape is created, along with TransformProperty
s. Each TransformProperty
is changed to WPF TransformGroup
using the following function. Initial Path
is taken and copied to a new path, and appropriate transformation are applied, to create the fractal branch.
public TransformGroup ConvertToTransform(Point rotationCenter, Point StartPoint)
{
TransformGroup group = new TransformGroup();
List<Transform> transforms = new List<Transform>();
if (Flip)
group.Children.Add(new ScaleTransform(-1, 1) { CenterX = StartPoint.X, CenterY = StartPoint.Y });
group.Children.Add(new TranslateTransform() { X= rotationCenter.X - StartPoint.X, Y=rotationCenter.Y - StartPoint.Y });
group.Children.Add(new ScaleTransform( SizeRatio, SizeRatio) { CenterX = rotationCenter.X, CenterY = rotationCenter.Y });
group.Children.Add(new RotateTransform(BaseRotation) { CenterX = rotationCenter.X, CenterY = rotationCenter.Y });
return group;
}
The recursion with which the fractal is created is given below
public void GeneratePath(int step, PointControl currentPoint, PointControl originalReferencePoint, double prevRotation = 0, double prevSize = 1, TransformGroup prevTransform = null)
{
foreach (TransformProperty transform in currentPoint.Transforms)
{
if ((transform.StartLevel <= step && (transform.EndLevel >= step || transform.EndLevel == 0)) && transform.Probability >= Random.NextDouble())
{
Path currentPath = new Path() { Data = Path.Data };
Point originalRefPoint = originalReferencePoint.Point;
currentPath.Fill = new SolidColorBrush(transform.FillBrush);
currentPath.Stroke = new SolidColorBrush(transform.LineBrush);
double sizeRatio = prevSize * transform.SizeRatio;
double rotation = prevRotation + transform.BaseRotation;
TransformGroup currentTransform = new TransformGroup();
if (transform.Flip)
currentTransform.Children.Add(new ScaleTransform(-1, 1) { CenterX = originalRefPoint.X, CenterY = originalRefPoint.Y });
currentTransform.Children.Add(new ScaleTransform() { ScaleX = sizeRatio, ScaleY = sizeRatio, CenterX = originalRefPoint.X, CenterY = originalRefPoint.Y });
currentTransform.Children.Add(new RotateTransform() { Angle = rotation, CenterX = originalRefPoint.X, CenterY = originalRefPoint.Y });
if (prevTransform == null)
{
currentTransform.Children.Add(new TranslateTransform() { X = currentPoint.Point.X - originalRefPoint.X, Y = currentPoint.Point.Y - originalRefPoint.Y });
}
else
{
Point currentPointTransformed = prevTransform.Transform(currentPoint.Point);
currentTransform.Children.Add(new TranslateTransform() { X = currentPointTransformed.X - originalRefPoint.X, Y = currentPointTransformed.Y - originalRefPoint.Y });
}
currentPath.RenderTransform = currentTransform;
canvas.Children.Add(currentPath);
if (step < MaxSteps && !transform.EndHere)
foreach (PointControl point in PointControls)
GeneratePath(step + 1, point, originalReferencePoint, rotation, sizeRatio, currentTransform);
}
}
}
Scale and rotate level in go according to a simple multiplication and addition
double sizeRatio = prevSize * transform.SizeRatio;
double rotation = prevRotation + transform.BaseRotation;
But translate needs the point node to be sprouted after begin transformed by previous transformation.
Hence we use recursion variable prevTransform
which is a TransformGroup
to translate a node point with previous transformation to provide the current point, to which the path needs to be translated.
Point currentPointTransformed = prevTransform.Transform(currentPoint.Point);
Weather the branch is to be sprouted or not is given by the following block within the recursive function.
if ((transform.StartLevel <= step && (transform.EndLevel >= step || transform.EndLevel == 0)) && transform.Probability >= Random.NextDouble())
File Format
Each Fractal is generated by converting the PointControl
s and TransformProperty
s to a serializable format, and saving it as XML. Extension is *.frac. Here, SaveFormat
object is populated with the Path
, Point
s and Transform
s and then serialized using XMLSerializer
. Retrival is done in the same way, creating PointControl
s out of SavePoint
s and Transform
s out of SaveTransform
s.
Saving is done in the following way
private void SaveFractalFile()
{
if (shape.PointControls.Count > 0)
{
SaveFormat format = new SaveFormat();
format.CanvasColor = CanvasColor;
format.FillColor = shapeViewModel.FillColor;
format.LineColor = shapeViewModel.LineColor;
format.PointControls = SaveFormat.SavePointFromControls(shape.PointControls);
format.ReferencePoint = shapeViewModel.ReferencePoint.Point;
format.Steps = Steps;
SaveFileDialog save = new SaveFileDialog();
save.AddExtension = true;
save.DefaultExt = ".frac";
save.Filter = "Fractal Files | *.frac";
save.FileOk += (o, e) =>
{
var stream = File.Open(save.FileName, FileMode.Create);
XmlSerializer serializer = new XmlSerializer(typeof(SaveFormat));
serializer.Serialize(stream, format);
stream.Close();
};
save.ShowDialog();
}
}
Retrival in the following way
private void ReadFractalFile(string path)
{
XmlSerializer serializer = new XmlSerializer(typeof(SaveFormat));
SaveFormat format = (SaveFormat)serializer.Deserialize(File.Open(path, FileMode.Open));
List<UIElement> removethese = new List<UIElement>();
for (int i = 0; i < canvas.Children.Count; i++)
{
if (canvas.Children[i] != selectedPath)
removethese.Add(canvas.Children[i]);
}
removethese.ForEach(r =>
{
canvas.Children.Remove(r);
});
removethese.Clear();
ControlsStack.Children.Remove(shape);
shapeViewModel = null;
shape = null;
Steps = 0;
CanvasColor = Colors.Transparent;
Steps = format.Steps;
CanvasColor = format.CanvasColor;
shapeViewModel = new ViewModels.ShapeViewModel();
shapeViewModel.SelectedPath = selectedPath;
shape = new Shape(canvas) { DataContext = shapeViewModel };
shapeViewModel.Path = shape.path;
ControlsStack.Children.Add(shape);
shapeViewModel.FillColor = format.FillColor;
shapeViewModel.LineColor = format.LineColor;
foreach (SavePoint point in format.PointControls)
{
List<TransformProperty> transforms = new List<TransformProperty>();
foreach(SaveTransform transform in point.Transforms)
{
transforms.Add(new TransformProperty(null) {
BaseRotation = transform.BaseRotation,
EndHere = transform.EndHere,
EndLevel = transform.EndLevel,
FillBrush = transform.FillBrush,
Flip = transform.Flip,
LineBrush = transform.LineBrush,
Probability = transform.Probability,
SizeRatio = transform.SizeRatio,
StartLevel = transform.StartLevel
});
}
shape.AddPoint(point.Point, point.SegementType, point.ArcRadius, transforms);
}
}
Examples
Randomized branch generation
Few examples
GIT
https://gitlab.com/hemanthk119/fractaldesigner
Tips
The number of steps to execute should be kept low to make sure the calculations don’t require much time, but high enough to maintain some detail. For 4 branches and 15 steps will require creation of 1073741824 paths and appropriate transformations. Just keep it low. Application also has some *.frac files in Examples folder, which can be opened by the application via menu to see some examples. Press run button to execute the Fractal.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.