diff --git a/Editor/RulesetDrawer.cs b/Editor/RulesetDrawer.cs index cc9c6a8..105108f 100644 --- a/Editor/RulesetDrawer.cs +++ b/Editor/RulesetDrawer.cs @@ -1,4 +1,3 @@ -using System.Text; using ProceduralToolkit.CellularAutomaton; using UnityEditor; using UnityEngine; @@ -15,8 +14,7 @@ public class RulesetDrawer : PropertyDrawer private const float labelSpacing = 1; private const float dropdownWidth = 13; private const float dropdownSpacing = 2; - private const string birthRuleName = "birthRule"; - private const string survivalRuleName = "survivalRule"; + private const string ruleName = "rule"; private readonly GUIStyle dropdownStyle = (GUIStyle) "StaticDropdown"; private readonly GUIContent[] options = new GUIContent[] @@ -86,78 +84,57 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten { EditorGUI.BeginProperty(position, label, property); - var birthRule = property.FindPropertyRelative(birthRuleName); - var survivalRule = property.FindPropertyRelative(survivalRuleName); + var ruleProperty = property.FindPropertyRelative(ruleName); position = EditorGUI.PrefixLabel(position, label); - float ruleWidth = (position.width - dropdownWidth)/2; - Rect ruleRect = new Rect(position.x - labelSpacing, position.y, ruleWidth, position.height); - DrawRule(birthRule, ruleRect, "B"); + position = DrawRule(ruleProperty, position); - ruleRect.x += ruleWidth + labelSpacing; - DrawRule(survivalRule, ruleRect, "S"); - - int oldIndentLevel = EditorGUI.indentLevel; - EditorGUI.indentLevel = 0; - { - Rect dropdownRect = new Rect(ruleRect.xMax + dropdownSpacing, position.y, dropdownWidth, position.height); - int selected = EditorGUI.Popup(dropdownRect, -1, options, dropdownStyle); - if (selected >= 0) - { - SelectRuleset(birthRule, survivalRule, selected); - } - } - EditorGUI.indentLevel = oldIndentLevel; + DrawDropdown(ruleProperty, position); EditorGUI.EndProperty(); } - private void SelectRuleset(SerializedProperty birthRule, SerializedProperty survivalRule, int selected) + private Rect DrawRule(SerializedProperty ruleProperty, Rect position) { - var ruleset = rulesets[selected]; + string birthRuleString = Ruleset.GetBirthRuleString(ruleProperty.intValue); + string survivalRuleString = Ruleset.GetSurvivalRuleString(ruleProperty.intValue); - birthRule.ClearArray(); - for (int i = 0; i < ruleset.birthRule.Length; i++) - { - birthRule.InsertArrayElementAtIndex(i); - var element = birthRule.GetArrayElementAtIndex(i); - element.intValue = ruleset.birthRule[i]; - } - - survivalRule.ClearArray(); - for (int i = 0; i < ruleset.survivalRule.Length; i++) - { - survivalRule.InsertArrayElementAtIndex(i); - var element = survivalRule.GetArrayElementAtIndex(i); - element.intValue = ruleset.survivalRule[i]; - } - } - - private void DrawRule(SerializedProperty rule, Rect position, string label) - { - var stringBuilder = new StringBuilder(); - for (int i = 0; i < rule.arraySize; i++) - { - stringBuilder.Append(rule.GetArrayElementAtIndex(i).intValue); - } + float ruleWidth = (position.width - dropdownWidth)/2; + var ruleRect = new Rect(position.x - labelSpacing, position.y, ruleWidth, position.height); float oldLabelWidth = EditorGUIUtility.labelWidth; int oldIndentLevel = EditorGUI.indentLevel; EditorGUIUtility.labelWidth = labelWidth; EditorGUI.indentLevel = 0; - string ruleString = EditorGUI.TextField(position, label, stringBuilder.ToString()); + { + birthRuleString = EditorGUI.TextField(ruleRect, "B", birthRuleString); + + ruleRect.x += ruleWidth + labelSpacing; + + survivalRuleString = EditorGUI.TextField(ruleRect, "S", survivalRuleString); + } EditorGUIUtility.labelWidth = oldLabelWidth; EditorGUI.indentLevel = oldIndentLevel; - var ruleList = Ruleset.ConvertRuleStringToList(ruleString); - rule.ClearArray(); - for (int i = 0; i < ruleList.Count; i++) + ruleProperty.intValue = Ruleset.ConvertRuleString(birthRuleString, survivalRuleString); + + return ruleRect; + } + + private void DrawDropdown(SerializedProperty ruleProperty, Rect position) + { + int oldIndentLevel = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; { - rule.InsertArrayElementAtIndex(i); - var element = rule.GetArrayElementAtIndex(i); - element.intValue = ruleList[i]; + Rect dropdownRect = new Rect(position.xMax + dropdownSpacing, position.y, dropdownWidth, position.height); + int selected = EditorGUI.Popup(dropdownRect, -1, options, dropdownStyle); + if (selected >= 0) + { + ruleProperty.intValue = rulesets[selected].rule; + } } + EditorGUI.indentLevel = oldIndentLevel; } } } diff --git a/Runtime/CellularAutomaton/Ruleset.cs b/Runtime/CellularAutomaton/Ruleset.cs index 6ccb0cb..2894f57 100644 --- a/Runtime/CellularAutomaton/Ruleset.cs +++ b/Runtime/CellularAutomaton/Ruleset.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections; namespace ProceduralToolkit.CellularAutomaton { @@ -42,87 +42,115 @@ public struct Ruleset #endregion Common rulesets - public byte[] birthRule; - public byte[] survivalRule; + /// + /// Rule integer + /// + /// Life: B3/S23 + /// S8 S7 S6 S5 S4 S3 S2 S1 S0 B8 B7 B6 B5 B4 B3 B2 B1 B0 + /// 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 0 0 0 - 000001100 000001000 + /// + /// B0 = 2 ^ 0 = 1 + /// B1 = 2 ^ 1 = 2 + /// B2 = 2 ^ 2 = 4 + /// B3 = 2 ^ 3 = 8 + /// B4 = 2 ^ 4 = 16 + /// B5 = 2 ^ 5 = 32 + /// B6 = 2 ^ 6 = 64 + /// B7 = 2 ^ 7 = 128 + /// B8 = 2 ^ 8 = 256 + /// + /// S0 = 2 ^ 9 = 512 + /// S1 = 2 ^ 10 = 1024 + /// S2 = 2 ^ 11 = 2048 + /// S3 = 2 ^ 12 = 4096 + /// S4 = 2 ^ 13 = 8192 + /// S5 = 2 ^ 14 = 16384 + /// S6 = 2 ^ 15 = 32768 + /// S7 = 2 ^ 16 = 65536 + /// S8 = 2 ^ 17 = 131072 + /// + public int rule; - public Ruleset(byte[] birthRule, byte[] survivalRule) + private const int survivalOffset = 9; + + /// Birth rule numbers, for example: "3" + /// Survival rule numbers, for example: "23" + public Ruleset(string birthRuleString = null, string survivalRuleString = null) { - this.birthRule = new byte[birthRule.Length]; - for (int i = 0; i < birthRule.Length; i++) - { - this.birthRule[i] = birthRule[i]; - } - this.survivalRule = new byte[survivalRule.Length]; - for (int i = 0; i < survivalRule.Length; i++) - { - this.survivalRule[i] = survivalRule[i]; - } + rule = ConvertRuleString(birthRuleString, survivalRuleString); } - public Ruleset(List birthRule, List survivalRule) + public bool CanSpawn(int aliveCells) { - this.birthRule = birthRule.ToArray(); - this.survivalRule = survivalRule.ToArray(); + int pow = 1 << aliveCells; + return (rule & pow) == pow; } - public Ruleset(string birthRule = null, string survivalRule = null) + public bool CanSurvive(int aliveCells) { - this.birthRule = ConvertRuleStringToList(birthRule).ToArray(); - this.survivalRule = ConvertRuleStringToList(survivalRule).ToArray(); + int pow = 1 << (aliveCells + survivalOffset); + return (rule & pow) == pow; } - public bool CanSpawn(int aliveCells) + public static int ConvertRuleString(string birthRuleString, string survivalRuleString) { - foreach (byte number in birthRule) + int rule = 0; + if (!string.IsNullOrEmpty(birthRuleString)) { - if (number == aliveCells) return true; + foreach (char c in birthRuleString) + { + if (!char.IsDigit(c)) continue; + + byte digit = (byte) char.GetNumericValue(c); + int pow = 1 << digit; + rule |= pow; + } } - return false; + if (!string.IsNullOrEmpty(survivalRuleString)) + { + foreach (char c in survivalRuleString) + { + if (!char.IsDigit(c)) continue; + + byte digit = (byte) char.GetNumericValue(c); + int pow = 1 << (digit + survivalOffset); + rule |= pow; + } + } + return rule; } - public bool CanSurvive(int aliveCells) + public override string ToString() { - foreach (byte number in survivalRule) - { - if (number == aliveCells) return true; - } - return false; + return $"B{GetBirthRuleString(rule)}/S{GetSurvivalRuleString(rule)}"; } - public static List ConvertRuleStringToList(string rule) + public static string GetBirthRuleString(int rule) { - var list = new List(); - if (!string.IsNullOrEmpty(rule)) + string b = ""; + var bitArray = new BitArray(new[] {rule}); + for (int i = 0; i < survivalOffset; i++) { - foreach (char c in rule) + if (bitArray[i]) { - if (char.IsDigit(c)) - { - byte digit = (byte) char.GetNumericValue(c); - if (!list.Contains(digit)) - { - list.Add(digit); - } - } + b += i; } - list.Sort(); } - return list; + return b; } - public override string ToString() + public static string GetSurvivalRuleString(int rule) { - string b = ""; - foreach (var digit in birthRule) - { - b += digit; - } string s = ""; - foreach (var digit in survivalRule) + var bitArray = new BitArray(new[] {rule}); + for (int i = survivalOffset; i < bitArray.Length; i++) { - s += digit; + if (bitArray[i]) + { + s += i - survivalOffset; + } } - return string.Format("B{0}/S{1}", b, s); + return s; } } } diff --git a/Samples/CellularAutomata/CellularAutomata.unity b/Samples/CellularAutomata/CellularAutomata.unity index cfb40e6..1228836 100644 --- a/Samples/CellularAutomata/CellularAutomata.unity +++ b/Samples/CellularAutomata/CellularAutomata.unity @@ -223,8 +223,7 @@ MonoBehaviour: width: 128 height: 128 ruleset: - birthRule: 03 - survivalRule: 0203 + rule: 6152 startNoise: 0.25 aliveBorders: 0 --- !u!4 &643499691