diff --git a/Assets/NanoBrain/Editor/NeuroidWindow.cs b/Assets/NanoBrain/Editor/NeuroidWindow.cs index b0510df..72f9a9b 100644 --- a/Assets/NanoBrain/Editor/NeuroidWindow.cs +++ b/Assets/NanoBrain/Editor/NeuroidWindow.cs @@ -5,13 +5,13 @@ using System.Collections.Generic; public class NeuroidLayer { public int ix = 0; - public List neuroids = new(); + public List neuroids = new(); } public class GraphEditorWindow : EditorWindow { - private Neuroid currentNeuroid; + private Nucleus currentNucleus; private List allNeuroids; - private Dictionary neuroidPositions = new(); + private Dictionary neuroidPositions = new(); private List layers = new(); @@ -21,7 +21,58 @@ public class GraphEditorWindow : EditorWindow { SelectNeuron(); } - private void BuildLayers(List neuroids) { + private void AddToLayer(NeuroidLayer layer, Nucleus nucleus) { + layer.neuroids.Add(nucleus); + nucleus.layerIx = layer.ix; + // Store its position + Vector2Int neuroidPosition = new(layer.ix, layer.neuroids.Count - 1); + neuroidPositions[nucleus] = neuroidPosition; + + } + + private void BuildLayers() { + // A temporary list to track what's been added to layers + this.layers = new(); + int layerIx = 0; + + Nucleus selectedNucleus = this.currentNucleus; + NeuroidLayer currentLayer = new() { ix = layerIx }; + + foreach (Neuroid outputNeuroid in selectedNucleus.outputNeuroids) { + if (outputNeuroid != null) { + AddToLayer(currentLayer, outputNeuroid); + Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}"); + } + } + if (currentLayer.neuroids.Count > 0) { + this.layers.Add(currentLayer); + layerIx++; + currentLayer = new() { ix = layerIx }; + } + + AddToLayer(currentLayer, selectedNucleus); + this.layers.Add(currentLayer); + Debug.Log($"layer {layerIx} nucleus {selectedNucleus.name}"); + + layerIx++; + currentLayer = new() { ix = layerIx }; + + int six = 0; + foreach (Synapse synapse in selectedNucleus.synapses.Values) { + Debug.Log($"Synapse {six}"); + Nucleus input = synapse.neuroid; + if (input != null) { + AddToLayer(currentLayer, input); + Debug.Log($"layer {layerIx} nucleus {input.name}"); + } + six++; + } + if (currentLayer.neuroids.Count > 0) { + this.layers.Add(currentLayer); + } + } + + private void BuildLayers_old(List neuroids) { if (neuroids == null) return; @@ -41,10 +92,11 @@ public class GraphEditorWindow : EditorWindow { if (neuronVisited.Contains(neuroid)) continue; - if (neuroid.IsStale()) { - neuronVisited.Add(neuroid); - continue; - } + // if (neuroid.IsStale()) { + // Debug.Log($"neuron {neuroid.name} is stale {neuroid.stale}"); + // neuronVisited.Add(neuroid); + // continue; + // } // If the output neuroid is visited // Note: this does not yet work for multiple outputs yet (see the use of First()) @@ -92,7 +144,7 @@ public class GraphEditorWindow : EditorWindow { } private void DrawGraph() { - if (currentNeuroid == null) + if (currentNucleus == null) return; foreach (NeuroidLayer layer in layers) @@ -103,56 +155,88 @@ public class GraphEditorWindow : EditorWindow { int column = layer.ix * 100; int nodeCount = layer.neuroids.Count; float maxValue = 0; - foreach (Neuroid neuroid in layer.neuroids) { - float value = neuroid.outputValue.magnitude; - if (value > maxValue) - maxValue = value; + foreach (Nucleus nucleus in layer.neuroids) { + if (nucleus is Neuroid neuroid) { + float value = neuroid.outputValue.magnitude; + if (value > maxValue) + maxValue = value; + } } - float spacing = 200f / nodeCount; + float spacing = 400f / nodeCount; float margin = 100 + spacing / 2; - foreach (Neuroid layerNeuroid in layer.neuroids) { - Vector2Int layerNeuroidPos = this.neuroidPositions[layerNeuroid]; - Vector3 parentPos = new(100 + layerNeuroidPos.x * 100, margin + layerNeuroidPos.y * spacing, 0.1f); + foreach (Nucleus layerNucleus in layer.neuroids) { + if (layerNucleus is Neuroid layerNeuroid) { + Vector2Int layerNeuroidPos = this.neuroidPositions[layerNeuroid]; + Vector3 parentPos = new(100 + layerNeuroidPos.x * 100, margin + layerNeuroidPos.y * spacing, 0.1f); - int i = 0; - float inputSpacing = 200f / layerNeuroid.synapses.Count; - float inputMargin = 100 + inputSpacing / 2; - foreach (Synapse synapse in layerNeuroid.synapses.Values) { - if (synapse.neuroid != null) { - if (this.neuroidPositions.ContainsKey(synapse.neuroid)) { + int i = 0; + float inputSpacing = 400f / layerNeuroid.synapses.Count; + float inputMargin = 100 + inputSpacing / 2; + foreach (Synapse synapse in layerNeuroid.synapses.Values) { + if (synapse.neuroid != null) { + if (this.neuroidPositions.ContainsKey(synapse.neuroid)) { - Vector2Int inputNeuroidPos = this.neuroidPositions[synapse.neuroid]; - Vector3 pos = new(100 + inputNeuroidPos.x * 100, inputMargin + inputNeuroidPos.y * inputSpacing, 0.0f); + Vector2Int inputNeuroidPos = this.neuroidPositions[synapse.neuroid]; + if (inputNeuroidPos.x == layerNeuroidPos.x + 1) { + Vector3 pos = new(100 + inputNeuroidPos.x * 100, inputMargin + inputNeuroidPos.y * inputSpacing, 0.0f); - float brightness = synapse.weight / 10.0f; - Handles.color = new Color(brightness, brightness, brightness); - Handles.DrawLine(parentPos, pos); + float brightness = synapse.weight / 10.0f; + Handles.color = new Color(brightness, brightness, brightness); + Handles.DrawLine(parentPos, pos); + } + } } } + + float size = 20; + if (layerNeuroid.IsStale()) + Handles.color = Color.black; + else { + float brightness = layerNeuroid.outputValue.magnitude / maxValue; + Handles.color = new Color(brightness, brightness, brightness); + } + Handles.DrawSolidDisc(parentPos, Vector3.forward, size); + Vector3 labelPos = parentPos - Vector3.down * (size + 0.2f); // below disc along up axis + GUIStyle style = new GUIStyle(EditorStyles.label) { + alignment = TextAnchor.UpperCenter, + normal = { textColor = Color.white }, + fontStyle = FontStyle.Bold + }; + Handles.Label(labelPos, layerNeuroid.name, style); + + Rect neuronRect = new(parentPos.x - size, parentPos.y - size, size * 2, size * 2); + Event e = Event.current; + if (e != null && neuronRect.Contains(e.mousePosition)) { + HandleMouseHover(layerNeuroid, neuronRect); + // Process click + if (e.type == EventType.MouseDown && e.button == 0) { + // Consume the event so the scene doesn't also handle it + e.Use(); + HandleDiscClicked(layerNeuroid); + } + } + i++; } - - float size = layerNeuroid.outputValue.magnitude / maxValue * 20; - if (layerNeuroid.IsStale()) - Handles.color = Color.yellow; - else - Handles.color = Color.white; - Handles.DrawSolidDisc(parentPos, Vector3.forward, size); - Rect neuronRect = new(parentPos.x - size, parentPos.y - size, size * 2, size * 2); - if (neuronRect.Contains(Event.current.mousePosition)) - HandleMouseHover(layerNeuroid, neuronRect); - i++; - } } private void HandleMouseHover(Neuroid neuroid, Rect rect) { - // Draw the tooltip - GUIContent tooltip = new( - $"{neuroid.name}" + - $"\nsynapse count {neuroid.synapses.Count}" + - $"\nValue: {neuroid.outputValue}" + - $"\nStale: {neuroid.stale}"); - + GUIContent tooltip; + if (neuroid is SensoryNeuroid sensoryNeuroid) { + tooltip = new( + $"{sensoryNeuroid.name}" + + $"\nThing {sensoryNeuroid.receptor.thingId}" + + $"\nValue: {neuroid.outputValue}" + + $"\nStale: {neuroid.stale}"); + } + else { + tooltip = new( + $"{neuroid.name}" + + $"\nsynapse count {neuroid.synapses.Count}" + + $"\nValue: {neuroid.outputValue}" + + $"\nStale: {neuroid.stale}"); + } + Vector2 mousePosition = Event.current.mousePosition; // Display tooltip with some offset @@ -162,6 +246,10 @@ public class GraphEditorWindow : EditorWindow { GUI.Box(tooltipRect, tooltip); } + private void HandleDiscClicked(Nucleus nucleus) { + this.currentNucleus = nucleus; + BuildLayers(); + } // Update node colors based on selected GameObjects private void SelectNeuron() { @@ -175,15 +263,15 @@ public class GraphEditorWindow : EditorWindow { return; Neuroid neuroid = boid.totalForce; - this.currentNeuroid = neuroid; + this.currentNucleus = neuroid; if (neuroid == null) this.allNeuroids = new(); else this.allNeuroids = neuroid.net.neuroids; - //Debug.Log($"Neuroncount = {this.allNeuroids.Count}"); - BuildLayers(this.allNeuroids); + Debug.Log($"Neuroncount = {this.allNeuroids.Count}"); + BuildLayers(); Debug.Log($"Layercount = {this.layers.Count}"); } diff --git a/Assets/NanoBrain/Neuroid.cs b/Assets/NanoBrain/Neuroid.cs index e214df4..f4ff48b 100644 --- a/Assets/NanoBrain/Neuroid.cs +++ b/Assets/NanoBrain/Neuroid.cs @@ -3,12 +3,12 @@ using UnityEngine; using System.Linq; public class Synapse { - public Synapse(Neuroid neuroid, Vector3 value, float weight) { + public Synapse(Nucleus neuroid, Vector3 value, float weight) { this.neuroid = neuroid; this.value = value; this.weight = weight; } - public Neuroid neuroid; + public Nucleus neuroid; public Vector3 value; public float weight; } @@ -23,20 +23,17 @@ public class NeuroidNetwork { public void Update() { foreach (Neuroid neuroid in neuroids) { - neuroid.stale++; + neuroid.stale++; + if (neuroid.IsStale()) + neuroid.outputValue = Vector3.zero; } } } public class Neuroid : Nucleus { - public string name; - public int stale = 0; - // public readonly Dictionary synapses = new(); - - public Vector3 outputValue; - // public HashSet outputNeuroids = new(); + //public Vector3 outputValue; public bool average = false; //public bool quadratic = false; @@ -45,11 +42,12 @@ public class Neuroid : Nucleus { public NeuroidNetwork net; - public Neuroid(NeuroidNetwork net, string name) : base(net) { + public Neuroid(NeuroidNetwork net, string name) : base(name) { this.net = net; - this.name = name; if (this.net != null) this.net.neuroids.Add(this); + else + Debug.LogError("No neuroid network"); } public void AddSynapse(Neuroid input) { @@ -129,8 +127,14 @@ public class Neuroid : Nucleus { protected virtual void UpdateState() { Vector3 result = Vector3.zero; foreach (Synapse synapse in this.synapses.Values) { + // if (synapse.neuroid == null) + // continue; Vector3 direction = synapse.value.normalized; float magnitude = synapse.value.magnitude; + + // Vector3 direction = synapse.neuroid.outputValue.normalized; + // float magnitude = synapse.neuroid.outputValue.magnitude; + magnitude = synapse.weight * Mathf.Pow(magnitude, exponent); if (inverse) magnitude = 1 / magnitude; diff --git a/Assets/NanoBrain/Nucleus.cs b/Assets/NanoBrain/Nucleus.cs index 4272ae4..6fac46d 100644 --- a/Assets/NanoBrain/Nucleus.cs +++ b/Assets/NanoBrain/Nucleus.cs @@ -1,26 +1,21 @@ using System.Collections.Generic; +using UnityEngine; public class Nucleus { - //public Neuroid output; - public readonly Dictionary synapses = new(); + public string name; + public readonly Dictionary synapses = new(); public HashSet outputNeuroids = new(); + public virtual Vector3 outputValue {get; set; } public int layerIx; - - public Nucleus(NeuroidNetwork neuroidNet) { - //this.output = new(neuroidNet, "Nucleus output"); + public Nucleus(string name) { + this.name = name; } public virtual void AddReceiver(Neuroid receiver) { - //this.output.AddReceiver(receiver); this.outputNeuroids.Add(receiver); + receiver.synapses[this] = new(this, Vector3.zero, 1.0f); } - - // public void GetInputFrom(Neuroid input, float weight = 1.0f) { - // input.AddReceiver(this); - // this.synapses[input] = new(input, Vector3.zero, weight); - // } - } \ No newline at end of file diff --git a/Assets/NanoBrain/Perception.cs b/Assets/NanoBrain/Perception.cs index 5516459..c38f12b 100644 --- a/Assets/NanoBrain/Perception.cs +++ b/Assets/NanoBrain/Perception.cs @@ -15,7 +15,7 @@ public class Perception : Nucleus { public HashSet positionReceivers { get; protected set; } public HashSet velocityReceivers { get; protected set; } - public Perception(NeuroidNetwork neuroidNet) : base(neuroidNet) { + public Perception(NeuroidNetwork neuroidNet) : base("Perception") { this.neuroidNet = neuroidNet; this.positionReceivers = new(); this.velocityReceivers = new(); @@ -48,9 +48,9 @@ public class Perception : Nucleus { } } - public void ProcessStimulus(int thingId, int thingType, Vector3 localPosition) { + public void ProcessStimulus(int thingId, int thingType, Vector3 localPosition, string name = "Sensing") { int availableIx = -1; - SensoryNeuroid leastInterestingNeuroid = null; + int leastInterestingIx = -1; for (int i = 0; i < sensoryNeuroids.Length; i++) { if (sensoryNeuroids[i] == null || sensoryNeuroids[i].IsStale()) availableIx = i; @@ -59,39 +59,28 @@ public class Perception : Nucleus { return; } if (sensoryNeuroids[i] != null) { - if (leastInterestingNeuroid == null || leastInterestingNeuroid.receptor.position.magnitude > sensoryNeuroids[i].receptor.position.magnitude) - leastInterestingNeuroid = sensoryNeuroids[i]; + if (leastInterestingIx == -1 || sensoryNeuroids[leastInterestingIx].receptor.position.magnitude > sensoryNeuroids[i].receptor.position.magnitude) + leastInterestingIx = i; } } + if (availableIx == -1) + availableIx = leastInterestingIx; + if (availableIx != -1) { - if (sensoryNeuroids[availableIx] != null) { - // Debug.Log($"revived receptor {availableIx} for {thingId}"); - sensoryNeuroids[availableIx].receptor.thingId = thingId; - sensoryNeuroids[availableIx].receptor.position = localPosition; + // Debug.Log($"new receptor for {thingId}"); + SensoryNeuroid neuroid = new(neuroidNet, thingId) { name = name }; + foreach (Receiver receiver in positionReceivers) { + if (receiver.thingType == 0 || receiver.thingType == thingType) + receiver.neuroid.GetInputFrom(neuroid); } - else { - // Debug.Log($"new receptor for {thingId}"); - SensoryNeuroid neuroid = new(neuroidNet, thingId); - foreach (Receiver receiver in positionReceivers) { - if (receiver.thingType == 0 || receiver.thingType == thingType) - receiver.neuroid.GetInputFrom(neuroid); - } - foreach (Receiver receiver in velocityReceivers) { - if (receiver.thingType == 0 || receiver.thingType == thingType) - receiver.neuroid.GetInputFrom(neuroid.velocityNeuroid); - } - - sensoryNeuroids[availableIx] = neuroid; - neuroid.receptor.position = localPosition; + foreach (Receiver receiver in velocityReceivers) { + if (receiver.thingType == 0 || receiver.thingType == thingType) + receiver.neuroid.GetInputFrom(neuroid.velocityNeuroid); } - } - else if (leastInterestingNeuroid != null) { - //Debug.Log($"replaced receptor {leastInterestingNeuroid.thingId} for {thingId}"); - leastInterestingNeuroid.receptor.thingId = thingId; - leastInterestingNeuroid.receptor.position = localPosition; - } - //Debug.LogWarning($"No available receptor for {id}"); + sensoryNeuroids[availableIx] = neuroid; + neuroid.receptor.position = localPosition; + } } public void RemoveStimulus(int thingId) { @@ -103,6 +92,6 @@ public class Perception : Nucleus { return; } } - + } } \ No newline at end of file diff --git a/Assets/Scenes/Boids/Boids.unity b/Assets/Scenes/Boids/Boids.unity index e7ee4e7..ce4720b 100644 --- a/Assets/Scenes/Boids/Boids.unity +++ b/Assets/Scenes/Boids/Boids.unity @@ -393,7 +393,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ec888ca5333d45a438f9f417fa5ce135, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::SwarmSpawn - count: 1 + count: 2 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 b3326fe..9a2f7c8 100644 --- a/Assets/Scenes/Boids/Scripts/Boid.cs +++ b/Assets/Scenes/Boids/Scripts/Boid.cs @@ -21,15 +21,10 @@ public class Boid : MonoBehaviour { private Bounds innerBounds; private Bounds outerBounds; - readonly Collider[] results = new Collider[10]; - public NeuroidNetwork neuroidNet = new(); public Perception perception; - public Neuroid cohesion; - public Neuroid alignment; - public Neuroid avoidance; - // public Neuroid boundary; - public Roaming roaming; + + public Nucleus behaviour; public Neuroid totalForce; @@ -45,53 +40,34 @@ public class Boid : MonoBehaviour { perception = new Perception(neuroidNet); - // cohesion = new(neuroidNet, "Cohesion"); - // perception.SendPositions(cohesion, 1.0f, BoidType); - - // alignment = new(neuroidNet, "Alignment") { average = true }; - // perception.SendVelocities(alignment); - - // avoidance = new(neuroidNet, "Separation") { inverse = true }; - // perception.SendPositions(avoidance, sc.avoidanceForce); - - //boundary = new(neuroidNet, "Boundary"); - - roaming = new(neuroidNet, perception, sc); + //behaviour = new Roaming(neuroidNet, perception, sc); + behaviour = new Swarming(neuroidNet, perception, sc); totalForce = new(neuroidNet, "Total"); - //totalForce.GetInputFrom(alignment, sc.alignmentForce); - //totalForce.GetInputFrom(cohesion, sc.cohesionForce); - // totalForce.GetInputFrom(avoidance, -sc.avoidanceForce); - //totalForce.GetInputFrom(boundary, sc.boundaryForce); - roaming.AddReceiver(totalForce); + behaviour.AddReceiver(totalForce); } void Update() { - // Physics.OverlapSphereNonAlloc(this.transform.position, sc.perceptionDistance, results); - // foreach (Collider c in results) { - // if (c == null) - // continue; + Collider[] results = Physics.OverlapSphere(this.transform.position, sc.perceptionDistance); + foreach (Collider c in results) { + if (c as CapsuleCollider != null) { + Boid neighbour = c.GetComponentInParent(); + if (neighbour == null || neighbour == this) + continue; - // if (c as CapsuleCollider != null) { - // Boid neighbour = c.GetComponentInParent(); - // if (neighbour == null || neighbour == this) - // continue; + Vector3 localPosition = neighbour.transform.position - this.transform.position; - // Vector3 localPosition = neighbour.transform.position - this.transform.position; - // if (debug) - // Debug.Log($" distance {localPosition.magnitude}"); - - // int thingId = neighbour.GetInstanceID(); - // perception.ProcessStimulus(thingId, localPosition); - // } - // } + int thingId = neighbour.GetInstanceID(); + perception.ProcessStimulus(thingId, BoidType, localPosition); //, neighbour.gameObject.name); + } + } if (!innerBounds.Contains(this.transform.position)) { Vector3 point = this.transform.position; Vector3 pointOnBounds = innerBounds.ClosestPoint(point); Vector3 desiredWorldSpace = (pointOnBounds - point).normalized * sc.speed; Vector3 desiredLocalSpace = -this.transform.InverseTransformPoint(desiredWorldSpace); - perception.ProcessStimulus(777, BoundaryType, desiredLocalSpace); + perception.ProcessStimulus(777, BoundaryType, desiredLocalSpace, "Boundary"); } else { perception.RemoveStimulus(777); diff --git a/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs b/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs index a8889c6..50b6912 100644 --- a/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs +++ b/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs @@ -1,18 +1,11 @@ public class Roaming : Nucleus { - public float avoidanceForce; public Neuroid avoidance; - public Neuroid output; - - - public const int BoundaryType = 1; - public const int BoidType = 2; - - public Roaming(NeuroidNetwork neuroidNet, Perception perception, SwarmControl sc) : base(neuroidNet) { - this.avoidanceForce = sc.avoidanceForce; + public Neuroid output; + public Roaming(NeuroidNetwork neuroidNet, Perception perception, SwarmControl sc) : base("Roaming nucleus") { avoidance = new(neuroidNet, "Avoidance") { inverse = true }; - perception.SendPositions(avoidance); + perception.SendPositions(avoidance, 1.0f, 1); 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 f6ec099..f416c38 100644 --- a/Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs +++ b/Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs @@ -1,31 +1,34 @@ -public class Swarming : Nucleus { - //public Perception perception; +using UnityEngine; +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(NeuroidNetwork neuroidNet, Perception perception, SwarmControl sc) : base(neuroidNet) { - cohesion = new(neuroidNet, "Cohesion"); - perception.SendPositions(cohesion, 1.0f, BoidType); + public Swarming(NeuroidNetwork neuroidNet, Perception perception, SwarmControl sc) : base("Swarming Nucleus") { + this.cohesion = new(neuroidNet, "Cohesion"); + perception.SendPositions(this.cohesion, 1.0f, BoidType); - alignment = new(neuroidNet, "Alignment") { average = true }; - perception.SendVelocities(alignment); + this.alignment = new(neuroidNet, "Alignment") { average = true }; + perception.SendVelocities(this.alignment, 1.0f, BoidType); - avoidance = new(neuroidNet, "Separation") { inverse = true }; - perception.SendPositions(avoidance, sc.avoidanceForce); - - boundary = new(neuroidNet, "Boundary"); + this.avoidance = new(neuroidNet, "Avoidance") { inverse = true }; + perception.SendPositions(this.avoidance); this.output = new(neuroidNet, "Swarming"); - output.GetInputFrom(alignment, sc.alignmentForce); - output.GetInputFrom(cohesion, sc.cohesionForce); - output.GetInputFrom(avoidance, -sc.avoidanceForce); - output.GetInputFrom(boundary, sc.boundaryForce); - }} \ No newline at end of file + //this.output.GetInputFrom(alignment, sc.alignmentForce); + this.output.GetInputFrom(cohesion, sc.cohesionForce); + this.output.GetInputFrom(avoidance, -sc.avoidanceForce); + } + + public override void AddReceiver(Neuroid receiver) { + this.output.AddReceiver(receiver); + } +} \ No newline at end of file