First setup

This commit is contained in:
Pascal Serrarens 2026-01-21 15:17:25 +01:00
parent 14436cbfba
commit 4b72e6c712
13 changed files with 268 additions and 114 deletions

View File

@ -10,6 +10,7 @@ public class Cluster : ScriptableObject, INucleus {
[SerializeReference]
public List<IReceptor> nuclei = new();
public List<Cluster> subClusters = new();
public INucleus output => this.nuclei[0] as INucleus;
@ -33,6 +34,18 @@ public class Cluster : ScriptableObject, INucleus {
public NucleusArray array { get; set; }
public List<INucleus> receivers {
get { return this.output.receivers; }
set { this.output.receivers = value; }
}
public List<Cluster> clusterReceivers {
get { return this.output.clusterReceivers; }
set { this.output.clusterReceivers = clusterReceivers; }
}
public IEnumerable<INucleus> allReceivers {
get => output.allReceivers;
}
public INucleus Clone() {
Cluster clone = CreateInstance<Cluster>();
// Lots to add here...
@ -48,19 +61,27 @@ public class Cluster : ScriptableObject, INucleus {
new Neuron(this, "Output"); // Every cluster should have at least 1 neuron
}
public void AddReceiver(INucleus receiver) {
output.AddReceiver(receiver);
public void AddReceiver(INucleus receivingNucleus) {
//output.AddReceiver(receiver);
this.output.receivers.Add(receivingNucleus);
receivingNucleus.AddClusterSynapse(this);
}
public void AddClusterReceiver(Cluster clusterReceiver) {
this.output.clusterReceivers.Add(clusterReceiver);
clusterReceiver.AddClusterSynapse(this);
}
public void RemoveReceiver(INucleus receiver) {
output.RemoveReceiver(receiver);
}
public List<INucleus> receivers {
get => output.receivers;
}
public Synapse AddSynapse(IReceptor sender) {
Synapse synapse = new(sender, 1.0f);
Synapse synapse = new(sender);
synapses.Add(synapse);
return synapse;
}
public Synapse AddClusterSynapse(Cluster sender) {
Synapse synapse = new(sender);
synapses.Add(synapse);
return synapse;
}
@ -89,9 +110,9 @@ public class Cluster : ScriptableObject, INucleus {
}
nucleus.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false);
}
if (nucleus.receivers != null) {
if (nucleus.allReceivers != null) {
HashSet<INucleus> visitedReceivers = new();
foreach (INucleus receiver in nucleus.receivers) {
foreach (INucleus receiver in nucleus.allReceivers) {
if (receiver != null && receiver != null) {
visitedReceivers.Add(receiver);
visitedNuclei.Add(receiver);

View File

@ -1,7 +1,4 @@
using System.Collections.Generic;
using UnityEngine;
using Unity.Burst;
using Unity.Collections;
using Unity.Mathematics;
public interface INucleus : IReceptor {
@ -14,6 +11,7 @@ public interface INucleus : IReceptor {
// Senders
public List<Synapse> synapses { get; }
public Synapse AddSynapse(IReceptor sender);
public Synapse AddClusterSynapse(Cluster clusterSender);
public NucleusArray array { get; set; }
@ -35,8 +33,12 @@ public interface IReceptor {
public string name { get; set; }
// Receivers
public List<INucleus> receivers { get; }
public List<INucleus> receivers { get; set; }
public List<Cluster> clusterReceivers { get; set; }
public IEnumerable<INucleus> allReceivers { get; }
public void AddReceiver(INucleus receiver);
public void AddClusterReceiver(Cluster clusterReceiver);
public void RemoveReceiver(INucleus receiverNucleus);
#endregion static

View File

@ -0,0 +1,60 @@
%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: 60a957541c24c57e78018c202ebb1d9b, type: 3}
m_Name: Identity
m_EditorClassIdentifier: Assembly-CSharp::Cluster
nuclei:
- rid: 2243601242842202169
references:
version: 2
RefIds:
- rid: 2243601242842202169
type: {class: Neuron, ns: , asm: Assembly-CSharp}
data:
_name: Output
_synapses: []
_receivers: []
_array:
rid: 2243601242842202170
_curvePreset: 0
curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 1
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 1000
value: 1000
inSlope: 1
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
curveMax: 1
average: 0
- rid: 2243601242842202170
type: {class: NucleusArray, ns: , asm: Assembly-CSharp}
data:
nuclei:
- rid: 2243601242842202169
name: Output

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5f4d2ea0d0115b3549f8e9aa5e669163
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
@ -20,7 +21,25 @@ public class Neuron : INucleus {
[SerializeReference]
private List<INucleus> _receivers = new();
public List<INucleus> receivers => _receivers;
public List<INucleus> receivers {
get { return _receivers; }
set { _receivers = value; }
}
private List<Cluster> _clusterReceivers = new();
public List<Cluster> clusterReceivers {
get { return _clusterReceivers; }
set { _clusterReceivers = value; }
}
public IEnumerable<INucleus> allReceivers { //=> _receivers.Concat(_clusterReceivers);
get {
if (_receivers == null)
return _clusterReceivers;
else if (_clusterReceivers == null)
return _receivers;
else
return _receivers.Concat(_clusterReceivers);
}
}
[SerializeReference]
private NucleusArray _array;
@ -133,28 +152,32 @@ public class Neuron : INucleus {
Synapse clonedSynapse = clone.AddSynapse(synapse.nucleus);
clonedSynapse.weight = synapse.weight;
}
foreach (INucleus receiver in this.receivers) {
foreach (INucleus receiver in this.allReceivers) {
clone.AddReceiver(receiver);
}
return clone;
}
public virtual void AddReceiver(INucleus receivingNucleus) {
this.receivers.Add(receivingNucleus);
this._receivers.Add(receivingNucleus);
receivingNucleus.AddSynapse(this);
}
public void AddClusterReceiver(Cluster receivingCluster) {
this._clusterReceivers.Add(receivingCluster);
receivingCluster.AddSynapse(this);
}
public void RemoveReceiver(INucleus receiverNucleus) {
this.receivers.RemoveAll(receiver => receiver == receiverNucleus);
this._receivers.RemoveAll(receiver => receiver == receiverNucleus);
receiverNucleus.synapses.RemoveAll(synapse => synapse.nucleus == this);
}
public static void Delete(INucleus nucleus) {
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.nucleus is Neuron synapse_nucleus) {
if (synapse_nucleus.receivers.Count > 1) {
if (synapse_nucleus._receivers.Count > 1) {
// there is another nucleus feeding into this input nucleus
synapse_nucleus.receivers.RemoveAll(r => r == nucleus);
synapse_nucleus._receivers.RemoveAll(r => r == nucleus);
}
else {
// No other links, delete it.
@ -162,7 +185,7 @@ public class Neuron : INucleus {
}
}
}
foreach (INucleus receiver in nucleus.receivers) {
foreach (INucleus receiver in nucleus.allReceivers) {
if (receiver != null && receiver.synapses != null)
receiver.synapses.RemoveAll(s => s.nucleus == nucleus);
}
@ -174,7 +197,12 @@ public class Neuron : INucleus {
}
public Synapse AddSynapse(IReceptor sendingNucleus) {
Synapse synapse = new(sendingNucleus, 1.0f);
Synapse synapse = new(sendingNucleus);
this.synapses.Add(synapse);
return synapse;
}
public Synapse AddClusterSynapse(Cluster sendingCluster) {
Synapse synapse = new(sendingCluster);
this.synapses.Add(synapse);
return synapse;
}
@ -222,7 +250,7 @@ public class Neuron : INucleus {
// }
this.outputValue = result;
foreach (INucleus receiver in this.receivers)
foreach (INucleus receiver in this.allReceivers)
receiver.UpdateState();
}

View File

@ -5,6 +5,7 @@ using UnityEngine;
public class NucleusArray {
[SerializeReference]
public INucleus[] nuclei;
public Cluster[] clusters;
public string name;
public NucleusArray(INucleus nucleus) {

View File

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
@ -25,17 +26,30 @@ public class Receptor : IReceptor {
[SerializeReference]
private List<INucleus> _receivers = new();
public List<INucleus> receivers => _receivers;
public List<INucleus> receivers {
get { return _receivers; }
set { _receivers = value; }
}
private List<Cluster> _clusterReceivers = new();
public List<Cluster> clusterReceivers {
get { return _clusterReceivers; }
set { _clusterReceivers = value; }
}
public IEnumerable<INucleus> allReceivers => _receivers.Concat(_clusterReceivers);
protected int[] thingIds; // every receiver can handle a thing with this id
public virtual void AddReceiver(INucleus receivingNucleus) {
this.receivers.Add(receivingNucleus);
this._receivers.Add(receivingNucleus);
receivingNucleus.AddSynapse(this);
}
public void AddClusterReceiver(Cluster receivingCluster) {
this._clusterReceivers.Add(receivingCluster);
receivingCluster.AddSynapse(this);
}
public void RemoveReceiver(INucleus receiverNucleus) {
this.receivers.RemoveAll(receiver => receiver == receiverNucleus);
this._receivers.RemoveAll(receiver => receiver == receiverNucleus);
receiverNucleus.synapses.RemoveAll(synapse => synapse.nucleus == this);
}
@ -88,7 +102,7 @@ public class Receptor : IReceptor {
receptor.AddReceiver(nucleus);
}
}
if (receptor.receivers.Count == 0)
if (receptor._receivers.Count == 0)
return null;
else
return receptor;
@ -97,12 +111,12 @@ public class Receptor : IReceptor {
public virtual void ProcessStimulus(int thingId, Vector3 newLocalPositionVector, string thingName = null) {
this.localPosition = newLocalPositionVector;
thingIds ??= new int[this.receivers.Count];
thingIds ??= new int[this._receivers.Count];
int receiverIx = 0;
INucleus selectedReceiver = null;
int selectedReceiverIx = 0;
foreach (INucleus receiver in this.receivers) {
foreach (INucleus receiver in this.allReceivers) {
// selectedReceiver = receiver;
// receiverIx++;

View File

@ -4,12 +4,16 @@ using UnityEditor;
[Serializable]
public class Synapse {
//[NonSerialized]
[SerializeReference]
public IReceptor nucleus;
// Support access to cluster of basic nucleus
public IReceptor nucleus => clusterNucleus != null ? clusterNucleus : basicNucleus;
[SerializeReference]
private IReceptor basicNucleus;
// The Cluster is a ScriptableObject and can therefore not be serialized using [SerializeReference]
private Cluster clusterNucleus;
public Cluster cluster;
//public int nucleusId;
public float weight;
public enum CurvePresets {
@ -19,55 +23,16 @@ public class Synapse {
Reciprocal,
Custom
}
// public CurvePresets curvePreset;
// public AnimationCurve curve;
public float curveMax = 1.0f;
public Synapse(IReceptor nucleus, float weight) {
this.nucleus = nucleus;
//this.nucleusId = nucleus.id;
public Synapse(IReceptor nucleus, float weight = 1.0f) {
this.basicNucleus = nucleus;
this.weight = weight;
}
public Synapse(Cluster cluster, float weight = 1.0f) {
this.clusterNucleus = cluster;
this.weight = weight;
}
// public void Rebuild(NanoBrain brain) {
// // if (brain == null) {
// // return;
// // }
// // foreach (Nucleus nucleus in brain.nuclei) {
// // if (nucleus.id == this.nucleusId) {
// // this.nucleus = nucleus;
// // 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}");
// }
// 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;

View File

@ -3,15 +3,13 @@ using UnityEngine;
using System;
using System.Linq;
public class BrainPickerWindow : EditorWindow
{
public class BrainPickerWindow : EditorWindow {
private Vector2 scroll;
private Cluster[] items = new Cluster[0];
private Action<Cluster> onPicked;
private string search = "";
public static void ShowPicker(Action<Cluster> onPicked, string title = "Select NanoBrain")
{
public static void ShowPicker(Action<Cluster> onPicked, string title = "Select Cluster") {
var w = CreateInstance<BrainPickerWindow>();
w.titleContent = new GUIContent(title);
w.minSize = new Vector2(360, 320);
@ -22,9 +20,8 @@ public class BrainPickerWindow : EditorWindow
private void OnEnable() => RefreshList();
private void RefreshList()
{
var guids = AssetDatabase.FindAssets("t:NanoBrain");
private void RefreshList() {
var guids = AssetDatabase.FindAssets("t:Cluster");
items = guids
.Select(g => AssetDatabase.LoadAssetAtPath<Cluster>(AssetDatabase.GUIDToAssetPath(g)))
.Where(b => b != null)
@ -32,11 +29,10 @@ public class BrainPickerWindow : EditorWindow
.ToArray();
}
private void OnGUI()
{
private void OnGUI() {
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Choose NanoBrain:", EditorStyles.boldLabel);
EditorGUILayout.LabelField("Choose Cluster:", EditorStyles.boldLabel);
if (GUILayout.Button("Refresh", GUILayout.Width(80))) RefreshList();
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
@ -46,15 +42,13 @@ public class BrainPickerWindow : EditorWindow
EditorGUILayout.Space();
scroll = EditorGUILayout.BeginScrollView(scroll);
foreach (var it in items)
{
foreach (var it in items) {
if (!string.IsNullOrEmpty(search) && it.name.IndexOf(search, StringComparison.OrdinalIgnoreCase) < 0)
continue;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(EditorGUIUtility.ObjectContent(it, typeof(Cluster)), GUILayout.Height(20));
if (GUILayout.Button("Select", GUILayout.Width(70)))
{
if (GUILayout.Button("Select", GUILayout.Width(70))) {
onPicked?.Invoke(it);
Close();
return;

View File

@ -156,8 +156,8 @@ public class ClusterInspector : Editor {
return;
NeuroidLayer currentLayer = new() { ix = layerIx };
if (selectedNucleus.receivers != null) {
foreach (INucleus receiver in selectedNucleus.receivers) {
if (selectedNucleus.allReceivers != null) {
foreach (INucleus receiver in selectedNucleus.allReceivers) {
INucleus outputNeuroid = receiver;
if (outputNeuroid != null) {
AddToLayer(currentLayer, outputNeuroid);
@ -201,19 +201,6 @@ public class ClusterInspector : Editor {
}
// void OnMouseDown(MouseDownEvent e) {
// if (e.button == 2) { draggingCanvas = true; lastMouse = e.mousePosition; e.StopPropagation(); }
// }
// void OnMouseMove(MouseMoveEvent e) {
// if (draggingCanvas) {
// var delta = e.mousePosition - lastMouse;
// pan += delta;
// //content.style.left = pan.x;
// //content.style.top = pan.y;
// lastMouse = e.mousePosition;
// }
// }
// void OnMouseUp(MouseUpEvent e) { if (e.button == 2) draggingCanvas = false; }
public void OnIMGUI() {
if (currentNucleus == null)
@ -285,12 +272,12 @@ public class ClusterInspector : Editor {
}
private void DrawReceivers(INucleus nucleus, Vector3 parentPos, float size) {
int nodeCount = nucleus.receivers.Count;
int nodeCount = nucleus.allReceivers.Count();
// Determine the maximum value in this layer
// This is used to 'scale' the output value colors of the nuclei
float maxValue = 0;
foreach (INucleus receiver in nucleus.receivers) {
foreach (INucleus receiver in nucleus.allReceivers) {
if (receiver is Neuron neuroid) {
float value = length(neuroid.outputValue);
if (value > maxValue)
@ -303,7 +290,7 @@ public class ClusterInspector : Editor {
float margin = 10 + spacing / 2;
int row = 0;
foreach (INucleus receiver in nucleus.receivers) {
foreach (INucleus receiver in nucleus.allReceivers) {
INucleus receiverNucleus = receiver;
if (receiverNucleus == null)
continue;
@ -414,9 +401,14 @@ public class ClusterInspector : Editor {
}
else {
style.alignment = TextAnchor.UpperCenter;
Vector3 labelPos = position - Vector3.down * (size + 0.2f); // below disc along up axis
Vector3 labelPos = position - Vector3.down * (size + 10f); // below disc along up axis
Handles.Label(labelPos, nucleus.name, style);
}
if (nucleus is Cluster cluster) {
Handles.color = Color.white;
Handles.DrawWireDisc(position, Vector3.forward, size + 10);
}
}
else {
style.alignment = TextAnchor.UpperCenter;
@ -592,7 +584,7 @@ public class ClusterInspector : Editor {
return;
if (nucleus.cluster != null)
this.currentNucleus = nucleus.cluster.output;
foreach (INucleus receiver in nucleus.receivers) {
foreach (INucleus receiver in nucleus.allReceivers) {
if (receiver != null) {
this.currentNucleus = receiver;
break;
@ -606,9 +598,9 @@ public class ClusterInspector : Editor {
BrainPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster");
}
private void OnClusterPicked(INucleus nucleus, Cluster brain) {
Cluster brainInstance = Instantiate(brain);
brainInstance.AddReceiver(nucleus);
private void OnClusterPicked(INucleus nucleus, Cluster cluster) {
Cluster clusterInstance = Instantiate(cluster);
clusterInstance.AddReceiver(nucleus);
}
// Connect to another nucleus in the same cluster

View File

@ -0,0 +1,62 @@
%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: 60a957541c24c57e78018c202ebb1d9b, type: 3}
m_Name: New Cluster
m_EditorClassIdentifier: Assembly-CSharp::Cluster
nuclei:
- rid: 2243601242842202217
subClusters: []
references:
version: 2
RefIds:
- rid: 2243601242842202217
type: {class: Neuron, ns: , asm: Assembly-CSharp}
data:
_name: Output
_synapses: []
_receivers: []
_array:
rid: 2243601242842202218
_curvePreset: 0
curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 1
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 1000
value: 1000
inSlope: 1
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
curveMax: 1
average: 0
- rid: 2243601242842202218
type: {class: NucleusArray, ns: , asm: Assembly-CSharp}
data:
nuclei:
- rid: 2243601242842202217
clusters: []
name: Output

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4160a4557d16d7c03833982ab779d28e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -32,7 +32,6 @@ public class Boid : MonoBehaviour {
// boidVelocityReceptor = Receptor.GetReceptor(nanoBrain.brain, BoidVelocityType);
boundaryReceptor = Receptor.CreateReceptor(nanoBrain.brain, "Avoidance");
boundaryReceptor.name = "Boundary";
boundaryReceptor.receivers[0].synapses[0].weight = -1;
boidReceptor = Receptor.CreateReceptor(nanoBrain.brain, "Boid");
boidVelocityReceptor = Receptor.CreateReceptor(nanoBrain.brain, "Alignment");