Brain object

This commit is contained in:
Pascal Serrarens 2025-12-11 12:27:55 +01:00
parent 86b5053383
commit 21751f8cea
23 changed files with 611 additions and 256 deletions

View File

@ -50,6 +50,7 @@
<ItemGroup>
<Compile Include="Assets/NanoBrain/VisualEditor/NanoBrainObj.cs" />
<Compile Include="Assets/Scenes/Boids/Scripts/SwarmSpawner.cs" />
<Compile Include="Assets/NanoBrain/Perceptoid.cs" />
<Compile Include="Assets/NanoBrain/NeuroidBehaviour.cs" />
<Compile Include="Assets/NanoBrain/NanoBrain.cs" />
<Compile Include="Assets/NanoBrain/SensoryNeuroid.cs" />

View File

@ -221,7 +221,7 @@ public class NanoBrain_Editor : Editor {
if (neuroid is SensoryNeuroid sensoryNeuroid) {
tooltip = new(
$"{sensoryNeuroid.name}" +
$"\nThing {sensoryNeuroid.receptor.thingId}" +
$"\nThing {sensoryNeuroid.receptor.thingType}" +
$"\nValue: {neuroid.outputValue}" +
$"\nStale: {neuroid.stale}");
}

View File

@ -244,7 +244,7 @@ public class GraphEditorWindow : EditorWindow {
if (neuroid is SensoryNeuroid sensoryNeuroid) {
tooltip = new(
$"{sensoryNeuroid.name}" +
$"\nThing {sensoryNeuroid.receptor.thingId}" +
$"\nThing {sensoryNeuroid.receptor.thingType}" +
$"\nValue: {neuroid.outputValue}" +
$"\nStale: {neuroid.stale}");
}

View File

@ -1,5 +1,6 @@
using UnityEngine;
[System.Serializable]
public class Neuroid : Nucleus {
public bool average = false;
public bool inverse = false;
@ -13,9 +14,17 @@ public class Neuroid : Nucleus {
// Debug.LogError("No neuroid network");
// }
public Neuroid(NanoBrainObj brain, string name) : base(brain, name) {
public Neuroid(NanoBrainObj brain, string name) : base(name) {
this.brain = brain;
if (this.brain != null) {
this.brain.nuclei.Add(this);
}
else
Debug.LogError("No neuroid network");
}
public Neuroid(string name): base(name) {}
public void SetWeight(Neuroid input, float weight) {
//this.synapses[input] = weight;
this.SetWeight((Nucleus)input, weight);
@ -30,7 +39,7 @@ public class Neuroid : Nucleus {
public void SetInput(Neuroid input) {
// if (this.synapses.ContainsKey(input) == false)
// this.synapses[input] = 1.0f;
if (this.SynapseExists(input))
if (this.SynapseExists(input) == false)
this.SetWeight(input, 1.0f);
UpdateState();
}

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using UnityEngine;
@ -13,33 +14,53 @@ public class Nucleus {
set => _name = value;
}
//public readonly Dictionary<Nucleus, float> synapses = new();
[SerializeField]
public List<Synapse> synapses = new();
//public HashSet<Nucleus> receivers = new();
public List<Synapse> synapses = new();
[SerializeField]
public List<Receiver> receivers = new();
#region Serialization
public void Rebuild(NanoBrainObj brain) {
[SerializeField]
protected string nucleusType;
public virtual void Rebuild(NanoBrainObj brain) {
if (this.synapses != null) {
foreach (Synapse synapse in synapses)
synapse.Rebuild(brain);
}
if (this.receivers == null)
this.receivers = new();
else
foreach (Receiver receiver in receivers)
receiver.Rebuild(brain);
else {
foreach (Receiver receiver in receivers.ToArray()) {
if (receiver.Rebuild(brain) == false) {
Debug.Log("Rebuilding failed, removing receiver.");
receivers.Remove(receiver);
}
}
}
}
#endregion
public static Nucleus RebuildType(NanoBrainObj brain, Nucleus nucleus) {
if (string.IsNullOrEmpty(nucleus.nucleusType) == false) {
Type nucleusType = Type.GetType(nucleus.nucleusType);
if (nucleusType != null) {
object[] args = new object[] { brain, nucleus.name };
Nucleus rebuiltNucleus = (Nucleus)Activator.CreateInstance(nucleusType, args);
rebuiltNucleus.Deserialize(nucleus);
return rebuiltNucleus;
}
}
return nucleus;
}
public virtual void Deserialize(Nucleus nucleus) { }
#endregion Serialization
#region Runtime state (not serialized)
// public NanoBrain brain { get; protected set; }
public NanoBrainObj newBrain { get; protected set; }
public NanoBrainObj brain { get; protected set; }
public virtual Vector3 outputValue { get; set; }
@ -50,12 +71,13 @@ public class Nucleus {
#endregion Runtime state
public Nucleus(NanoBrainObj brain, string name) {
this.newBrain = brain;
if (this.newBrain != null)
this.newBrain.nuclei.Add(this);
else
Debug.LogError("No neuroid network");
public Nucleus(string name) {
// this.brain = brain;
// if (this.brain != null) {
// this.brain.nuclei.Add(this);
// }
// else
// Debug.LogError("No neuroid network");
this._name = name;
this.id = this.GetHashCode();
@ -63,9 +85,7 @@ public class Nucleus {
public virtual void AddReceiver(Nucleus receiver) {
this.receivers.Add(new Receiver(receiver));
//receiver.synapses[this] = 1.0f; // new(this);
receiver.SetWeight(this, 1.0f);
//Debug.Log($"receiver # {this.receivers.Count} synapse count {receiver.synapses.Count}");
}
public static void Delete(Nucleus nucleus) {
@ -73,15 +93,19 @@ public class Nucleus {
if (synapse.nucleus.receivers.Count > 1) {
// there is another nucleus feeding into this input nucleus
synapse.nucleus.receivers.RemoveAll(r => r.nucleus == nucleus);
} else {
}
else {
// No other links, delete it.
Nucleus.Delete(synapse.nucleus);
}
}
foreach (Receiver receiver in nucleus.receivers)
receiver.nucleus.synapses.RemoveAll(s => s.nucleus == nucleus);
foreach (Receiver receiver in nucleus.receivers) {
if (receiver.nucleus != null && receiver.nucleus.synapses != null)
receiver.nucleus.synapses.RemoveAll(s => s.nucleus == nucleus);
}
nucleus.newBrain.nuclei.RemoveAll(n => n == nucleus);
nucleus.brain.nuclei.RemoveAll(n => n == nucleus);
nucleus.brain.GarbageCollection();
}
public void GetInputFrom(Nucleus input, float weight = 1.0f) {
@ -113,6 +137,7 @@ public class Nucleus {
Synapse newSynapse = new(nucleus, weight);
synapses.Add(newSynapse);
}
}
[System.Serializable]
@ -139,6 +164,12 @@ public class Synapse {
return;
}
}
foreach (Perceptoid perceptoid in brain.perceptei) {
if (perceptoid.id == this.nucleusId) {
this.nucleus = perceptoid;
return;
}
}
Debug.LogError($"Synapse deserialization error: could not find nucleus with id {this.nucleusId}");
}
}
@ -154,16 +185,18 @@ public class Receiver {
this.nucleusId = nucleus.id;
}
public void Rebuild(NanoBrainObj brain) {
if (brain == null)
return;
public bool Rebuild(NanoBrainObj brain) {
if (brain == null) {
return false;
}
foreach (Nucleus nucleus in brain.nuclei) {
if (nucleus.id == this.nucleusId) {
this.nucleus = nucleus;
return;
return true;
}
}
Debug.LogError($"Synapse deserialization error: could not find nucleus with id {this.nucleusId}");
Debug.LogWarning($"Receiver deserialization error: could not find nucleus with id {this.nucleusId}");
return false;
}
}

View File

@ -16,11 +16,18 @@ public class Perception : Nucleus {
//public HashSet<Receiver> velocityReceivers { get; protected set; }
public List<Receiver> velocityReceivers;
public Perception(NanoBrainObj brain) : base(brain, "Perception") {
public Perception(NanoBrainObj brain) : base("Perception") {
this.positionReceivers = new();
this.velocityReceivers = new();
}
public Perceptoid GetPerception(int thingType = 0) {
foreach (Nucleus nucleus in this.brain.nuclei) {
if (nucleus is Perceptoid perceptoid && (thingType == 0 || perceptoid.receptor.thingType == thingType))
return perceptoid;
}
return null;
}
public void SendPositions(Nucleus receivingNeuroid, int thingType = 0, float weight = 1.0f) {
Receiver receiver = new() {
@ -55,7 +62,7 @@ public class Perception : Nucleus {
for (int i = 0; i < sensoryNeuroids.Length; i++) {
if (sensoryNeuroids[i] == null)
availableIx = i;
else if (sensoryNeuroids[i].receptor.thingId == thingId) {
else if (sensoryNeuroids[i].receptor.thingType == thingId) {
sensoryNeuroids[i].receptor.position = localPosition;
return;
}
@ -80,7 +87,7 @@ public class Perception : Nucleus {
}
else {
Debug.Log($"new receptor for {thingId} at {availableIx}");
neuroid = new(newBrain, thingId, name);
neuroid = new(brain, thingId, name);
sensoryNeuroids[availableIx] = neuroid;
}
foreach (Receiver receiver in positionReceivers) {

View File

@ -0,0 +1,156 @@
using UnityEngine;
[System.Serializable]
public class Perceptoid : Neuroid {
// A neuroid which has no neurons as input
// But receives value from a receptor
public Receptor receptor;
public VelocityNeuroid velocityNeuroid;
#region Serialization
[SerializeField]
public int thingType;
public override void Rebuild(NanoBrainObj brain) {
base.Rebuild(brain);
this.receptor = new Receptor() {
neuroid = this
};
}
public override void Deserialize(Nucleus nucleus) {
base.Deserialize(nucleus);
if (nucleus is Perceptoid perceptoid)
this.receptor.thingType = perceptoid.thingType;
// Point all receivers to this perceptoid instead of the default nucleus
foreach (Receiver receiver in nucleus.receivers) {
foreach (Synapse synapse in receiver.nucleus.synapses) {
if (synapse.nucleus == nucleus)
synapse.nucleus = this;
}
}
// Point all synapses to this perceptoid instead of the default nucleus
foreach (Synapse synapse in nucleus.synapses) {
foreach (Receiver receiver in synapse.nucleus.receivers) {
if (receiver.nucleus == nucleus)
receiver.nucleus = this;
}
}
// Copy all the synapses
this.synapses = nucleus.synapses;
// Copy all receivers
this.receivers = nucleus.receivers;
}
#endregion Serialization
public Perceptoid(NanoBrainObj brain, string name) : base(name) {
this.brain = brain;
if (this.brain != null) {
this.brain.perceptei.Add(this);
}
else
Debug.LogError("No neuroid network");
this.nucleusType = nameof(Perceptoid);
this.name = name;
this.receptor = new Receptor {
neuroid = this
};
}
public Perceptoid(NanoBrainObj brain, int thingType, string name = "sensor") : base(name) {
this.brain = brain;
if (this.brain != null) {
this.brain.perceptei.Add(this);
}
else
Debug.LogError("No neuroid network");
this.nucleusType = nameof(Perceptoid);
this.name = name;
this.thingType = thingType;
this.receptor = new Receptor {
neuroid = this,
thingType = thingType
};
this.velocityNeuroid = new(brain, name + ": velocity");
// The velocity neuroid received position data from this
this.AddReceiver(velocityNeuroid);
}
public void Replace(int thingType, string name = "sensor") {
this.name = name;
this.thingType = thingType;
this.receptor.thingType = thingType;
this.receptor.localPosition = Vector3.zero;
this.outputValue = Vector3.zero;
this.receivers = new();
this.AddReceiver(velocityNeuroid);
this.velocityNeuroid.Replace(name + ": velocity");
}
public override void UpdateState() {
Vector3 result = receptor.localPosition;
foreach (Synapse synapse in this.synapses) {
Nucleus nucleus = synapse.nucleus;
float weight = synapse.weight;
Vector3 direction = nucleus.outputValue.normalized;
float magnitude = nucleus.outputValue.magnitude;
magnitude = weight * Mathf.Pow(magnitude, exponent);
if (inverse)
magnitude = 1 / magnitude;
result += direction * magnitude;
}
if (average && this.synapses.Count > 0)
result /= this.synapses.Count + 1;
this.outputValue = result;
foreach (Receiver receiver in this.receivers)
if (receiver.nucleus is Neuroid neuroid)
neuroid.SetInput(this);
this.stale = 0;
}
public static Perceptoid GetPerception(NanoBrainObj brain, int thingType = 0) {
foreach (Nucleus nucleus in brain.nuclei) {
if (nucleus is Perceptoid perceptoid && (thingType == 0 || perceptoid.thingType == thingType))
return perceptoid;
}
return null;
}
public static void ProcessStimulus(NanoBrainObj brain, int thingType, Vector3 lcoalPosition) {
Perceptoid selectedPerceptoid = null;
foreach (Perceptoid nucleus in brain.perceptei) {
if (nucleus is Perceptoid perceptoid && (thingType == 0 || perceptoid.thingType == thingType))
if (selectedPerceptoid == null) {
selectedPerceptoid = perceptoid;
if (perceptoid.isSleeping) {
break;
}
}
else if (perceptoid.receptor.position.magnitude < selectedPerceptoid.receptor.position.magnitude)
selectedPerceptoid = perceptoid;
}
if (selectedPerceptoid == null) {
Debug.Log("No perceptoid selected, stimulus is ignored");
return;
}
selectedPerceptoid.receptor.position = lcoalPosition;
}
public static Receptor GetReceptor(NanoBrainObj brain, int thingType) {
foreach (Perceptoid perceptoid in brain.perceptei) {
if (thingType == 0 || perceptoid.thingType == thingType)
return perceptoid.receptor;
}
return null;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 702f634001a21a9d7ae1057c8ce356e9

View File

@ -1,12 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class Receptor {
public SensoryNeuroid neuroid;
public Neuroid neuroid;
public int thingId;
public int thingType;
public Vector3 localPosition;
/// <summary>
@ -33,7 +32,7 @@ public class SensoryNeuroid : Neuroid {
this.name = name + ": position";
this.receptor = new Receptor {
neuroid = this,
thingId = thingId
thingType = thingId
};
this.velocityNeuroid = new(brain, name + ": velocity");
// The velocity neuroid received position data from this
@ -43,7 +42,7 @@ public class SensoryNeuroid : Neuroid {
public void Replace(int thingId, string name = "sensor") {
this.name = name + ": position";
this.receptor.thingId = thingId;
this.receptor.thingType = thingId;
this.receptor.localPosition = Vector3.zero;
this.outputValue = Vector3.zero;

View File

@ -11,14 +11,16 @@ public class NanoBrainComponent_Editor : Editor {
private SerializedProperty brainProp;
public void OnEnable() {
brainProp = serializedObject.FindProperty(nameof(NanoBrainComponent.brain));
if (Application.isPlaying == false)
brainProp = serializedObject.FindProperty(nameof(NanoBrainComponent.defaultBrain));
}
public override VisualElement CreateInspectorGUI() {
NanoBrainComponent component = target as NanoBrainComponent;
NanoBrainObj brain = component.brain;
NanoBrainObj brain = Application.isPlaying ? component.brain : component.defaultBrain;
serializedObject.Update();
if (Application.isPlaying == false)
serializedObject.Update();
VisualElement root = new();
@ -32,9 +34,11 @@ public class NanoBrainComponent_Editor : Editor {
root.styleSheets.Add(Resources.Load<StyleSheet>("GraphStyles"));
PropertyField brainField = new(brainProp);
brainField.label = "Nano Brain";
root.Add(brainField);
if (Application.isPlaying == false) {
PropertyField brainField = new(brainProp);
brainField.label = "Nano Brain";
root.Add(brainField);
}
mainContainer = new() {
name = "main",
@ -70,14 +74,15 @@ public class NanoBrainComponent_Editor : Editor {
if (brain != null)
board.SetGraph(brain, brain.root, inspectorContainer);
else
Debug.LogWarning(" No brain!");
// else
// Debug.LogWarning(" No brain!");
serializedObject.ApplyModifiedProperties();
if (Application.isPlaying == false)
serializedObject.ApplyModifiedProperties();
return root;
}
private void UpdateLayout(float containerWidth) {
private void UpdateLayout(float containerWidth) {
if (containerWidth > 800f) {
mainContainer.style.flexDirection = FlexDirection.Row;
inspectorContainer.style.width = 400; // fixed sidebar width

View File

@ -299,7 +299,7 @@ public class GraphBoardView : VisualElement {
if (neuroid is SensoryNeuroid sensoryNeuroid) {
tooltip = new(
$"{sensoryNeuroid.name}" +
$"\nThing {sensoryNeuroid.receptor.thingId}" +
$"\nThing {sensoryNeuroid.receptor.thingType}" +
$"\nValue: {neuroid.outputValue}" +
$"\nStale: {neuroid.stale}");
}

View File

@ -104,7 +104,8 @@ public class NanoBrainInspector : Editor {
public void SetGraph(NanoBrainObj brain, Nucleus nucleus, VisualElement inspectorContainer) {
this.brain = brain;
this.serializedBrain = new SerializedObject(brain);
if (Application.isPlaying == false)
this.serializedBrain = new SerializedObject(brain);
this.currentNucleus = nucleus;
Rebuild(inspectorContainer);
}
@ -212,17 +213,130 @@ public class NanoBrainInspector : Editor {
if (currentNucleus == null)
return;
serializedBrain.Update();
if (Application.isPlaying == false)
serializedBrain.Update();
Handles.BeginGUI();
foreach (NeuroidLayer layer in layers)
DrawLayer(layer);
DrawGraph();
// foreach (NeuroidLayer layer in layers)
// DrawLayer(layer);
Handles.EndGUI();
}
private void DrawGraph() {
float size = 20;
Vector3 position = new(200, 210, 0);
DrawReceivers(this.currentNucleus, position, size);
DrawSynapses(this.currentNucleus, position, size);
// Draw selected Nucleus
Handles.color = Color.white;
Handles.DrawSolidDisc(position, Vector3.forward, size + 2);
DrawNucleus(this.currentNucleus, position, this.currentNucleus.outputValue.magnitude, 20);
}
private void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) {
if (nucleus.isSleeping)
Handles.color = Color.darkRed;
else {
float brightness = nucleus.outputValue.magnitude / maxValue;
Handles.color = new Color(brightness, brightness, brightness);
}
Handles.DrawSolidDisc(position, Vector3.forward, size);
Vector3 labelPos = position - 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, nucleus.name, style);
Rect neuronRect = new(position.x - size, position.y - size, size * 2, size * 2);
int id = GUIUtility.GetControlID(FocusType.Passive);
Event e = Event.current;
EventType et = e.GetTypeForControl(id);
if (e != null && neuronRect.Contains(e.mousePosition)) {
// Process Hover
HandleMouseHover(nucleus, neuronRect);
// Process click
if (e.type == EventType.MouseDown && e.button == 0) {
// Consume the event so the scene doesn't also handle it
e.Use();
HandleClicked(nucleus);
}
}
}
private void DrawReceivers(Nucleus nucleus, Vector3 parentPos, float size) {
int nodeCount = nucleus.receivers.Count;
// Determine the maximum value in this layer
// This is used to 'scale' the output value colors of the nuclei
float maxValue = 0;
foreach (Receiver receiver in nucleus.receivers) {
if (receiver.nucleus is Neuroid neuroid) {
float value = neuroid.outputValue.magnitude;
if (value > maxValue)
maxValue = value;
}
}
// Determine the spacing of the nuclei in the layer
float spacing = 400f / nodeCount;
float margin = 10 + spacing / 2;
int row = 0;
foreach (Receiver receiver in nucleus.receivers) {
Nucleus receiverNucleus = receiver.nucleus;
Vector3 pos = new(100, margin + row * spacing, 0.0f);
Handles.color = Color.white;
Handles.DrawLine(parentPos, pos);
DrawNucleus(receiverNucleus, pos, maxValue, size);
row++;
}
}
private void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) {
int nodeCount = nucleus.synapses.Count;
// Determine the maximum value in this layer
// This is used to 'scale' the output value colors of the nuclei
float maxValue = 0;
foreach (Synapse receiver in nucleus.synapses) {
if (receiver.nucleus is Neuroid neuroid) {
float value = neuroid.outputValue.magnitude;
if (value > maxValue)
maxValue = value;
}
}
// Determine the spacing of the nuclei in the layer
float spacing = 400f / nodeCount;
float margin = 10 + spacing / 2;
int row = 0;
foreach (Synapse receiver in nucleus.synapses) {
Nucleus receiverNucleus = receiver.nucleus;
Vector3 pos = new(300, margin + row * spacing, 0.0f);
Handles.color = Color.white;
Handles.DrawLine(parentPos, pos);
DrawNucleus(receiverNucleus, pos, maxValue, size);
row++;
}
}
/*
private void DrawLayer(NeuroidLayer layer) {
int nodeCount = layer.neuroids.Count;
// Determine the maximum value in this layer
// This is used to 'scale' the output value colors of the nuclei
float maxValue = 0;
foreach (Nucleus nucleus in layer.neuroids) {
if (nucleus is Neuroid neuroid) {
@ -231,8 +345,11 @@ public class NanoBrainInspector : Editor {
maxValue = value;
}
}
// Determine the spacing of the nuclei in the layer
float spacing = 400f / nodeCount;
float margin = 10 + spacing / 2;
foreach (Nucleus layerNucleus in layer.neuroids) {
Vector2Int layerNeuroidPos = this.neuroidPositions[layerNucleus];
Vector3 parentPos = new(100 + layerNeuroidPos.x * 100, margin + layerNeuroidPos.y * spacing, 0.1f);
@ -240,11 +357,9 @@ public class NanoBrainInspector : Editor {
float inputSpacing = 400f / layerNucleus.synapses.Count;
float inputMargin = 10 + inputSpacing / 2;
int minStale = 10000;
Debug.Log($"layer neuron {layerNucleus.name} has {layerNucleus.synapses.Count} synapses");
foreach (Synapse synapse in layerNucleus.synapses) {
Nucleus nucleus = synapse.nucleus;
if (nucleus != null) {
Debug.Log($"Synapse to {nucleus.name} is valid"); float weight = synapse.weight;
if (this.neuroidPositions.ContainsKey(nucleus)) {
Vector2Int inputNeuroidPos = this.neuroidPositions[nucleus];
if (inputNeuroidPos.x == layerNeuroidPos.x + 1) {
@ -265,20 +380,21 @@ public class NanoBrainInspector : Editor {
Handles.color = Color.white;
Handles.DrawSolidDisc(parentPos, Vector3.forward, size + 2);
}
if (layerNucleus.isSleeping)
Handles.color = Color.darkRed;
else {
float brightness = layerNucleus.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, layerNucleus.name, style);
DrawNucleus(layerNucleus, parentPos, maxValue, size);
// if (layerNucleus.isSleeping)
// Handles.color = Color.darkRed;
// else {
// float brightness = layerNucleus.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, layerNucleus.name, style);
Rect neuronRect = new(parentPos.x - size, parentPos.y - size, size * 2, size * 2);
int id = GUIUtility.GetControlID(FocusType.Passive);
@ -296,13 +412,14 @@ public class NanoBrainInspector : Editor {
}
}
}
*/
private void HandleMouseHover(Nucleus neuroid, Rect rect) {
GUIContent tooltip;
if (neuroid is SensoryNeuroid sensoryNeuroid) {
tooltip = new(
$"{sensoryNeuroid.name}" +
$"\nThing {sensoryNeuroid.receptor.thingId}" +
$"\nThing {sensoryNeuroid.receptor.thingType}" +
$"\nValue: {neuroid.outputValue}" +
$"\nStale: {neuroid.stale}");
}
@ -343,7 +460,11 @@ public class NanoBrainInspector : Editor {
if (so.targetObject == null)
return;
so.Update();
if (this.currentNucleus == null)
return;
this.currentNucleus.name = EditorGUILayout.TextField(this.currentNucleus.name);
if (this.currentNucleus is Perceptoid currentPerceptoid)
currentPerceptoid.thingType = EditorGUILayout.IntField("Thing Type", currentPerceptoid.thingType);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Output Value", GUILayout.Width(100));
EditorGUILayout.Vector3Field(GUIContent.none, this.currentNucleus.outputValue);
@ -359,8 +480,15 @@ public class NanoBrainInspector : Editor {
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.EndHorizontal();
@ -372,10 +500,8 @@ public class NanoBrainInspector : Editor {
}
if (GUILayout.Button("Add Neuron"))
AddInputNeuron(this.currentNucleus);
if (GUILayout.Button("Add Position Perception"))
AddPositionPerception(this.currentNucleus);
if (GUILayout.Button("Add Velocity Perception"))
AddVelocityPerception(this.currentNucleus);
if (GUILayout.Button("Add Perceptoid"))
AddPerceptoid(this.currentNucleus);
if (GUILayout.Button("Delete this neuron"))
DeleteNeuron(this.currentNucleus);
@ -384,9 +510,9 @@ public class NanoBrainInspector : Editor {
inspectorContainer.Add(container);
}
protected virtual void AddInputNeuron(Nucleus receiver) {
protected virtual void AddInputNeuron(Nucleus nucleus) {
Neuroid newNeuroid = new(this.brain, "New neuron");
newNeuroid.AddReceiver(receiver);
newNeuroid.AddReceiver(nucleus);
//Rebuild(inspectorContainer);
BuildLayers();
}
@ -398,13 +524,19 @@ public class NanoBrainInspector : Editor {
BuildLayers();
}
protected virtual void AddPositionPerception(Nucleus receiver) {
this.brain.perception.SendPositions(receiver);
}
protected virtual void AddVelocityPerception(Nucleus receiver) {
this.brain.perception.SendVelocities(receiver);
protected virtual void AddPerceptoid(Nucleus nucleus) {
Perceptoid newPerceptoid = new(this.brain, 0, "New Perceptoid");
newPerceptoid.AddReceiver(nucleus);
BuildLayers();
}
// protected virtual void AddPositionPerception(Nucleus receiver) {
// this.brain.perception.SendPositions(receiver);
// }
// protected virtual void AddVelocityPerception(Nucleus receiver) {
// this.brain.perception.SendVelocities(receiver);
// }
private Vector3 NodePosition(Nucleus nucleus, int layerNodeCount = 1) {
if (this.neuroidPositions.ContainsKey(nucleus)) {
Vector2Int nucleusPos = this.neuroidPositions[nucleus];

View File

@ -1,15 +1,21 @@
using UnityEngine;
public class NanoBrainComponent : MonoBehaviour {
public NanoBrainObj brain;
public NanoBrainObj defaultBrain;
private NanoBrainObj brainInstance;
public Nucleus root {
get {
return brain.root;
return brainInstance.root;
}
}
public Perception perception {
public NanoBrainObj brain {
get {
return brain.perception;
if (brainInstance == null && defaultBrain != null) {
brainInstance = Instantiate(defaultBrain);
brainInstance.name = defaultBrain.name + " (Instance)";
}
return brainInstance;
}
}
}

View File

@ -9,18 +9,19 @@ public class NanoBrainObj : ScriptableObject, ISerializationCallbackReceiver {
public Color color = Color.white;
public Texture2D texture;
public List<Nucleus> nuclei = new();
public List<Neuroid> nuclei = new();
public List<Perceptoid> perceptei = new();
// This is probably always the first element in the nuclei list...
[System.NonSerialized]
public Nucleus root;
public int rootId;
public Perception perception;
// public Perception perception;
public NanoBrainObj() {
this.root = new(this, "Root");
this.perception = new Perception(this);
this.root = new Neuroid(this, "Root");
// this.perception = new Perception(this);
}
public Neuroid AddNeuron(string name) {
@ -40,10 +41,48 @@ public class NanoBrainObj : ScriptableObject, ISerializationCallbackReceiver {
this.rootId = root.id;
}
public void OnAfterDeserialize() {
foreach (Nucleus nucleus in nuclei) {
if (this.rootId == nucleus.id)
this.root = nucleus;
nucleus.Rebuild(this);
try {
foreach (Nucleus nucleus in this.nuclei.ToArray()) {
if (this.rootId == nucleus.id)
this.root = nucleus;
nucleus.Rebuild(this);
}
// List<Nucleus> rebuildNuclei = new();
// foreach (Nucleus nucleus in this.nuclei.ToArray()) {
// rebuildNuclei.Add(Nucleus.RebuildType(this, nucleus));
// }
// this.nuclei = rebuildNuclei;
foreach (Perceptoid perceptoid in this.perceptei.ToArray()) {
perceptoid.Rebuild(this);
}
}
catch (System.Exception) { }
this.GarbageCollection();
}
public void GarbageCollection() {
HashSet<Nucleus> visitedNuclei = new();
MarkNuclei(visitedNuclei, this.root);
Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei");
this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false);
this.perceptei.RemoveAll(perceptoid => visitedNuclei.Contains(perceptoid) == false);
}
public void MarkNuclei(HashSet<Nucleus> visitedNuclei, Nucleus nucleus) {
if (nucleus is null)
return;
visitedNuclei.Add(nucleus);
if (nucleus.synapses != null) {
HashSet<Synapse> visitedSynapses = new();
foreach (Synapse synapse in nucleus.synapses) {
if (synapse != null && synapse.nucleus != null) {
visitedSynapses.Add(synapse);
MarkNuclei(visitedNuclei, synapse.nucleus);
}
}
nucleus.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false);
}
}
}

View File

@ -393,7 +393,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: ec888ca5333d45a438f9f417fa5ce135, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::SwarmSpawn
count: 1
count: 3
boidPrefab: {fileID: 8702527964058765413, guid: f9c706268554ce449a8773675b2864b8, type: 3}
spawnAreaSize: {x: 0.5, y: 0.5, z: 0.5}
minDelay: 0.05

View File

@ -1,60 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 36081359186edfec998d891a1feeb17b, type: 3}
m_Name: New Nano Brain Obj
m_EditorClassIdentifier: Assembly-CSharp::NanoBrainObj
title:
count: -26
color: {r: 1, g: 1, b: 1, a: 1}
texture: {fileID: 0}
nuclei:
- id: 257807948
_name: Root
synapses:
- nucleusId: -651011940
weight: 1
- nucleusId: -1689048724
weight: 1
receivers: []
- id: -1868865374
_name: Perception
synapses: []
receivers: []
- id: -651011940
_name: Neuron 1
synapses: []
receivers:
- nucleusId: 257807948
- id: -1689048724
_name: New neuron
synapses: []
receivers:
- nucleusId: 257807948
- id: -152927560
_name: 'Boundary: position'
synapses: []
receivers:
- nucleusId: -1462684836
- id: -1462684836
_name: 'Boundary: velocity'
synapses:
- nucleusId: -152927560
weight: 1
receivers: []
rootId: 257807948
perception:
id: 1565766940
_name: Perception
synapses: []
receivers: []
positionReceivers: []
velocityReceivers: []

View File

@ -176,4 +176,4 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 92f34a5e4027a1dc39efd8ce63cf6aba, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::NanoBrainComponent
brain: {fileID: 11400000, guid: af8d90b8b4b9dcad7837130c4143d91c, type: 2}
defaultBrain: {fileID: 11400000, guid: fc1a4800a8c531eb4855b436bc9084ae, type: 2}

View File

@ -17,86 +17,36 @@ MonoBehaviour:
color: {r: 1, g: 1, b: 1, a: 1}
texture: {fileID: 0}
nuclei:
- id: -1753291412
- id: -1707533328
_name: Root
synapses:
- nucleusId: 237822944
- nucleusId: -112538112
weight: 1
receivers: []
- id: 472852910
_name: Perception
synapses: []
receivers: []
- id: 237822944
nucleusType:
average: 0
inverse: 0
exponent: 1
- id: -112538112
_name: Avoidance
synapses: []
receivers:
- nucleusId: -1753291412
- id: 983561152
_name: 'Boundary: position'
synapses: []
receivers:
- nucleusId: -1818801134
- id: -1818801134
_name: 'Boundary: velocity'
synapses:
- nucleusId: 983561152
weight: 1
receivers: []
- id: 1386590800
_name: 'Boundary: position'
- nucleusId: 407735232
weight: -1
receivers:
- nucleusId: -1707533328
nucleusType:
average: 0
inverse: 0
exponent: 1
perceptei:
- id: 407735232
_name: Boundary
synapses: []
receivers:
- nucleusId: -1415771486
- nucleusId: 237822944
- id: -1415771486
_name: 'Boundary: velocity'
synapses:
- nucleusId: 1386590800
weight: 1
receivers: []
- id: -213085248
_name: 'Boundary: position'
synapses: []
receivers:
- nucleusId: 1279519762
- nucleusId: 237822944
- id: 1279519762
_name: 'Boundary: velocity'
synapses:
- nucleusId: -213085248
weight: 1
receivers: []
- id: 1783940116
_name: 'Boundary: position'
synapses: []
receivers:
- nucleusId: -1018422170
- nucleusId: 237822944
- id: -1018422170
_name: 'Boundary: velocity'
synapses:
- nucleusId: 1783940116
weight: 1
receivers: []
rootId: -1753291412
perception:
id: 2139386530
_name: Perception
synapses: []
receivers: []
positionReceivers:
- thingType: 0
neuroid:
id: 237822944
_name: Avoidance
synapses:
- nucleusId: 1386590800
weight: 1
- nucleusId: -213085248
weight: 1
- nucleusId: 1783940116
weight: 1
receivers:
- nucleusId: -1753291412
velocityReceivers: []
- nucleusId: -112538112
nucleusType: Perceptoid
average: 0
inverse: 0
exponent: 1
thingType: 1
rootId: -1707533328

View File

@ -13,32 +13,21 @@ public class Boid : MonoBehaviour {
private Bounds innerBounds;
public NanoBrainComponent nanoBrain;
// public NanoBrain neuroidNet;
// public Perception perception;
// public Nucleus behaviour;
// public Neuroid totalForce;
public Receptor boundaryReceptor;
public Receptor boidReceptor;
public int id;
void Awake() {
nanoBrain = GetComponent<NanoBrainComponent>();
this.id = this.GetInstanceID();
nanoBrain = GetComponent<NanoBrainComponent>();
boundaryReceptor = Perceptoid.GetReceptor(nanoBrain.brain, BoundaryType);
boidReceptor = Perceptoid.GetReceptor(nanoBrain.brain, BoidType);
sc = FindFirstObjectByType<SwarmControl>();
innerBounds = new(sc.transform.position, sc.spaceSize - 2 * sc.boundaryWidth);
// neuroidNet = GetComponent<NanoBrain>();
// perception = new Perception(neuroidNet);
// //behaviour = new Roaming(neuroidNet, perception, sc);
// behaviour = new Swarming(neuroidNet, perception, sc);
// totalForce = new(neuroidNet, "Total");
// behaviour.AddReceiver(totalForce);
}
void Update() {
@ -52,8 +41,9 @@ public class Boid : MonoBehaviour {
Vector3 localPosition = this.transform.InverseTransformPoint(neighbour.transform.position);
//Debug.DrawRay(this.transform.position, this.transform.TransformDirection(localPosition), Color.magenta);
int thingId = neighbour.GetInstanceID();
nanoBrain.perception.ProcessStimulus(thingId, BoidType, localPosition, neighbour.gameObject.name);
//int thingId = neighbour.GetInstanceID();
// nanoBrain.perception.ProcessStimulus(thingId, BoidType, localPosition, neighbour.gameObject.name);
boidReceptor.position = localPosition;
}
}
@ -62,10 +52,9 @@ public class Boid : MonoBehaviour {
Vector3 pointOnBounds = innerBounds.ClosestPoint(point);
Vector3 desiredWorldSpace = (pointOnBounds - point).normalized * sc.speed;
Vector3 desiredLocalSpace = -this.transform.InverseTransformPoint(desiredWorldSpace);
nanoBrain.perception.ProcessStimulus(777, BoundaryType, desiredLocalSpace, "Boundary");
boundaryReceptor.position = desiredLocalSpace;
}
//Vector3 worldForce = this.transform.TransformDirection(behaviour.outputValue);
Vector3 worldForce = this.transform.TransformDirection(nanoBrain.root.outputValue);
this.velocity = (1 - sc.inertia) * (worldForce * Time.deltaTime) + sc.inertia * velocity;
@ -82,7 +71,6 @@ public class Boid : MonoBehaviour {
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 2f); // Adjust the speed of rotation
}
//neuroidNet.UpdateNeurons();
nanoBrain.brain.UpdateNuclei();
}

View File

@ -3,7 +3,7 @@ public class Roaming : Nucleus {
public Neuroid output;
public Roaming(NanoBrainObj brain, Perception perception, SwarmControl sc) : base(null, "Roaming nucleus") {
public Roaming(NanoBrainObj brain, Perception perception, SwarmControl sc) : base("Roaming nucleus") {
avoidance = new(brain, "Avoidance") { inverse = true };
perception.SendPositions(avoidance, Boid.BoundaryType);

View File

@ -10,7 +10,7 @@ public class Swarming : Nucleus {
public override Vector3 outputValue { get => output.outputValue; set => output.outputValue = value; }
public Swarming(NanoBrainObj brain, Perception perception, SwarmControl sc) : base(null, "Swarming Nucleus") {
public Swarming(NanoBrainObj brain, Perception perception, SwarmControl sc) : base("Swarming Nucleus") {
this.cohesion = new(brain, "Cohesion") { inverse = false };
perception.SendPositions(this.cohesion, Boid.BoidType);

View File

@ -0,0 +1,88 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 36081359186edfec998d891a1feeb17b, type: 3}
m_Name: SwarmingBrain
m_EditorClassIdentifier: Assembly-CSharp::NanoBrainObj
title:
count: 0
color: {r: 1, g: 1, b: 1, a: 1}
texture: {fileID: 0}
nuclei:
- id: -1707533328
_name: Root
synapses:
- nucleusId: -112538112
weight: 1
- nucleusId: 1938577052
weight: 1
receivers: []
nucleusType:
average: 0
inverse: 0
exponent: 1
- id: -112538112
_name: Avoidance
synapses:
- nucleusId: 407735232
weight: -1
receivers:
- nucleusId: -1707533328
nucleusType:
average: 0
inverse: 0
exponent: 1
- id: 1938577052
_name: Cohesion
synapses:
- nucleusId: -1408496896
weight: 1
- nucleusId: -133566816
weight: 1
receivers:
- nucleusId: -1707533328
nucleusType:
average: 0
inverse: 0
exponent: 1
perceptei:
- id: 407735232
_name: Boundary
synapses: []
receivers:
- nucleusId: -112538112
nucleusType: Perceptoid
average: 0
inverse: 0
exponent: 1
thingType: 1
- id: -1408496896
_name: Boid1
synapses: []
receivers:
- nucleusId: 1938577052
nucleusType: Perceptoid
average: 0
inverse: 0
exponent: 1
thingType: 2
- id: -133566816
_name: Boid2
synapses: []
receivers:
- nucleusId: 27651644
- nucleusId: 1938577052
nucleusType: Perceptoid
average: 0
inverse: 0
exponent: 1
thingType: 2
rootId: -1707533328

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 55099766f6f09071ab4e8c89b02fa302
guid: fc1a4800a8c531eb4855b436bc9084ae
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000