lsys is a blazing fast L-System renderer written in CoffeeScript.
Below is a simple renderer in C# and WPF. It is hardcoded to render this example. The result when run looks as follows:
A mouse-click in the window will adjust the angleGrowth
variable. The re-calculation of the GeometryGroup
as well as building the Canvas
usually take much less than a tenth of a second. However, the actual screen update seems to take much longer.
Any suggestions for how to make this faster or more efficient? It's currently way slower than the CoffeeScript/JavaScript version... :-)
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Diagnostics;
namespace WpfLsysRender
{
class DrawingVisualElement : FrameworkElement
{
public DrawingVisual visual;
public DrawingVisualElement() { visual = new DrawingVisual(); }
protected override int VisualChildrenCount { get { return 1; } }
protected override Visual GetVisualChild(int index) { return visual; }
}
class State
{
public double size;
public double angle;
public double x;
public double y;
public double dir;
public State Clone() { return (State) this.MemberwiseClone(); }
}
public partial class MainWindow : Window
{
static string Rewrite(Dictionary<char, string> tbl, string str)
{
var sb = new StringBuilder();
foreach (var elt in str)
{
if (tbl.ContainsKey(elt))
sb.Append(tbl[elt]);
else
sb.Append(elt);
}
return sb.ToString();
}
public MainWindow()
{
InitializeComponent();
Width = 800;
Height = 800;
var states = new Stack<State>();
var str = "L";
{
var tbl = new Dictionary<char, string>();
tbl.Add('L', "|-S!L!Y");
tbl.Add('S', "[F[FF-YS]F)G]+");
tbl.Add('Y', "--[F-)<F-FG]-");
tbl.Add('G', "FGF[Y+>F]+Y");
for (var i = 0; i < 12; i++) str = Rewrite(tbl, str);
}
var canvas = new Canvas();
Content = canvas;
var sizeGrowth = -1.359672;
var angleGrowth = -0.138235;
State state;
var pen = new Pen(new SolidColorBrush(Colors.Black), 0.25);
var geometryGroup = new GeometryGroup();
Action buildGeometry = () =>
{
state = new State()
{
x = 0,
y = 0,
dir = 0,
size = 14.11,
angle = -3963.7485
};
geometryGroup = new GeometryGroup();
foreach (var elt in str)
{
if (elt == 'F')
{
var new_x = state.x + state.size * Math.Cos(state.dir * Math.PI / 180.0);
var new_y = state.y + state.size * Math.Sin(state.dir * Math.PI / 180.0);
geometryGroup.Children.Add(
new LineGeometry(
new Point(state.x, state.y),
new Point(new_x, new_y)));
state.x = new_x;
state.y = new_y;
}
else if (elt == '+') state.dir += state.angle;
else if (elt == '-') state.dir -= state.angle;
else if (elt == '>') state.size *= (1.0 - sizeGrowth);
else if (elt == '<') state.size *= (1.0 + sizeGrowth);
else if (elt == ')') state.angle *= (1 + angleGrowth);
else if (elt == '(') state.angle *= (1 - angleGrowth);
else if (elt == '[') states.Push(state.Clone());
else if (elt == ']') state = states.Pop();
else if (elt == '!') state.angle *= -1.0;
else if (elt == '|') state.dir += 180.0;
}
};
Action populateCanvas = () =>
{
var drawingVisualElement = new DrawingVisualElement();
Console.WriteLine(".");
canvas.Children.Clear();
canvas.RenderTransform = new TranslateTransform(400.0, 400.0);
using (var dc = drawingVisualElement.visual.RenderOpen())
dc.DrawGeometry(null, pen, geometryGroup);
canvas.Children.Add(drawingVisualElement);
};
MouseDown += (s, e) =>
{
angleGrowth += 0.001;
Console.WriteLine("angleGrowth: {0}", angleGrowth);
var sw = Stopwatch.StartNew();
buildGeometry();
populateCanvas();
sw.Stop();
Console.WriteLine(sw.Elapsed);
};
buildGeometry();
populateCanvas();
}
}
}
WPF's geometry rendering is just slow. If you want fast, render using another technology, and host the result in WPF. For example, you could render using Direct3D and host your render target inside a D3DImage. Here's an example using Direct2D instead. Or you could draw by manually setting byte values in a RGB buffer and copy that inside a WriteableBitmap.
EDIT: as the OP found out, there's also a free library to help out with drawing inside a WriteableBitmap called WriteableBitmapEx.