diff --git a/Assets/NanoBrain/Editor/NanoBrain_Editor.cs b/Assets/NanoBrain/Editor/NanoBrain_Editor.cs index 1f0c46f..58e4ff5 100644 --- a/Assets/NanoBrain/Editor/NanoBrain_Editor.cs +++ b/Assets/NanoBrain/Editor/NanoBrain_Editor.cs @@ -8,6 +8,8 @@ public class NanoBrain_Editor : Editor { private List layers = new(); private Dictionary neuroidPositions = new(); + protected bool breakOnWake = false; + #region Start private void OnEnable() { @@ -19,7 +21,7 @@ public class NanoBrain_Editor : Editor { if (!selectedObject.TryGetComponent(out Boid boid)) return; - Neuroid neuroid = boid.totalForce; + Nucleus neuroid = boid.totalForce; this.currentNucleus = neuroid; BuildLayers(); @@ -34,12 +36,32 @@ public class NanoBrain_Editor : Editor { if (this.currentNucleus == null) return; + breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake); + if (breakOnWake && currentNucleus is Neuroid currentNeuroid) { + if (!currentNeuroid.isSleeping) + Debug.Break(); + } + DrawGraph(); //DrawDefaultInspector(); EditorGUILayout.TextField("Name", currentNucleus.name); - EditorGUILayout.FloatField("Output Value", currentNucleus.outputValue.magnitude); - EditorGUILayout.IntField("# synapses", currentNucleus.synapses.Count); + EditorGUILayout.Vector3Field("Output Value", currentNucleus.outputValue); + if (currentNucleus.synapses.Count > 0) { + EditorGUI.indentLevel++; + foreach ((Nucleus nucleus, float weight) in currentNucleus.synapses) { + EditorGUI.BeginDisabledGroup(nucleus.isSleeping); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.Vector3Field(nucleus.name, nucleus.outputValue); + EditorGUILayout.FloatField(weight, GUILayout.Width(50)); + EditorGUILayout.EndHorizontal(); + + EditorGUI.EndDisabledGroup(); + + } + EditorGUI.indentLevel--; + } } private void BuildLayers() { @@ -140,8 +162,8 @@ public class NanoBrain_Editor : Editor { float size = 20; - if (layerNeuroid.IsStale()) - Handles.color = Color.darkCyan; + if (layerNeuroid.isSleeping) + Handles.color = Color.darkRed; else { float brightness = layerNeuroid.outputValue.magnitude / maxValue; Handles.color = new Color(brightness, brightness, brightness); @@ -224,7 +246,8 @@ public class NanoBrain_Editor : Editor { // Handles.DrawLine(brain.transform.position, brain.transform.position + Vector3.up); Handles.color = Color.yellow; Vector3 worldForce = brain.transform.TransformDirection(this.currentNucleus.outputValue); - Debug.DrawRay(position, worldForce * 10, Color.yellow); + //Debug.DrawRay(position, worldForce * 10, Color.yellow); + Handles.DrawLine(position, position + worldForce * 10); } #endregion Update diff --git a/Assets/NanoBrain/Editor/NeuroidWindow.cs b/Assets/NanoBrain/Editor/NeuroidWindow.cs index 69d8a34..248e0e4 100644 --- a/Assets/NanoBrain/Editor/NeuroidWindow.cs +++ b/Assets/NanoBrain/Editor/NeuroidWindow.cs @@ -199,7 +199,7 @@ public class GraphEditorWindow : EditorWindow { } float size = 20; - if (layerNeuroid.IsStale()) + if (layerNeuroid.isSleeping) Handles.color = Color.black; else { float brightness = layerNeuroid.outputValue.magnitude / maxValue; @@ -272,7 +272,7 @@ public class GraphEditorWindow : EditorWindow { if (boid == null) return; - Neuroid neuroid = boid.totalForce; + Nucleus neuroid = boid.behaviour; this.currentNucleus = neuroid; if (neuroid == null) this.allNeuroids = new(); diff --git a/Assets/NanoBrain/NanoBrain.cs b/Assets/NanoBrain/NanoBrain.cs index df9ef3b..437f0b2 100644 --- a/Assets/NanoBrain/NanoBrain.cs +++ b/Assets/NanoBrain/NanoBrain.cs @@ -12,7 +12,7 @@ public class NanoBrain : MonoBehaviour { public void UpdateNeurons() { foreach (Neuroid neuroid in neuroids) { neuroid.stale++; - if (neuroid.IsStale()) + if (neuroid.isSleeping) neuroid.outputValue = Vector3.zero; } } diff --git a/Assets/NanoBrain/Neuroid.cs b/Assets/NanoBrain/Neuroid.cs index 852f8b5..7e03903 100644 --- a/Assets/NanoBrain/Neuroid.cs +++ b/Assets/NanoBrain/Neuroid.cs @@ -1,8 +1,6 @@ using UnityEngine; public class Neuroid : Nucleus { - public int stale = 0; - public bool average = false; public bool inverse = false; public float exponent = 1.0f; @@ -15,11 +13,6 @@ public class Neuroid : Nucleus { Debug.LogError("No neuroid network"); } - // public void AddSynapse(Neuroid input) { - // input.AddReceiver(this); - // this.synapses[input] = 1.0f; - // } - public void SetWeight(Neuroid input, float weight) { this.synapses[input] = weight; } @@ -43,7 +36,7 @@ public class Neuroid : Nucleus { public virtual void UpdateState() { Vector3 result = Vector3.zero; foreach ((Nucleus nucleus, float weight) in this.synapses) { - if (nucleus is Neuroid neuroid && neuroid.IsStale()) + if (nucleus is Neuroid neuroid && neuroid.isSleeping) continue; Vector3 direction = nucleus.outputValue.normalized; @@ -65,8 +58,8 @@ public class Neuroid : Nucleus { receiver.SetInput(this); } - public bool IsStale() { - return this.stale > 2; - } + // public bool IsStale() { + // return this.stale > 2; + // } } diff --git a/Assets/NanoBrain/Nucleus.cs b/Assets/NanoBrain/Nucleus.cs index c34b8ed..ce38a1d 100644 --- a/Assets/NanoBrain/Nucleus.cs +++ b/Assets/NanoBrain/Nucleus.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using UnityEngine; public class Nucleus { + public int stale = 0; + public NanoBrain brain { get; protected set; } - public string name; + public virtual string name { get; set; } public readonly Dictionary synapses = new(); public HashSet receivers = new(); @@ -22,4 +24,11 @@ public class Nucleus { receiver.synapses[this] = 1.0f; // new(this); //Debug.Log($"receiver # {this.receivers.Count} synapse count {receiver.synapses.Count}"); } + + public bool isSleeping { + get { + return this.stale > 2; + } + } + } \ No newline at end of file diff --git a/Assets/NanoBrain/Perception.cs b/Assets/NanoBrain/Perception.cs index fd5dfeb..3353d62 100644 --- a/Assets/NanoBrain/Perception.cs +++ b/Assets/NanoBrain/Perception.cs @@ -19,7 +19,7 @@ public class Perception : Nucleus { this.velocityReceivers = new(); } - public void SendPositions(Neuroid receivingNeuroid, float weight = 1.0f, int thingType = 0) { + public void SendPositions(Neuroid receivingNeuroid, int thingType = 0, float weight = 1.0f) { Receiver receiver = new() { thingType = thingType, neuroid = receivingNeuroid @@ -32,7 +32,7 @@ public class Perception : Nucleus { } } } - public void SendVelocities(Neuroid receivingNeuroid, float weight = 1.0f, int thingType = 0) { + public void SendVelocities(Neuroid receivingNeuroid, int thingType = 0, float weight = 1.0f) { Receiver receiver = new() { thingType = thingType, neuroid = receivingNeuroid @@ -57,7 +57,7 @@ public class Perception : Nucleus { return; } if (availableIx == -1) { - if (sensoryNeuroids[i].IsStale()) + if (sensoryNeuroids[i].isSleeping) leastInterestingIx = i; else if (sensoryNeuroids[i] != null) { if (leastInterestingIx == -1 || sensoryNeuroids[leastInterestingIx].receptor.position.magnitude > sensoryNeuroids[i].receptor.position.magnitude) @@ -77,7 +77,7 @@ public class Perception : Nucleus { } else { // Debug.Log($"new receptor for {thingId} at {availableIx}"); - neuroid = new(brain, thingId) { name = name }; + neuroid = new(brain, thingId, name); sensoryNeuroids[availableIx] = neuroid; } foreach (Receiver receiver in positionReceivers) { diff --git a/Assets/NanoBrain/SensoryNeuroid.cs b/Assets/NanoBrain/SensoryNeuroid.cs index 72985e5..02b6865 100644 --- a/Assets/NanoBrain/SensoryNeuroid.cs +++ b/Assets/NanoBrain/SensoryNeuroid.cs @@ -30,33 +30,38 @@ public class SensoryNeuroid : Neuroid { public VelocityNeuroid velocityNeuroid; // public SensoryNeuroid(NeuroidNetwork net, int thingId) : base(net, "sensory neuroid") { - public SensoryNeuroid(NanoBrain net, int thingId) : base(net, "sensory neuroid") { + public SensoryNeuroid(NanoBrain net, int thingId, string name = "sensor") : base(net, name) { + this.name = name + ": position"; this.receptor = new Receptor { neuroid = this, thingId = thingId }; - this.velocityNeuroid = new(net); + this.velocityNeuroid = new(net, name + ": velocity"); // The velocity neuroid received position data from this this.AddReceiver(velocityNeuroid); } - public void Replace(int thingId, string name = "sensory neuroid") { - this.name = name; + public void Replace(int thingId, string name = "sensor") { + this.name = name + ": position"; + this.receptor.thingId = thingId; this.receptor.localPosition = Vector3.zero; + this.outputValue = Vector3.zero; this.receivers = new(); this.AddReceiver(velocityNeuroid); - this.velocityNeuroid.receivers = new(); + + // this.velocityNeuroid.name = name + ": velocity"; + // this.velocityNeuroid.receivers = new(); + this.velocityNeuroid.Replace(name + ": velocity"); } public override void UpdateState() { Vector3 result = receptor.localPosition; - //foreach ((Nucleus nucleus, Synapse synapse) in this.synapses) { foreach ((Nucleus nucleus, float weight) in this.synapses) { Vector3 direction = nucleus.outputValue.normalized; float magnitude = nucleus.outputValue.magnitude; - //magnitude = synapse.weight * Mathf.Pow(magnitude, exponent); + magnitude = weight * Mathf.Pow(magnitude, exponent); if (inverse) magnitude = 1 / magnitude; @@ -77,8 +82,14 @@ public class VelocityNeuroid : Neuroid { private Vector3 lastPosition = Vector3.zero; private float lastValueTime = 0; - // public VelocityNeuroid(NeuroidNetwork net) : base(net, "Velocity") { - public VelocityNeuroid(NanoBrain net) : base(net, "Velocity") { + public VelocityNeuroid(NanoBrain net, string name = "velocity") : base(net, name) { + } + + public void Replace(string name = "velocity") { + this.name = name; + this.receivers = new(); + this.lastPosition = Vector3.zero; + this.lastValueTime = 0; } public override void UpdateState() { @@ -86,17 +97,20 @@ public class VelocityNeuroid : Neuroid { Vector3 currentPosition = this.synapses.First().Key.outputValue; float currentValueTime = Time.time; - float deltaTime = currentValueTime - lastValueTime; - Vector3 translation = currentPosition - lastPosition; - Vector3 velocity = translation / deltaTime; + if (lastValueTime != 0) { + float deltaTime = currentValueTime - lastValueTime; + Vector3 translation = currentPosition - lastPosition; + Vector3 velocity = translation / deltaTime; - // No activation function... - this.outputValue = velocity; - foreach (Neuroid receiver in receivers) - receiver?.SetInput(this); - this.stale = 0; + // No activation function... + this.outputValue = velocity; + this.stale = 0; - this.lastValueTime = Time.time; + foreach (Neuroid receiver in receivers) + receiver?.SetInput(this); + } + + this.lastValueTime = currentValueTime; this.lastPosition = currentPosition; } } \ No newline at end of file diff --git a/Assets/Scenes/Boids/Boids.unity b/Assets/Scenes/Boids/Boids.unity index 1c9b8ef..2c5cb28 100644 --- a/Assets/Scenes/Boids/Boids.unity +++ b/Assets/Scenes/Boids/Boids.unity @@ -372,10 +372,10 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Assembly-CSharp::SwarmControl speed: 2 - inertia: 0.1 - alignmentForce: 3 - cohesionForce: 3 - avoidanceForce: 5 + inertia: 0.5 + alignmentForce: 2 + cohesionForce: 2 + avoidanceForce: 1 separationDistance: 0.3 perceptionDistance: 1 boundaryForce: 5 @@ -393,7 +393,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ec888ca5333d45a438f9f417fa5ce135, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::SwarmSpawn - count: 5 + count: 20 boidPrefab: {fileID: 8702527964058765413, guid: f9c706268554ce449a8773675b2864b8, type: 3} spawnAreaSize: {x: 0.5, y: 0.5, z: 0.5} minDelay: 0.05 diff --git a/Assets/Scenes/Boids/Scripts/Boid.cs b/Assets/Scenes/Boids/Scripts/Boid.cs index 0ac0c98..0fb20f7 100644 --- a/Assets/Scenes/Boids/Scripts/Boid.cs +++ b/Assets/Scenes/Boids/Scripts/Boid.cs @@ -2,17 +2,15 @@ using UnityEngine; [RequireComponent(typeof(NanoBrain))] public class Boid : MonoBehaviour { - public const int BoundaryType = 1; - public const int BoidType = 2; + public static int BoundaryType = 1; + public static int BoidType = 2; public SwarmControl sc; public Vector3 velocity = Vector3.zero; public Vector3 acceleration = Vector3.zero; private Bounds innerBounds; - private Bounds outerBounds; - //public NeuroidNetwork neuroidNet = new(); public NanoBrain neuroidNet; public Perception perception; @@ -30,7 +28,6 @@ public class Boid : MonoBehaviour { sc = FindFirstObjectByType(); innerBounds = new(sc.transform.position, sc.spaceSize - 2 * sc.boundaryWidth); - outerBounds = new(sc.transform.position, sc.spaceSize); perception = new Perception(neuroidNet); @@ -49,7 +46,8 @@ public class Boid : MonoBehaviour { if (neighbour == null || neighbour == this) continue; - Vector3 localPosition = neighbour.transform.position - this.transform.position; + Vector3 localPosition = this.transform.InverseTransformPoint(neighbour.transform.position); + //Debug.DrawRay(this.transform.position, this.transform.TransformDirection(localPosition), Color.magenta); int thingId = neighbour.GetInstanceID(); perception.ProcessStimulus(thingId, BoidType, localPosition, neighbour.gameObject.name); @@ -64,13 +62,14 @@ public class Boid : MonoBehaviour { perception.ProcessStimulus(777, BoundaryType, desiredLocalSpace, "Boundary"); } - Vector3 worldForce = this.transform.TransformDirection(totalForce.outputValue); + Vector3 worldForce = this.transform.TransformDirection(behaviour.outputValue); this.velocity = (1 - sc.inertia) * (worldForce * Time.deltaTime) + sc.inertia * velocity; if (this.velocity.magnitude > 0) this.velocity = this.velocity.normalized * sc.speed; else this.velocity = this.transform.forward * sc.speed; + Debug.DrawRay(this.transform.position, this.velocity, Color.blue); this.transform.position += this.velocity * Time.deltaTime; @@ -82,14 +81,4 @@ public class Boid : MonoBehaviour { neuroidNet.UpdateNeurons(); } - Vector3 ClosestPointOnBoundsSurface(Bounds b, Vector3 p) { - if (!b.Contains(p)) return b.ClosestPoint(p); - Vector3 d = p - b.center; - Vector3 ext = b.extents; - float sx = ext.x / Mathf.Abs(d.x); - float sy = ext.y / Mathf.Abs(d.y); - float sz = ext.z / Mathf.Abs(d.z); - float m = Mathf.Min(sx, Mathf.Min(sy, sz)); - return b.center + d * m; - } } diff --git a/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs b/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs index 2499b5d..7fbb301 100644 --- a/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs +++ b/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs @@ -5,7 +5,7 @@ public class Roaming : Nucleus { public Roaming(NanoBrain neuroidNet, Perception perception, SwarmControl sc) : base("Roaming nucleus") { avoidance = new(neuroidNet, "Avoidance") { inverse = true }; - perception.SendPositions(avoidance, 1.0f, 1); + perception.SendPositions(avoidance, Boid.BoundaryType); this.output = new(neuroidNet, "Roaming"); output.GetInputFrom(avoidance, -sc.avoidanceForce); diff --git a/Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs b/Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs index f8c7bef..580761f 100644 --- a/Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs +++ b/Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs @@ -4,28 +4,30 @@ public class Swarming : Nucleus { public Neuroid cohesion; public Neuroid alignment; public Neuroid avoidance; + public Neuroid boundary; public Neuroid output; public override Vector3 outputValue { get => output.outputValue; set => output.outputValue = value; } - public const int BoundaryType = 1; - public const int BoidType = 2; - public Swarming(NanoBrain neuroidNet, Perception perception, SwarmControl sc) : base("Swarming Nucleus") { - this.cohesion = new(neuroidNet, "Cohesion"); - perception.SendPositions(this.cohesion, 1.0f, BoidType); + this.cohesion = new(neuroidNet, "Cohesion") { inverse = false }; + perception.SendPositions(this.cohesion, Boid.BoidType); this.alignment = new(neuroidNet, "Alignment") { average = true }; - perception.SendVelocities(this.alignment, 1.0f, BoidType); + perception.SendVelocities(this.alignment, Boid.BoidType); this.avoidance = new(neuroidNet, "Avoidance") { inverse = true }; perception.SendPositions(this.avoidance); + this.boundary = new(neuroidNet, "Boundary"); + perception.SendPositions(this.boundary, Boid.BoundaryType); + this.output = new(neuroidNet, "Swarming"); this.output.GetInputFrom(alignment, sc.alignmentForce); this.output.GetInputFrom(cohesion, sc.cohesionForce); this.output.GetInputFrom(avoidance, -sc.avoidanceForce); + this.output.GetInputFrom(boundary, -sc.avoidanceForce); } public override void AddReceiver(Neuroid receiver) {