An L-System engine and turtle-based renderer.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123 lines
3.3 KiB

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace SBRL.Algorithms.LSystem
{
public class Rule
{
public string Find;
public string Replace;
public Rule(string inFind, string inReplace)
{
Find = inFind;
Replace = inReplace;
}
}
/// <summary>
/// Simulates an L-System.
/// Implemented according to http://www.cs.unm.edu/~joel/PaperFoldingFractal/L-system-rules.html
/// </summary>
public class LSystemEngine
{
public readonly string Root;
private List<Rule> rules = new List<Rule>();
public int GenerationCount { get; private set; }
public string CurrentGeneration { get; private set; }
public Dictionary<string, string> Definitions { get; private set; }
public LSystemEngine(string inRoot)
{
CurrentGeneration = Root = inRoot;
Definitions = new Dictionary<string, string>();
}
public void AddRule(string find, string replace)
{
rules.Add(new Rule(find, replace));
}
public string Simulate()
{
List<KeyValuePair<int, Rule>> rulePositions = new List<KeyValuePair<int, Rule>>();
// Find all the current positions
foreach(Rule rule in rules)
{
IEnumerable<int> positions = AllIndexesOf(CurrentGeneration, rule.Find);
foreach (int pos in positions)
rulePositions.Add(new KeyValuePair<int, Rule>(pos, rule));
}
rulePositions.Sort(compareRulePairs);
string nextGeneration = CurrentGeneration;
int replaceOffset = 0;
foreach(KeyValuePair<int, Rule> rulePos in rulePositions)
{
int nextPos = rulePos.Key + replaceOffset;
nextGeneration = nextGeneration.Substring(0, nextPos) + rulePos.Value.Replace + nextGeneration.Substring(nextPos + rulePos.Value.Find.Length);
replaceOffset += rulePos.Value.Replace.Length - rulePos.Value.Find.Length;
}
CurrentGeneration = nextGeneration;
GenerationCount++;
return CurrentGeneration;
}
private int compareRulePairs(KeyValuePair<int, Rule> a, KeyValuePair<int, Rule> b)
{
return a.Key - b.Key;
}
/// <summary>
/// From http://stackoverflow.com/a/2641383/1460422
/// </summary>
/// <param name="str"></param>
/// <param name="value"></param>
/// <returns></returns>
private IEnumerable<int> AllIndexesOf(string str, string value) {
if (String.IsNullOrEmpty(value))
throw new ArgumentException("the string to find may not be empty", nameof(value));
for (int index = 0; ; index += value.Length) {
index = str.IndexOf(value, index);
if (index == -1)
break;
yield return index;
}
}
public static LSystemEngine FromFile(string filename)
{
return FromStream(new StreamReader(filename));
}
public static LSystemEngine FromStream(StreamReader source)
{
LSystemEngine resultSystem = new LSystemEngine(source.ReadLine());
string nextLine = string.Empty;
while (true) {
nextLine = source.ReadLine();
if (nextLine == null)
break;
if (!nextLine.Contains("=") || nextLine.StartsWith("#") || nextLine.Trim().Length == 0)
continue;
string[] parts = nextLine.Split(new char[]{'='}, 2);
if(parts[0].StartsWith("!"))
{
// This is a definition
resultSystem.Definitions.Add(parts[0].Trim('!'), parts[1]);
}
else
{
resultSystem.AddRule(parts[0].Trim(), parts[1].Trim());
}
}
return resultSystem;
}
}
}