From 76af037a01ffb3a09bfe47a2b1f77a4e81990995 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Fri, 12 Dec 2025 16:00:23 +0100 Subject: [PATCH] Added curves --- Assets/NanoBrain/Neuroid.cs | 14 ---- Assets/NanoBrain/Nucleus.cs | 80 ++++++++++++++++++- .../VisualEditor/Editor/NanoBrainInspector.cs | 51 ++++-------- Assets/Scenes/Boids/SwarmingBrain.asset | 17 ++++ 4 files changed, 111 insertions(+), 51 deletions(-) diff --git a/Assets/NanoBrain/Neuroid.cs b/Assets/NanoBrain/Neuroid.cs index 92cf2f2..ac417e7 100644 --- a/Assets/NanoBrain/Neuroid.cs +++ b/Assets/NanoBrain/Neuroid.cs @@ -6,13 +6,6 @@ public class Neuroid : Nucleus { public bool inverse = false; public float exponent = 1.0f; - // public Neuroid(NanoBrain brain, string name) : base(null, name) { - // this.brain = brain; - // if (this.brain != null) - // this.brain.neuroids.Add(this); - // else - // Debug.LogError("No neuroid network"); - // } public Neuroid(NanoBrainObj brain, string name) : base(name) { this.brain = brain; @@ -26,16 +19,9 @@ public class Neuroid : Nucleus { public Neuroid(string name): base(name) {} public void SetWeight(Neuroid input, float weight) { - //this.synapses[input] = weight; this.SetWeight((Nucleus)input, weight); } - // public void GetInputFrom(Neuroid input, float weight = 1.0f) { - // input.AddReceiver(this); - // //this.synapses[input] = weight; - // this.SetWeight((Nucleus)input, weight); - // } - public void SetInput(Neuroid input) { // if (this.synapses.ContainsKey(input) == false) // this.synapses[input] = 1.0f; diff --git a/Assets/NanoBrain/Nucleus.cs b/Assets/NanoBrain/Nucleus.cs index ae4b0c6..340d3c0 100644 --- a/Assets/NanoBrain/Nucleus.cs +++ b/Assets/NanoBrain/Nucleus.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using UnityEngine; +using UnityEditor; [System.Serializable] public class Nucleus { @@ -15,7 +16,7 @@ public class Nucleus { } [SerializeField] - public List synapses = new(); + public List synapses = new(); [SerializeField] public List receivers = new(); @@ -54,7 +55,7 @@ public class Nucleus { return nucleus; } - public virtual void Deserialize(Nucleus nucleus) { } + public virtual void Deserialize(Nucleus nucleus) { } #endregion Serialization @@ -152,6 +153,17 @@ public class Synapse { public int nucleusId; public float weight; + public enum CurvePresets { + Linear, + Power, + Sqrt, + Reciprocal, + Custom + } + public CurvePresets curvePreset; + public AnimationCurve curve; + public float curveMax = 1.0f; + public Synapse(Nucleus nucleus, float weight) { this.nucleus = nucleus; this.nucleusId = nucleus.id; @@ -177,6 +189,70 @@ public class Synapse { } Debug.LogError($"Synapse deserialization error: could not find nucleus with id {this.nucleusId}"); } + + public AnimationCurve GenerateCurve() { + switch (this.curvePreset) { + case CurvePresets.Linear: + this.curveMax = this.weight; + return Presets.Linear(this.weight); + case CurvePresets.Power: + this.curveMax = this.weight; + return Presets.Power(2.0f, this.weight); + case CurvePresets.Sqrt: + this.curveMax = this.weight; + return Presets.Power(0.5f, this.weight); + case CurvePresets.Reciprocal: + this.curveMax = 1 / 0.01f * this.weight; + return Presets.Reciprocal(this.weight); + default: + this.curveMax = weight; + return AnimationCurve.Constant(0, 1, weight); + } + } + + public static class Presets { + private const int samples = 32; + public static AnimationCurve Linear(float weight) { + return AnimationCurve.Linear(0f, 0f, 1f, weight); + } + public static AnimationCurve Power(float exponent, float weight) { + // build keyframes + Keyframe[] keys = new Keyframe[samples]; + for (int i = 0; i < samples; i++) { + float t = i / (float)(samples - 1); + float v = Mathf.Pow(t, exponent) * weight; + keys[i] = new Keyframe(t, v); + } + + AnimationCurve curve = new(keys); + + // set tangent modes for each key to Auto (smooth). Use Linear if you prefer straight segments. + for (int i = 0; i < curve.length; i++) { + AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Auto); + AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Auto); + } + + return curve; + } + public static AnimationCurve Reciprocal(float weight) { + int samples = 128; + float xMin = 0.001f; + float xMax = 1; + var keys = new Keyframe[samples]; + for (int i = 0; i < samples; i++) { + float t = i / (float)(samples - 1); + float x = Mathf.Lerp(xMin, xMax, t); + float y = 1f / x * weight; + keys[i] = new Keyframe(x, y); + } + var curve = new AnimationCurve(keys); + for (int i = 0; i < curve.length; i++) { + AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Linear); + AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Linear); + } + return curve; + } + } } [System.Serializable] diff --git a/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs index 7897927..f9ff10a 100644 --- a/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs +++ b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs @@ -403,33 +403,31 @@ public class NanoBrainInspector : Editor { EditorGUILayout.Vector3Field(GUIContent.none, this.currentNucleus.outputValue); EditorGUILayout.EndHorizontal(); if (this.currentNucleus.synapses.Count > 0) { - EditorGUILayout.LabelField("Synapses"); - EditorGUI.indentLevel++; foreach (Synapse synapse in this.currentNucleus.synapses) { if (synapse.nucleus != null) { EditorGUI.BeginDisabledGroup(synapse.nucleus.isSleeping); - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField(synapse.nucleus.name, GUILayout.Width(120)); - EditorGUI.indentLevel--; - // if (synapse.nucleus is Perceptoid perceptoid) { - // EditorGUILayout.LabelField("Thing", GUILayout.Width(45)); - // perceptoid.thingType = EditorGUILayout.IntField(perceptoid.thingType, GUILayout.Width(40)); - - // } - // else { - EditorGUILayout.LabelField("Weight", GUILayout.Width(45)); - synapse.weight = EditorGUILayout.FloatField(synapse.weight, GUILayout.Width(40)); - // } - EditorGUI.indentLevel++; - EditorGUILayout.Vector3Field(GUIContent.none, synapse.nucleus.outputValue, GUILayout.Width(180)); + EditorGUILayout.LabelField(synapse.nucleus.name, GUILayout.Width(150)); + EditorGUILayout.Vector3Field(GUIContent.none, synapse.nucleus.outputValue); //, GUILayout.Width(180)); EditorGUILayout.EndHorizontal(); + EditorGUI.indentLevel++; + EditorGUI.BeginChangeCheck(); + synapse.weight = EditorGUILayout.FloatField("Weight", synapse.weight); + synapse.curvePreset = (Synapse.CurvePresets)EditorGUILayout.EnumPopup("Preset", synapse.curvePreset); + if (EditorGUI.EndChangeCheck()) { + synapse.curve = synapse.GenerateCurve(); + } + if (synapse.curveMax > 0) + EditorGUILayout.CurveField("Curve", synapse.curve, Color.cyan, new Rect(0, 0, 1, synapse.curveMax)); + else + EditorGUILayout.CurveField("Curve", synapse.curve, Color.cyan, new Rect(0, synapse.curveMax, 1, -synapse.curveMax)); + EditorGUI.indentLevel--; EditorGUI.EndDisabledGroup(); } } - EditorGUI.indentLevel--; + //EditorGUI.indentLevel--; } if (GUILayout.Button("Add Neuron")) AddInputNeuron(this.currentNucleus); @@ -437,24 +435,9 @@ public class NanoBrainInspector : Editor { AddPerceptoid(this.currentNucleus); if (GUILayout.Button("Delete this neuron")) DeleteNeuron(this.currentNucleus); - // if (GUILayout.Button("Connect to...")) - // ConnectNucleus(this.currentNucleus); ConnectNucleus(this.currentNucleus); DisconnectNucleus(this.currentNucleus); - // GUIStyle toggleButton = new("Button"); - // if (connecting) { - // toggleButton.normal = toggleButton.active; - // } - // if (GUILayout.Button(connecting ? "Connecting..." : "Connect to...", toggleButton)) { - // connecting = !connecting; - // if (connecting) { - // names = this.currentNucleus.brain.perceptei.Select(i => i.name).ToArray(); - // } - // } - // if (connecting) - // ConnectNucleus(this.currentNucleus); - }); inspectorContainer.Add(container); @@ -503,10 +486,8 @@ public class NanoBrainInspector : Editor { int selectedIndex = -1; selectedIndex = EditorGUILayout.Popup("Disconnect from", selectedIndex, names); if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.brain.perceptei.Count) { - Synapse synapse =this.currentNucleus.synapses[selectedIndex]; - //n.AddReceiver(this.currentNucleus); + Synapse synapse = this.currentNucleus.synapses[selectedIndex]; synapse.nucleus.RemoveReceiver(this.currentNucleus); - //BuildLayers(); } } diff --git a/Assets/Scenes/Boids/SwarmingBrain.asset b/Assets/Scenes/Boids/SwarmingBrain.asset index ab81e1e..0784a70 100644 --- a/Assets/Scenes/Boids/SwarmingBrain.asset +++ b/Assets/Scenes/Boids/SwarmingBrain.asset @@ -24,6 +24,8 @@ MonoBehaviour: weight: 1 - nucleusId: 1938577052 weight: 10 + - nucleusId: 1641120128 + weight: -5 receivers: [] nucleusType: average: 0 @@ -53,6 +55,19 @@ MonoBehaviour: average: 0 inverse: 0 exponent: 1 + - id: 1641120128 + _name: Separation + synapses: + - nucleusId: -1420275136 + weight: 1 + - nucleusId: -1266532688 + weight: 1 + receivers: + - nucleusId: -1707533328 + nucleusType: + average: 0 + inverse: 0 + exponent: 1 perceptei: - id: 407735232 _name: Boundary @@ -70,6 +85,7 @@ MonoBehaviour: synapses: [] receivers: - nucleusId: 1938577052 + - nucleusId: 1641120128 nucleusType: Perceptoid average: 0 inverse: 0 @@ -81,6 +97,7 @@ MonoBehaviour: synapses: [] receivers: - nucleusId: 1938577052 + - nucleusId: 1641120128 nucleusType: Perceptoid average: 0 inverse: 0