Compare commits

..

61 Commits

Author SHA1 Message Date
133804a154 Cleanup 2026-05-26 09:02:20 +02:00
f8a6f579ea GetNeuron/Cluster instead of GetNucleus 2026-05-22 17:15:18 +02:00
923a5fafe3 Removed Neuron_Drawer and curves 2026-05-22 17:02:34 +02:00
94df545222 Let go food only at home 2026-05-22 15:21:58 +02:00
62bacfb1c0 Some editor performance improvements 2026-05-22 15:01:07 +02:00
ffa1581f54 Fix clusterview 2026-05-22 13:28:25 +02:00
5584123c20 Fix sleeping issue 2026-05-20 17:39:14 +02:00
57967e3409 Automatic recreate cluster instance 2026-05-20 17:14:38 +02:00
92bfb29ed0 multicluster visualization 2026-05-20 15:08:42 +02:00
a87118fc23 Cluster instance per prefab instance 2026-05-20 09:53:48 +02:00
76023718cb Compatibility with latest nanobrain 2026-05-19 17:17:13 +02:00
1918faf280 Merge commit 'ac9e964c1dfe99fbd8da63e8e03339c1e7f09fc9' 2026-05-19 16:50:24 +02:00
40f8da92ef Merge commit '06084b7c6d52bc32a0812399ca1b97e93df08531' 2026-05-19 16:47:13 +02:00
e23389d072 Fixes following nanobrain update 2026-05-07 15:33:45 +02:00
fa8cd1c728 Fixes following nanobrain update 2026-05-07 15:33:45 +02:00
35a8c07e8a Merge commit '4363079d43cb47b469fd2c7de60bbe25e541558c' 2026-05-07 15:28:23 +02:00
26ef665634 Merge commit '4363079d43cb47b469fd2c7de60bbe25e541558c' 2026-05-07 15:28:23 +02:00
4363079d43 Squashed 'NanoBrain/' changes from 7ef8e42..ec3b1d4
ec3b1d4 Completed cluster documentation
348fee3 Update .gitea/workflows/copy_documentation.yml
911e52f Update .gitea/workflows/copy_documentation.yml
d472790 Update .gitea/workflows/copy_documentation.yml
b87f40f Trying to get the workflow running...10
2b0db4f Trying to get the workflow running...9
927fd6d Trying to get the workflow running...8
176f399 Trying to get the workflow running...7
3c841c7 Trying to get the workflow running...6
5c798c2 Trying to get the workflow running...5
30b25a1 Trying to get the workflow running...4
5edf019 Trying to get the workflow running...3
587cf82 Trying to get the workflow running...2
a1d3aa7 Trying to get the workflow running...1
97ec277 Removed LinearAlgebra, first setup webserver copy workflow
5827396 Fixed documentation links
ce19335 Added Documentation
da370bb Improvements
32b5885 Multi smell works
33ea14b Single smell works
a651ec6 Add neuron property drawer
baa7def Pheromones WIP
551b4d9 Improve ant walking speed
7187f61 Ant is walking again
c78722a Make it work again
2ff550c Removed clusterPrefab property
2ef67fe Cleanup and fix connect neuron
a9a0072 Merge commit 'dd326823a8256f3ddb808e071d98c4aede72e410'
22ee17c Insect rig improvements
b6a3bc1 Added insect body parts
517e738 Merge commit '4ae9a15fc61f386b96ce0f7b440780f562d7dc68'
033ddf4 Merge commit '05fd588f9bc41d84113d410a2ca992f1a2ee66e0'
ef700c0 Merge commit '3f8716794ad9d685cfb9ed9dd230eb31cd8df10f'
7d5e157 Added NanoBrain namespace
f138201 Merge commit '611055cdcd58b01f2f19991ad35eb8fe8e573ebb'
1c4d361 Merge commit '9fcbaa5bf84f91680d24b56dbf114bcb97de4aee'
0f83945 Added NanoBrain subtree
6f398ad Merge commit '8e87e4ea77308b51c3691bdad96e7f9707952821' as 'NanoBrain'
587f104 Move out non-subtree NanoBrain
fc581a0 cleanup & documentation
837c5ce WIP Physics based walking
63486d1 The ant does it ant things!
ce8e476 Added sample assets...
88d5eb5 Placing home pheromones
481829c Ensure model follows target in editor
018c99d Any walks
e709ea4 Steps to get it working
c1dcc83 Initial Ant setup
af2fa77 Merge commit '04ca8dda0793476a59fc06f1958453730a99c105' as 'NanoBrain'
04ca8dd Squashed 'NanoBrain/' content from commit b3423b9
d9ba98d WIP: Initial scripts
2219e98 Initial commit

git-subtree-dir: NanoBrain
git-subtree-split: ec3b1d46ab2b9f332a8ae63589b09c3fb6fb1b1a
2026-05-07 15:25:20 +02:00
6c30009564 Improvements 2026-05-06 17:51:14 +02:00
09a75aa22e Multi smell works 2026-05-06 17:39:56 +02:00
b1121eed87 Single smell works 2026-05-06 12:10:26 +02:00
67beb5e486 Add neuron property drawer 2026-05-06 09:48:21 +02:00
7fb23e9b89 Pheromones WIP 2026-05-05 17:37:59 +02:00
10d99bce87 Improve ant walking speed 2026-05-05 14:05:51 +02:00
fdae20b6b4 Ant is walking again 2026-05-05 12:39:04 +02:00
3f0ed444bb Make it work again 2026-05-05 11:52:04 +02:00
d2ef26b6f8 Removed clusterPrefab property 2026-05-05 11:14:35 +02:00
79376c30ea Cleanup and fix connect neuron 2026-05-05 10:59:24 +02:00
ec2a6b7ae9 Merge commit 'dd326823a8256f3ddb808e071d98c4aede72e410' 2026-05-05 08:49:23 +02:00
dd326823a8 Squashed 'NanoBrain/' changes from cc9a845..7ef8e42
7ef8e42 multi-cluster calculation fix
af0ba68 Viewer for multi-clusters
0401090 Created runtime sibling, but not the synapses yet
36f876c draw external receivers only once
ffcf420 Fix cloned external connections
b2bc92b Multiple players working-ish
335dae7 Fix deleting neuron without synapses
fc8caa8 Fix adding/removing cluster outputs
805b0f8 Fix broken outputpop references
ecab0b0 Fix neuron name editor

git-subtree-dir: NanoBrain
git-subtree-split: 7ef8e42e091cd5a46bf77dfbf9b1a3b3a4422752
2026-05-05 08:48:46 +02:00
36c414ae02 Insect rig improvements 2026-05-05 08:48:41 +02:00
9eda1cdf06 Added insect body parts 2026-04-23 17:39:51 +02:00
4cb6286f20 Merge commit '4ae9a15fc61f386b96ce0f7b440780f562d7dc68' 2026-04-23 15:24:00 +02:00
4ae9a15fc6 Squashed 'NanoBrain/' changes from 832d849..cc9a845
cc9a845 Fix sleeping for product combinator
e4ba7f8 Better cross-cluster monitoring
4f8a6ab Improved (but not fixed) cross-cluster monitoring
b12616b Fix neuron output visualisation
96439cc Visualize all outputs
d583e67 WIP cluster references/instance
04bab92 Fix links to multiple cluster neurons & cleanup
e17a249 Cross-cluster editor links
0ab2d21 Migrating and cleaning up
b6630ad First steps to using instanceCount for clusters
8801fa2 Cluster reimport fixes
befb69d full graph with collapsed clusters
1a1919f Fix expansion of clsuter arrays
c708f4d Improved clusterarray support
c2e4e1b Fix Cluster array extension
02047a4 Adde full graph scrollbar
471ed36 Completed full graph integration
830e3e7 Added full graph view mode
249e888 Improve full graph view
308a6a1 The Entities are battling
75d9d1c Cleanup
c8f0f0c Fix aging of neurons
e2e169c small fixes
619ced6 Removed the use of Receptors
19f9296 Simplifications
bc0a796 Integrated clusterarray in cluster
e40dd23 Fixed clusterViewer for clusterarrays
b0f4b41 Status quo adding clusterArrays
1fc75a8 Added ClusterArray
0023920 Cover seeking(-ish) behaviour
1c7b8e7 Added Tanh Activation
a99d40c BrainViewer added
db43655 Pew pew!
18ef4cd Merge commit '89017475984bbbf1899fb38846c5bb0e7775dedd' into NanoBrain

git-subtree-dir: NanoBrain
git-subtree-split: cc9a845b643ffb4a9abe4f7da787ac5c5b14dae8
2026-04-23 15:22:02 +02:00
983b1e1c40 Fix match targets to legs 2026-04-14 10:16:50 +02:00
ae035d4672 Fix Ant 2026-04-13 17:42:21 +02:00
7c1061ade5 Fixed issues with inverse root motion 2026-04-13 16:39:32 +02:00
b71115d823 Inverse Root Motion 2026-04-13 14:54:52 +02:00
38391181af Animation scaling step 1 2026-04-10 17:43:44 +02:00
742fc3f323 Better animations, 3DoF animator 2026-04-10 09:32:16 +02:00
0a66372c3f Extended docs 2026-04-09 17:36:19 +02:00
08a3a7da56 Trying to fix documentation links 2026-04-09 17:14:20 +02:00
0e31a37068 Improved README 2026-04-09 15:54:17 +02:00
f2884327cf Initial Doxygen documentation 2026-04-09 15:46:29 +02:00
7ced91909a Improved control 2026-04-09 12:53:00 +02:00
f0bb2a9fa4 Aligned foot animations 2026-04-09 12:10:15 +02:00
dce7835dfc Improved animations 2026-04-08 16:50:09 +02:00
6fab0a35cf Fix brain namespaces 2026-04-08 09:40:33 +02:00
05fd588f9b Squashed 'NanoBrain/' changes from 3eb4ab7..832d849
832d849 Added doxygen files

git-subtree-dir: NanoBrain
git-subtree-split: 832d8492398553db3cebb4b249812c0c9908abf5
2026-04-08 09:38:11 +02:00
f65dcf803b Merge commit '05fd588f9bc41d84113d410a2ca992f1a2ee66e0' 2026-04-08 09:38:11 +02:00
c5808c5fb9 Fix namespaces 2026-04-08 09:38:05 +02:00
3f8716794a Squashed 'NanoBrain/' changes from fbca658..3eb4ab7
3eb4ab7 Cleanup

git-subtree-dir: NanoBrain
git-subtree-split: 3eb4ab7993054a9ec75ba7cadede74bfb213e34f
2026-04-08 09:33:52 +02:00
c9ad9f22a2 Merge commit '3f8716794ad9d685cfb9ed9dd230eb31cd8df10f' 2026-04-08 09:33:52 +02:00
334a4418c0 Added NanoBrain namespace 2026-04-07 17:35:08 +02:00
8e441f86ff Merge commit '611055cdcd58b01f2f19991ad35eb8fe8e573ebb' 2026-04-07 17:33:21 +02:00
611055cdcd Squashed 'NanoBrain/' changes from bef7ee2..fbca658
fbca658 Reorganizing the package and added documentation

git-subtree-dir: NanoBrain
git-subtree-split: fbca658b5975ade4aa5c0ef1294dc12ead936495
2026-04-07 17:32:37 +02:00
3b842e7751 Merge commit '9fcbaa5bf84f91680d24b56dbf114bcb97de4aee' 2026-04-07 14:07:27 +02:00
9fcbaa5bf8 Squashed 'NanoBrain/' changes from b3423b9..bef7ee2
bef7ee2 Made Unity.Mathematics optional

git-subtree-dir: NanoBrain
git-subtree-split: bef7ee24e549963b5cabfb91ada9289bc6dddbe0
2026-04-07 14:07:27 +02:00
3c6f2c1f66 Added NanoBrain subtree 2026-04-07 09:13:31 +02:00
8e87e4ea77 Squashed 'NanoBrain/' content from commit b3423b9
git-subtree-dir: NanoBrain
git-subtree-split: b3423b99a752cdabbc4e7c51565fb54425481feb
2026-04-07 09:12:29 +02:00
628f20b103 Merge commit '8e87e4ea77308b51c3691bdad96e7f9707952821' as 'NanoBrain' 2026-04-07 09:12:29 +02:00
29 changed files with 532 additions and 1568 deletions

View File

@ -1,75 +0,0 @@
/*
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace NanoBrain.Unity {
[CustomEditor(typeof(Brain))]
public class Brain_Editor : Editor {
protected static VisualElement mainContainer;
protected static VisualElement inspectorContainer;
public Brain component;
private SerializedProperty brainProp;
public void OnEnable() {
component = target as Brain;
if (Application.isPlaying == false && serializedObject != null) {
string propertyName = nameof(Brain.brainPrefab);
brainProp = serializedObject.FindProperty(propertyName);
}
}
public override VisualElement CreateInspectorGUI() {
if (Application.isPlaying == false)
serializedObject.Update();
VisualElement root = new() {
style = {
paddingLeft = 0,
paddingRight = 0,
paddingTop = 0,
paddingBottom = 0
}
};
root.styleSheets.Add(Resources.Load<StyleSheet>("GraphStyles"));
PropertyField brainField = new(brainProp) {
label = "Cluster Prefab"
};
root.Add(brainField);
CreateViewer(root, component.brain, component.gameObject);
if (Application.isPlaying == false)
serializedObject.ApplyModifiedProperties();
return root;
}
public ClusterViewer.GraphView CreateViewer(VisualElement root, Cluster cluster, GameObject gameObject) {
VisualElement mainContainer = new() {
style = {
flexDirection = FlexDirection.Row,
minHeight = 450
}
};
ClusterViewer.GraphView graph = new(cluster);
graph.style.flexGrow = 1;
mainContainer.Add(graph);
root.Add(mainContainer);
graph.SetGraph(gameObject);
return graph;
}
}
}
*/

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: f05072314d39990639a2dbf99f322664
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -76,8 +76,15 @@ namespace NanoBrain.Unity {
margin = new RectOffset(10, 0, 4, 4) margin = new RectOffset(10, 0, 4, 4)
}; };
// Nucleus type // Nucleus type
string nucleusType = this.view.currentNucleus.GetType().Name; System.Type nucleusType = this.view.currentNucleus.GetType();
GUILayout.Label(nucleusType, headerStyle); if (nucleusType == typeof(Cluster)) {
Cluster cluster = this.view.currentNucleus as Cluster;
GUILayout.Label($"{cluster.prefab.name} {nucleusType.Name}", headerStyle);
}
else
GUILayout.Label(nucleusType.Name, headerStyle);
// string nucleusType = this.view.currentNucleus.GetType().Name;
// GUILayout.Label(nucleusType, headerStyle);
// Nucleus name // Nucleus name
string newName = EditorGUILayout.TextField(this.view.currentNucleus.name, boldTextFieldStyle); string newName = EditorGUILayout.TextField(this.view.currentNucleus.name, boldTextFieldStyle);
@ -304,10 +311,10 @@ namespace NanoBrain.Unity {
if (this.view.currentNucleus is not MemoryCell) { if (this.view.currentNucleus is not MemoryCell) {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Activation Curve", GUILayout.MinWidth(60)); EditorGUILayout.LabelField("Activation Curve", GUILayout.MinWidth(60));
if (neuron.curveMax > 0) // if (neuron.curveMax > 0)
EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, 0, 1, neuron.curveMax), GUILayout.Width(40)); // EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, 0, 1, neuron.curveMax), GUILayout.Width(40));
else // else
EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, neuron.curveMax, 1, -neuron.curveMax), GUILayout.Width(40)); // EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, neuron.curveMax, 1, -neuron.curveMax), GUILayout.Width(40));
Neuron.ActivationType newPreset = (Neuron.ActivationType)EditorGUILayout.EnumPopup(neuron.activator, GUILayout.MinWidth(50)); Neuron.ActivationType newPreset = (Neuron.ActivationType)EditorGUILayout.EnumPopup(neuron.activator, GUILayout.MinWidth(50));
anythingChanged |= newPreset != neuron.activator; anythingChanged |= newPreset != neuron.activator;
neuron.activator = newPreset; neuron.activator = newPreset;

View File

@ -1,129 +0,0 @@
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor;
using System;
using System.Reflection;
namespace NanoBrain.Unity {
[CustomPropertyDrawer(typeof(ClusterPrefab))]
class ClusterPrefab_Drawer : PropertyDrawer {
public static void Insepctor(SerializedObject serializedObject, string propertyName) {
EditorGUILayout.PropertyField(serializedObject.FindProperty(propertyName));
}
const float padding = 4f;
const float elementHeight = 64f; // height reserved for the VisualElement
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
float height = EditorGUIUtility.singleLineHeight + padding;
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetEntityId();
s_foldouts.TryGetValue(key, out bool isOpen);
if (property.objectReferenceValue != null && isOpen) {
height += padding + elementHeight;
height = 500;
}
else
height = 36;
return height;
}
static readonly Dictionary<string, bool> s_foldouts = new();
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
label = EditorGUI.BeginProperty(position, label, property);
// Begin indent block
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
// Draw the object field on the top line
Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.PropertyField(fieldRect, property, label);
if (property.objectReferenceValue is ClusterPrefab prefab) {
// key per field instance
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetEntityId();
if (!s_foldouts.TryGetValue(key, out bool isOpen))
isOpen = true;
// foldout header rect
Rect headerRect = new(fieldRect.x, fieldRect.yMax + 4f, fieldRect.width, EditorGUIUtility.singleLineHeight);
isOpen = EditorGUI.Foldout(headerRect, isOpen, "Graph", true);
s_foldouts[key] = isOpen;
if (isOpen) {
// content rect below header
Rect drawRect = new(fieldRect.x, headerRect.yMax + 2f, fieldRect.width, 450f);
ClusterView.Render(drawRect, prefab.cluster, property);
//Debug.Log(prefab.cluster.defaultOutput.outputMagnitude);
}
}
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
/*
[InitializeOnLoad]
static class ClusterPrefabInspectorRepaints {
static ClusterPrefabInspectorRepaints() {
EditorApplication.update += OnEditorUpdate;
}
static double lastRepaint = 0;
const double repaintInterval = 1.0 / 15.0; // up to 15 FPS in inspector
static void OnEditorUpdate() {
if (!Application.isPlaying) return;
// throttle repaint frequency
if (EditorApplication.timeSinceStartup - lastRepaint < repaintInterval) return;
lastRepaint = EditorApplication.timeSinceStartup;
// Find all open inspectors (Editors) that target objects containing ClusterPrefab fields
var editors = Resources.FindObjectsOfTypeAll<Editor>();
foreach (var ed in editors) {
var targets = ed.targets;
if (targets == null)
continue;
bool shouldRepaint = targets.Any(t => ObjectHasClusterPrefabField(t));
if (shouldRepaint) {
try {
ed.Repaint();
}
catch {
// ignore
}
}
}
}
static bool ObjectHasClusterPrefabField(UnityEngine.Object obj) {
if (obj == null)
return false;
Type type = obj.GetType();
// search fields (instance, non-public/public)
FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (FieldInfo f in fields) {
if (f.FieldType == typeof(ClusterPrefab))
return true;
// also handle arrays/lists of ClusterPrefab or serializable wrappers:
if (f.FieldType.IsArray && f.FieldType.GetElementType() == typeof(ClusterPrefab))
return true;
if (f.FieldType.IsGenericType) {
Type[] gen = f.FieldType.GetGenericArguments();
if (gen.Length == 1 && gen[0] == typeof(ClusterPrefab))
return true;
}
}
return false;
}
}
*/
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5f43b401b7d59dec7ac7d493cbc4927d

View File

@ -7,6 +7,13 @@ namespace NanoBrain.Unity {
public class ClusterView { public class ClusterView {
public ClusterView(string key) {
this.key = key;
ClusterView.clusterViews[this.key] = this;
}
public static ClusterPrefab previousPrefab;
private static readonly float discRadius = 20; private static readonly float discRadius = 20;
private float viewWidth; private float viewWidth;
private float contentWidth = 1000; private float contentWidth = 1000;
@ -17,17 +24,17 @@ namespace NanoBrain.Unity {
} }
public Mode mode = Mode.Focus; public Mode mode = Mode.Focus;
static readonly Dictionary<string, ClusterView> clusterViews = new(); public static readonly Dictionary<string, ClusterView> clusterViews = new();
public static ClusterView GetClusterView(SerializedProperty property) { public static ClusterView GetClusterView(SerializedProperty property) {
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetEntityId(); string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetInstanceID();//GetEntityId();
if (!clusterViews.TryGetValue(key, out ClusterView clusterView)) if (!clusterViews.TryGetValue(key, out ClusterView clusterView))
clusterView = new() { key = key }; clusterView = new(key);// { key = key };
return clusterView; return clusterView;
} }
public static ClusterView GetClusterView(SerializedObject serializedObject) { public static ClusterView GetClusterView(SerializedObject serializedObject) {
string key = serializedObject.targetObject.GetEntityId().ToString(); string key = serializedObject.targetObject.GetInstanceID().ToString(); //GetEntityId().ToString();
if (!clusterViews.TryGetValue(key, out ClusterView clusterView)) if (!clusterViews.TryGetValue(key, out ClusterView clusterView))
clusterView = new() { key = key }; clusterView = new(key); // { key = key };
return clusterView; return clusterView;
} }
@ -37,7 +44,7 @@ namespace NanoBrain.Unity {
public static void Render(Rect drawRect, Cluster cluster, SerializedProperty property) { public static void Render(Rect drawRect, Cluster cluster, SerializedProperty property) {
ClusterView clusterView = GetClusterView(property); ClusterView clusterView = GetClusterView(property);
if (clusterView.currentCluster == null) { if (clusterView.currentCluster == null || clusterView.currentCluster != cluster) {
clusterView.currentCluster = cluster; clusterView.currentCluster = cluster;
clusterView.currentNucleus = cluster.defaultOutput; clusterView.currentNucleus = cluster.defaultOutput;
clusterView.selectedOutput = clusterView.currentNucleus; clusterView.selectedOutput = clusterView.currentNucleus;
@ -46,7 +53,7 @@ namespace NanoBrain.Unity {
} }
public static void Render(Rect drawRect, Cluster cluster, SerializedObject obj) { public static void Render(Rect drawRect, Cluster cluster, SerializedObject obj) {
ClusterView clusterView = GetClusterView(obj); ClusterView clusterView = GetClusterView(obj);
if (clusterView.currentCluster == null) { if (clusterView.currentCluster == null || clusterView.currentCluster != cluster) {
clusterView.currentCluster = cluster; clusterView.currentCluster = cluster;
clusterView.currentNucleus = cluster.defaultOutput; clusterView.currentNucleus = cluster.defaultOutput;
clusterView.selectedOutput = clusterView.currentNucleus; clusterView.selectedOutput = clusterView.currentNucleus;
@ -101,6 +108,8 @@ namespace NanoBrain.Unity {
public Nucleus currentNucleus = null; public Nucleus currentNucleus = null;
public Nucleus selectedSynapseNeuron = null; public Nucleus selectedSynapseNeuron = null;
public Nucleus selectedOutput; public Nucleus selectedOutput;
public bool isOpen = true;
public bool initialized = false;
#region Focus Graph #region Focus Graph
@ -267,6 +276,9 @@ namespace NanoBrain.Unity {
private void DescendGraph(Dag.Node receiver, ref int ix, Dag dag) { private void DescendGraph(Dag.Node receiver, ref int ix, Dag dag) {
Neuron receiverNeuron = receiver.nucleus as Neuron; Neuron receiverNeuron = receiver.nucleus as Neuron;
if (receiverNeuron == null)
return;
foreach (Synapse synapse in receiverNeuron.synapses) { foreach (Synapse synapse in receiverNeuron.synapses) {
Nucleus nucleus = synapse.neuron; Nucleus nucleus = synapse.neuron;
if (nucleus.parent != null && nucleus.parent != currentNucleus.parent) { if (nucleus.parent != null && nucleus.parent != currentNucleus.parent) {
@ -307,7 +319,7 @@ namespace NanoBrain.Unity {
if (nucleus == this.selectedOutput) { if (nucleus == this.selectedOutput) {
// Add link to 'Outpus' // Add link to 'Outpus'
nodeCount++; nodeCount++;
if (ClusterViewer.previousPrefab != null) if (ClusterView.previousPrefab != null && ClusterView.previousPrefab != nucleus.parent.prefab)
// Add link to previous editor // Add link to previous editor
nodeCount++; nodeCount++;
} }
@ -341,9 +353,9 @@ namespace NanoBrain.Unity {
} }
if (nucleus == this.selectedOutput) { if (nucleus == this.selectedOutput) {
Vector3 pos = new(50, margin + row * spacing, 0); Vector3 pos = new(50, margin + row * spacing, 0);
if (ClusterViewer.previousPrefab != null) { if (ClusterView.previousPrefab != null && ClusterView.previousPrefab != nucleus.parent.prefab) {
DrawEdge(parentPos, pos); DrawEdge(parentPos, pos);
DrawClusterPrefab(ClusterViewer.previousPrefab, pos); DrawClusterPrefab(ClusterView.previousPrefab, pos);
row++; row++;
} }
pos = new(50, margin + row * spacing, 0); pos = new(50, margin + row * spacing, 0);
@ -433,7 +445,7 @@ namespace NanoBrain.Unity {
float maxValue = 0; float maxValue = 0;
foreach (Cluster sibling in nucleus.parent.instances) { foreach (Cluster sibling in nucleus.parent.instances) {
Neuron siblingNeuron = sibling.GetNucleus(nucleus.name) as Neuron; Neuron siblingNeuron = sibling.GetNeuron(nucleus.name);
float value = siblingNeuron.outputMagnitude; // no need to add weight as they are all the same float value = siblingNeuron.outputMagnitude; // no need to add weight as they are all the same
if (value > maxValue) if (value > maxValue)
maxValue = value; maxValue = value;
@ -444,9 +456,10 @@ namespace NanoBrain.Unity {
float margin = 10 + spacing / 2; float margin = 10 + spacing / 2;
int row = 0; int row = 0;
Vector3 position = Vector3.zero;
foreach (Cluster sibling in nucleus.parent.instances) { foreach (Cluster sibling in nucleus.parent.instances) {
Neuron siblingNeuron = sibling.GetNucleus(nucleus.name) as Neuron; Neuron siblingNeuron = sibling.GetNeuron(nucleus.name);
Vector3 position = new(250, margin + row * spacing, 0.0f); position = new(250, margin + row * spacing, 0.0f);
DrawEdge(parentPos, position); DrawEdge(parentPos, position);
Color color = Color.black; Color color = Color.black;
if (Application.isPlaying) { if (Application.isPlaying) {
@ -456,16 +469,16 @@ namespace NanoBrain.Unity {
color = new Color(brightness, brightness, brightness, 1f); color = new Color(brightness, brightness, brightness, 1f);
} }
DrawNucleus(siblingNeuron, position, color); DrawNucleus(siblingNeuron, position, color);
row++;
}
Vector3 labelPos = position - Vector3.down * (discRadius + 5); // below neuron
string name = $"{nucleus.parent.instances[0].baseName}\n{nucleus.name}";
GUIStyle style = new(EditorStyles.label) { GUIStyle style = new(EditorStyles.label) {
alignment = TextAnchor.UpperCenter, alignment = TextAnchor.UpperCenter,
normal = { textColor = Color.white }, normal = { textColor = Color.white },
fontStyle = FontStyle.Bold, fontStyle = FontStyle.Bold,
}; };
Vector3 labelPos = position - Vector3.down * (discRadius + 5); // below neuron Handles.Label(labelPos, name, style);
string name = $"{sibling.baseName}\n{nucleus.name}";
Handles.Label(labelPos, name, style);
row++;
}
expandArray = false; expandArray = false;
} }
@ -523,7 +536,6 @@ namespace NanoBrain.Unity {
} }
} }
protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue) { protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue) {
maxValue = 1; maxValue = 1;
Color color; Color color;
@ -538,7 +550,6 @@ namespace NanoBrain.Unity {
DrawNucleus(nucleus, position, color); DrawNucleus(nucleus, position, color);
} }
protected void DrawNucleus(Nucleus nucleus, Vector2 position, Color color) { protected void DrawNucleus(Nucleus nucleus, Vector2 position, Color color) {
if (nucleus == null) if (nucleus == null)
return; return;
@ -696,7 +707,7 @@ namespace NanoBrain.Unity {
e.Use(); e.Use();
Selection.activeObject = prefab; Selection.activeObject = prefab;
EditorGUIUtility.PingObject(prefab); EditorGUIUtility.PingObject(prefab);
ClusterViewer.previousPrefab = null; ClusterView.previousPrefab = null;
Editor.CreateEditor(prefab); Editor.CreateEditor(prefab);
} }
} }
@ -762,16 +773,16 @@ namespace NanoBrain.Unity {
protected void OnNeuronClick(Nucleus nucleus) { protected void OnNeuronClick(Nucleus nucleus) {
if (nucleus == this.currentNucleus) { if (nucleus == this.currentNucleus) {
this.selectedSynapseNeuron = null; this.selectedSynapseNeuron = null;
// if (Application.isPlaying) { if (Application.isPlaying) {
// if (nucleus is Cluster) if (nucleus is Cluster)
// expandArray = !expandArray; expandArray = !expandArray;
// else else
// expandArray = false; expandArray = false;
// } }
// else { else {
if (nucleus is Cluster cluster) if (nucleus is Cluster cluster)
OnClusterClick(cluster); OnClusterClick(cluster);
// } }
} }
else if (nucleus.parent != null && this.currentNucleus != null && nucleus.parent != this.currentNucleus.parent) { else if (nucleus.parent != null && this.currentNucleus != null && nucleus.parent != this.currentNucleus.parent) {
// We go to a different cluster // We go to a different cluster
@ -792,6 +803,7 @@ namespace NanoBrain.Unity {
else { else {
// select the cluster, not the neuron in the cluster // select the cluster, not the neuron in the cluster
this.currentNucleus = nucleus.parent; this.currentNucleus = nucleus.parent;
this.selectedSynapseNeuron = null;
this.expandArray = false; this.expandArray = false;
} }
} }
@ -799,6 +811,7 @@ namespace NanoBrain.Unity {
this.currentNucleus = nucleus; this.currentNucleus = nucleus;
if (this.currentNucleus is Neuron neuron && neuron.receivers.Count == 0) if (this.currentNucleus is Neuron neuron && neuron.receivers.Count == 0)
this.selectedOutput = this.currentNucleus; this.selectedOutput = this.currentNucleus;
this.selectedSynapseNeuron = null;
this.expandArray = false; this.expandArray = false;
} }
} }
@ -807,7 +820,7 @@ namespace NanoBrain.Unity {
// May be used with storedPrefab... // May be used with storedPrefab...
Selection.activeObject = subCluster.prefab; Selection.activeObject = subCluster.prefab;
EditorGUIUtility.PingObject(subCluster.prefab); EditorGUIUtility.PingObject(subCluster.prefab);
ClusterViewer.previousPrefab = this.currentCluster.prefab; ClusterView.previousPrefab = this.currentCluster.prefab;
Editor.CreateEditor(subCluster.prefab); Editor.CreateEditor(subCluster.prefab);
} }

View File

@ -1,2 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 8a7663ccd347fd78dbdba393c03ed7c7 guid: 8a7663ccd347fd78dbdba393c03ed7c7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,902 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace NanoBrain.Unity {
public class ClusterViewer : Editor {
public static ClusterPrefab previousPrefab;
public class GraphView : VisualElement {
protected Cluster currentCluster;
protected SerializedObject serializedBrain;
protected Nucleus _currentNucleus;
protected virtual Nucleus currentNucleus {
get => _currentNucleus;
set => _currentNucleus = value;
}
//protected Nucleus currentNucleus;
protected Nucleus selectedOutput;
// Only used when selecting a synapse to a multi-cluster
protected Nucleus selectedSynapseNeuron;
protected GameObject gameObject;
private bool expandArray = false;
protected ClusterPrefab prefabAsset;
protected VisualElement topMenuContainer;
protected ScrollView scrollView;
protected IMGUIContainer graphContainer;
//protected readonly PopupField<string> outputsPopup;
public enum Mode {
Focus,
Full
}
public Mode mode = Mode.Focus;
public GraphView(Cluster cluster) {
this.currentCluster = cluster;
name = "content";
style.flexGrow = 1;
topMenuContainer = new() {
style = {
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
}
};
EnumField modePopup = new(mode);
modePopup.style.width = 80;
modePopup.RegisterValueChangedCallback(OnModeChange);
topMenuContainer.Add(modePopup);
scrollView = new(ScrollViewMode.Horizontal);
scrollView.style.position = Position.Absolute;
scrollView.style.left = 0; scrollView.style.top = 0;
scrollView.style.right = 0; scrollView.style.bottom = 0;
scrollView.horizontalScrollerVisibility = ScrollerVisibility.Auto; // Auto shows when needed
scrollView.verticalScrollerVisibility = ScrollerVisibility.Hidden;
graphContainer = new(OnIMGUI) {
pickingMode = PickingMode.Position,
focusable = true
};
scrollView.contentContainer.Add(graphContainer);
Add(scrollView);
Add(topMenuContainer);
// Subscribe when added to panel (editor UI ready)
RegisterCallback<AttachToPanelEvent>(evt => Subscribe());
RegisterCallback<DetachFromPanelEvent>(evt => Unsubscribe());
}
protected virtual void OnModeChange(ChangeEvent<System.Enum> changeEvent) {
this.mode = (Mode)changeEvent.newValue;
}
private bool subscribed = false;
void Subscribe() {
if (subscribed)
return;
SceneView.duringSceneGui += OnSceneGUI;
subscribed = true;
SceneView.RepaintAll();
}
void Unsubscribe() {
if (!subscribed)
return;
SceneView.duringSceneGui -= OnSceneGUI;
subscribed = false;
}
public void SetGraph(GameObject gameObject) {
this.gameObject = gameObject;
if (this.currentCluster == null)
return;
if (Application.isPlaying == false)
this.serializedBrain = new SerializedObject(this.currentCluster.prefab);
this.selectedOutput = this.currentCluster.defaultOutput;
this.currentNucleus = this.selectedOutput;
Rebuild();
}
void Rebuild() {
if (this.currentNucleus == null)
return;
string path = AssetDatabase.GetAssetPath(this.currentCluster.prefab); // or known path
this.prefabAsset = AssetDatabase.LoadAssetAtPath<ClusterPrefab>(path);
if (this.prefabAsset == null) {
// create in memory save if it doesn't exist
this.prefabAsset = CreateInstance<ClusterPrefab>();
//Debug.LogError("Cluster Prefab is not found on disk");
}
}
public void OnIMGUI() {
var rect = graphContainer.layout; // container local size
int id = GUIUtility.GetControlID(123456, FocusType.Passive, new Rect(0, 0, rect.width, rect.height));
//int id = GUIUtility.GetControlID(FocusType.Passive);
if (Application.isPlaying == false && serializedBrain != null)
serializedBrain.Update();
Rect r = new Rect(0, 0, rect.width, rect.height);
ClusterView.Render(r, currentCluster, serializedBrain);
// ClusterView clusterView = ClusterView.GetClusterView(serializedBrain);
// clusterView.currentCluster ??= currentCluster;
// clusterView.DrawGraph(id);
// Handles.BeginGUI();
// DrawGraph();
// Handles.EndGUI();
}
#region Graph
// public virtual void DrawGraph() {
// if (mode == Mode.Focus)
// DrawFocusGraph();
// // else
// // DrawFullGraph();
// }
#region Full Graph
/*
protected void DrawFullGraph() {
//Dag dag = GenerateGraph(this.prefab);
Dag dag = GenerateGraph(this.selectedOutput);
Dag.ComputeLayout(dag);
// Draw edges
foreach (Dag.Edge e in dag.edges) {
Dag.Node from = dag.nodes.FirstOrDefault(x => x.id == e.fromId);
Dag.Node to = dag.nodes.FirstOrDefault(x => x.id == e.toId);
if (from == null || to == null)
continue;
Vector2 fromPosition = from.position;
Vector2 toPosition = to.position;
DrawEdge(fromPosition, toPosition);
}
// Draw nodes
foreach (Dag.Node n in dag.nodes)
DrawNucleus(n.nucleus, n.position, 1, n.radius);
// Determine graph width
float width = 0;
float currentNucleusPosition = 0;
foreach (Dag.Node node in dag.nodes) {
if (node.position.x > width)
width = node.position.x;
if (node.nucleus == currentNucleus)
currentNucleusPosition = node.position.x;
}
// Resize the graph container to the full graph width
float margin = 50f;
graphContainer.style.width = width + 2 * margin;
// Scroll to the current nucleus
float viewportWidth = scrollView.layout.width;
// center currentNucleus in viewport
float desiredScrollX = currentNucleusPosition - viewportWidth * 0.5f;
// clamp between 0 and maximum scrollable range
float maxScrollX = Mathf.Max(0f, graphContainer.resolvedStyle.width - viewportWidth);
desiredScrollX = Mathf.Clamp(desiredScrollX, 0f, maxScrollX);
Vector2 current = scrollView.scrollOffset;
scrollView.scrollOffset = new Vector2(desiredScrollX, current.y);
}
public Dag GenerateGraph(Nucleus rootNucleus) {
Dag dag = new();
if (rootNucleus == null)
return dag;
int ix = 0;
Dag.Node receiver = new() {
id = ix,
//title = nucleus.name,
nucleus = rootNucleus
};
dag.nodes.Add(receiver);
ix++;
DescendGraph(receiver, ref ix, dag);
return dag;
}
private void DescendGraph(Dag.Node receiver, ref int ix, Dag dag) {
Neuron receiverNeuron = receiver.nucleus as Neuron;
foreach (Synapse synapse in receiverNeuron.synapses) {
Nucleus nucleus = synapse.neuron;
if (nucleus.parent != null && nucleus.parent != currentNucleus.parent) {
nucleus = nucleus.parent;
}
string nucleusName = nucleus.name;
Dag.Node synapseNode = dag.FindNode(nucleusName);
if (synapseNode == null) {
synapseNode = new() {
id = ix,
nucleus = nucleus
};
dag.nodes.Add(synapseNode);
}
Dag.Edge edge = new() {
fromId = synapseNode.id,
toId = receiver.id
};
dag.edges.Add(edge);
ix++;
DescendGraph(synapseNode, ref ix, dag);
}
}
*/
#endregion Full Graph
#region Focus Graph
/*
protected void DrawFocusGraph() {
float size = 20;
Vector3 position = new(150, 210, 0);
if (this.currentNucleus != null) {
DrawReceivers(this.currentNucleus, position, size);
DrawSynapses(this.currentNucleus, position, size);
// Draw selected Nucleus
if (expandArray) {
float maxValue = 1;
if (this.currentNucleus is Cluster cluster) {
float spacing = 400f / cluster.instanceCount;
float margin = 10 + spacing / 2;
float xMin = 150 - size;
float xMax = 150 + size;
float yMin = 10 + margin - size / 2;
float yMax = 400 - margin + size;
Vector3[] verts = new Vector3[4] {
new(xMin, yMin, 0),
new(xMax, yMin, 0),
new(xMax, yMax, 0),
new(xMin, yMax, 0)
};
Handles.color = Color.black;
Handles.DrawAAConvexPolygon(verts);
int row = 0;
if (cluster.instances == null) {
Vector3 pos = new(150, margin + row * spacing, 0.0f);
Handles.color = Color.white;
// The selected sibling highlight ring
Handles.DrawSolidDisc(pos, Vector3.forward, size + 2);
DrawNucleus(cluster, pos, maxValue, size);
row++;
}
else {
foreach (Cluster sibling in cluster.instances) {
Vector3 pos = new(150, margin + row * spacing, 0.0f);
Handles.color = Color.white;
// The selected sibling highlight ring
Handles.DrawSolidDisc(pos, Vector3.forward, size + 2);
DrawNucleus(sibling, pos, maxValue, size);
row++;
}
}
GUIStyle style = new(EditorStyles.label) {
alignment = TextAnchor.UpperCenter,
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold,
};
Vector3 labelPos = new(150, yMax + size + 5, 0);
string clusterName = cluster.name;
int colonPos = clusterName.IndexOf(":");
if (colonPos > 0) {
string baseName = clusterName[..colonPos];
Handles.Label(labelPos, baseName, style);
}
else
Handles.Label(labelPos, clusterName, style);
}
else {
if (this.currentNucleus is Neuron neuron)
maxValue = neuron.outputMagnitude;
DrawNucleus(this.currentNucleus, position, maxValue, 20);
}
}
else {
float maxValue = 1;
if (this.currentNucleus is Neuron neuron)
maxValue = neuron.outputMagnitude;
else if (this.currentNucleus is Cluster cluster)
maxValue = cluster.defaultOutput.outputMagnitude;
DrawNucleus(this.currentNucleus, position, maxValue, 20);
}
}
else {
DrawAllOutputs(position, size);
DrawOutputs(position, size);
}
graphContainer.style.width = 300;
}
protected void DrawReceivers(Nucleus nucleus, Vector3 parentPos, float size) {
List<Nucleus> receivers;
if (nucleus is Neuron neuron)
receivers = neuron.receivers;
else if (nucleus is Cluster cluster)
receivers = cluster.CollectReceivers(true);
else
return;
// For top-level nodes, add link to previous editor and/or 'Outputs'
int nodeCount = receivers.Count();
if (nucleus == this.selectedOutput) {
// Add link to 'Outpus'
nodeCount++;
if (ClusterViewer.previousPrefab != null)
// Add link to previous editor
nodeCount++;
}
// Determine the maximum value in this layer
// This is used to 'scale' the output value colors of the nuclei
float maxValue = 0;
foreach (Nucleus receiver in receivers) {
if (receiver is Neuron neuroid) {
float value = neuroid.outputMagnitude;
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 (Nucleus receiver in receivers) {
Nucleus receiverNucleus = receiver;
if (receiverNucleus == null)
continue;
Vector3 pos = new(50, margin + row * spacing, 0.0f);
DrawEdge(parentPos, pos);
DrawNucleus(receiverNucleus, pos, maxValue, size);
row++;
}
if (nucleus == this.selectedOutput) {
Vector3 pos = new(50, margin + row * spacing, 0);
if (ClusterViewer.previousPrefab != null) {
DrawEdge(parentPos, pos);
DrawClusterPrefab(ClusterViewer.previousPrefab, pos, size);
row++;
}
pos = new(50, margin + row * spacing, 0);
DrawEdge(parentPos, pos);
DrawAllOutputs(pos, size);
}
}
protected void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) {
if (nucleus is not Neuron neuron)
return;
if (this.selectedSynapseNeuron != null) {
DrawClusterSynapses(this.selectedSynapseNeuron, parentPos, size);
return;
}
if (nucleus == null)
return;
// Determine the maximum value in this layer
// This is used to 'scale' the output value colors of the nuclei
float maxValue = 0;
int neuronCount = 0;
List<string> drawnNeuronNames = new();
foreach (Synapse synapse in neuron.synapses) {
if (synapse.neuron == null)
continue;
// Count multiple synapses to the same neuron only once
string neuronName = synapse.neuron.name;
if (synapse.neuron.parent != null)
neuronName = synapse.neuron.parent.baseName + "." + neuronName;
if (drawnNeuronNames.Contains(neuronName))
continue;
drawnNeuronNames.Add(neuronName);
float value = synapse.neuron.outputMagnitude * synapse.weight;
if (value > maxValue)
maxValue = value;
neuronCount++;
}
// Determine the spacing of the nuclei in the layer
float spacing = 400f / neuronCount;
float margin = 10 + spacing / 2;
int row = 0;
//List<Neuron> drawnNeurons = new();
drawnNeuronNames = new();
foreach (Synapse synapse in neuron.synapses) {
if (synapse.neuron is null)
continue;
// Draw multiple synapses to the same neuron only once
string neuronName = synapse.neuron.name;
if (synapse.neuron.parent != null)
neuronName = synapse.neuron.parent.baseName + "." + neuronName;
if (drawnNeuronNames.Contains(neuronName))
continue;
drawnNeuronNames.Add(neuronName);
Vector3 pos = new(250, margin + row * spacing, 0.0f);
DrawEdge(parentPos, pos);
// Handles.color = Color.white;
// Handles.DrawLine(parentPos, pos);
Color color = Color.black;
if (Application.isPlaying) {
if (maxValue == 0 || !float.IsFinite(maxValue))
maxValue = 1;
float brightness = synapse.neuron.outputMagnitude * synapse.weight / maxValue;
color = new Color(brightness, brightness, brightness, 1f);
}
DrawNucleus(synapse.neuron, pos, size, color);
row++;
}
}
protected void DrawClusterSynapses(Nucleus nucleus, Vector3 parentPos, float size) {
if (nucleus == null || nucleus.parent == null || nucleus.parent.instances == null)
return;
// Hack to disable showing labels
expandArray = true;
float maxValue = 0;
foreach (Cluster sibling in nucleus.parent.instances) {
Neuron siblingNeuron = sibling.GetNucleus(nucleus.name) as Neuron;
float value = siblingNeuron.outputMagnitude; // no need to add weight as they are all the same
if (value > maxValue)
maxValue = value;
}
// Determine the spacing of the nuclei in the layer
float spacing = 400f / nucleus.parent.instanceCount;
float margin = 10 + spacing / 2;
int row = 0;
foreach (Cluster sibling in nucleus.parent.instances) {
Neuron siblingNeuron = sibling.GetNucleus(nucleus.name) as Neuron;
Vector3 position = new(250, margin + row * spacing, 0.0f);
DrawEdge(parentPos, position);
Color color = Color.black;
if (Application.isPlaying) {
if (maxValue == 0 || !float.IsFinite(maxValue))
maxValue = 1;
float brightness = siblingNeuron.outputMagnitude / maxValue;
color = new Color(brightness, brightness, brightness, 1f);
}
DrawNucleus(siblingNeuron, position, size, color);
GUIStyle style = new(EditorStyles.label) {
alignment = TextAnchor.UpperCenter,
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold,
};
Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron
string name = $"{sibling.baseName}\n{nucleus.name}";
Handles.Label(labelPos, name, style);
row++;
}
expandArray = false;
}
protected void DrawOutputs(Vector2 parentPos, float size) {
if (this.currentCluster == null)
return;
// Determine the maximum value in this layer
// This is used to 'scale' the output value colors of the nuclei
float maxValue = 0;
int neuronCount = 0;
List<Nucleus> drawnNuclei = new();
foreach (Nucleus nucleus in this.currentCluster.outputs) {
if (nucleus is not Neuron neuron)
continue;
// Draw multiple synapses to the same neuron only once
if (drawnNuclei.Contains(nucleus))
continue;
drawnNuclei.Add(nucleus);
float value = neuron.outputMagnitude;
if (value > maxValue)
maxValue = value;
neuronCount++;
}
// Determine the spacing of the nuclei in the layer
float spacing = 400f / neuronCount;
float margin = 10 + spacing / 2;
int row = 0;
drawnNuclei = new();
foreach (Nucleus nucleus in this.currentCluster.outputs) {
if (nucleus is not Neuron neuron)
continue;
// Draw multiple synapses to the same neuron only once
if (drawnNuclei.Contains(nucleus))
continue;
drawnNuclei.Add(nucleus);
Vector3 pos = new(250, margin + row * spacing, 0.0f);
DrawEdge(parentPos, pos);
Color color = Color.black;
if (Application.isPlaying) {
if (maxValue == 0 || !float.IsFinite(maxValue))
maxValue = 1;
float brightness = neuron.outputMagnitude / maxValue;
color = new Color(brightness, brightness, brightness, 1f);
}
DrawNucleus(nucleus, pos, size, color);
row++;
}
}
*/
#endregion Focus Graph
/*
protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) {
Color color;
if (Application.isPlaying) {
float brightness = 0;
if (nucleus is Neuron neuron)
brightness = neuron.outputMagnitude / maxValue;
color = new Color(brightness, brightness, brightness, 1f);
}
else
color = Color.black;
DrawNucleus(nucleus, position, size, color);
}
protected void DrawNucleus(Nucleus nucleus, Vector3 position, float size, Color color) {
if (nucleus == null)
return;
if (nucleus == this.currentNucleus) {
// The selected nucleus highlight ring
Handles.color = Color.white;
Handles.DrawSolidDisc(position, Vector3.forward, size + 2);
}
if (nucleus is MemoryCell) {
Handles.color = Color.white;
Handles.DrawWireDisc(position + Vector3.right * 10, Vector3.forward, size);
}
Handles.color = color;
Handles.DrawSolidDisc(position, Vector3.forward, size);
Handles.color = Color.white;
// Position the label in front of the disc
Vector3 labelPosition = position + (Vector3.forward * 0.1f);
GUIStyle style = new(EditorStyles.label) {
alignment = TextAnchor.MiddleCenter,
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold,
};
if (nucleus.parent is Cluster parentCluster && currentNucleus != null && parentCluster != currentNucleus.parent)
DrawCluster(parentCluster, position, color, size);
else if (nucleus is Cluster cluster)
DrawCluster(cluster, position, color, size);
if (expandArray == false) {// || nucleus != currentNucleus) {
// put name below nucleus
Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron
style.alignment = TextAnchor.UpperCenter;
if (nucleus.parent != null && currentNucleus != null && nucleus.parent != currentNucleus.parent && nucleus.parent is Cluster parentCluster1) {
// This neuron is part of another cluster
parentCluster1.name ??= "";
int colonPos = parentCluster1.name.IndexOf(":");
string baseName;
if (colonPos > 0 && colonPos < parentCluster1.name.Length - 2)
baseName = parentCluster1.name[..colonPos] + "\n";
else
baseName = parentCluster1.name + "\n";
Handles.Label(labelPos, baseName + nucleus.name, style);
}
else {
nucleus.name ??= "";
int colonPos = nucleus.name.IndexOf(":");
if (colonPos > 0 && colonPos < nucleus.name.Length - 2) {
// if it is an array, we should not show the :0 of the first element
string baseName = nucleus.name[..colonPos];
Handles.Label(labelPos, baseName, style);
}
else
Handles.Label(labelPos, nucleus.name, style);
}
}
// Tooltip
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();
if (nucleus is Cluster parentCluster2)
OnNeuronClick(parentCluster2);
else
OnNeuronClick(nucleus);
}
}
}
protected void DrawCluster(Cluster cluster, Vector3 position, Color color, float size) {
GUIStyle labelTextStyle = new(EditorStyles.label) {
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold,
};
if (expandArray) {
// Put array indices above the discs
labelTextStyle.alignment = TextAnchor.LowerCenter;
Vector3 labelPosition = position + Vector3.down * (size + 5); // below disc
// Strip the instance number in the name
int colonPos1 = cluster.name.IndexOf(":");
if (colonPos1 > 0) {
string extName = cluster.name[(colonPos1 + 2)..];
Handles.Label(labelPosition, extName, labelTextStyle);
}
else
Handles.Label(labelPosition, "0", labelTextStyle);
}
else {
// Put instance count inside the disc
labelTextStyle.alignment = TextAnchor.MiddleCenter;
Vector3 labelPosition = position + (Vector3.forward * 0.1f);
// Adjust text color based on disc color
if (color.grayscale > 0.5f)
labelTextStyle.normal.textColor = Color.black;
else
labelTextStyle.normal.textColor = Color.white;
if (cluster.instanceCount > 1) {
Handles.Label(labelPosition, cluster.instanceCount.ToString(), labelTextStyle);
labelTextStyle.normal.textColor = Color.white;
}
else if (cluster.instances != null && cluster.instances.Length > 1) {
Handles.Label(labelPosition, cluster.instances.Length.ToString(), labelTextStyle);
labelTextStyle.normal.textColor = Color.white;
}
}
// Draw a circle around the disc to indicate this is a Cluster
Handles.color = Color.white;
Handles.DrawWireDisc(position, Vector3.forward, size + 5);
}
protected void DrawClusterPrefab(ClusterPrefab prefab, Vector2 position, float size) {
Handles.color = Color.black;
Handles.DrawSolidDisc(position, Vector3.forward, size);
// Draw a circle around the disc to indicate this is a Cluster
Handles.color = Color.white;
Handles.DrawWireDisc(position, Vector3.forward, size + 5);
// put name below nucleus
GUIStyle style = new(EditorStyles.label) {
alignment = TextAnchor.MiddleCenter,
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold,
};
Vector2 labelPos = position - Vector2.down * (size + 5); // below neuron
style.alignment = TextAnchor.UpperCenter;
Handles.Label(labelPos, prefab.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 click
if (e.type == EventType.MouseDown && e.button == 0) {
// Consume the event so the scene doesn't also handle it
e.Use();
Selection.activeObject = prefab;
EditorGUIUtility.PingObject(prefab);
ClusterViewer.previousPrefab = null;
CreateEditor(prefab);
}
}
}
protected void DrawAllOutputs(Vector2 position, float size) {
GUIStyle labelTextStyle = new(EditorStyles.label) {
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter,
};
Handles.Label(position, "Outputs", labelTextStyle);
Rect neuronRect = new(position.x - size, position.y - size, size * 2, size * 2);
Event e = Event.current;
if (e != null && neuronRect.Contains(e.mousePosition)) {
// Process click
if (e.type == EventType.MouseDown && e.button == 0) {
// Consume the event so the scene doesn't also handle it
e.Use();
OnAllOutputsClick();
}
}
}
protected void DrawEdge(Vector2 from, Vector2 to, float radius = 20) {
Handles.color = Color.white;
// Handles.DrawLine(from, to);
Vector2 dir = to - from;
float len = dir.magnitude;
if (len <= 2f * radius || len <= Mathf.Epsilon)
// line too short
return;
Vector2 n = dir / len; // normalized
Vector2 a = from + n * radius;
Vector2 b = to - n * radius;
Handles.DrawLine(a, b);
}
protected void HandleMouseHover(Nucleus nucleus, Rect rect) {
GUIContent tooltip;
if (nucleus is Neuron neuron) {
tooltip = new(
$"{nucleus.name}" +
$"\nValue: {neuron.outputMagnitude}");
}
else
tooltip = new($"{nucleus.name}");
Vector2 mousePosition = Event.current.mousePosition;
// Display tooltip with some offset
Vector2 tooltipSize = GUI.skin.box.CalcSize(tooltip);
Rect tooltipRect = new Rect(mousePosition.x + 10, mousePosition.y + 10, tooltipSize.x, tooltipSize.y);
GUI.Box(tooltipRect, tooltip);
}
protected void OnNeuronClick(Nucleus nucleus) {
if (nucleus == this.currentNucleus) {
this.selectedSynapseNeuron = null;
// if (Application.isPlaying) {
// if (nucleus is Cluster)
// expandArray = !expandArray;
// else
// expandArray = false;
// }
// else {
if (nucleus is Cluster cluster)
OnClusterClick(cluster);
// }
}
else if (nucleus.parent != null && this.currentNucleus != null && nucleus.parent != this.currentNucleus.parent) {
// We go to a different cluster
if (Application.isPlaying) {
if (this.selectedSynapseNeuron == null && nucleus.parent.instanceCount > 1) {
this.selectedSynapseNeuron = nucleus;
expandArray = false;
}
else {
this.currentNucleus = nucleus;
if (this.currentNucleus is Neuron neuron && neuron.receivers.Count == 0)
this.selectedOutput = this.currentNucleus;
this.selectedSynapseNeuron = null;
expandArray = false;
}
}
else {
// select the cluster, not the neuron in the cluster
this.currentNucleus = nucleus.parent;
expandArray = false;
}
}
else {
this.currentNucleus = nucleus;
if (this.currentNucleus is Neuron neuron && neuron.receivers.Count == 0)
this.selectedOutput = this.currentNucleus;
expandArray = false;
}
}
protected void OnClusterClick(Cluster subCluster) {
// May be used with storedPrefab...
Selection.activeObject = subCluster.prefab;
EditorGUIUtility.PingObject(subCluster.prefab);
ClusterViewer.previousPrefab = this.currentCluster.prefab;
ClusterEditor newEditor = CreateEditor(subCluster.prefab) as ClusterEditor;
}
protected void OnAllOutputsClick() {
this.currentNucleus = null;
this.selectedOutput = null;
expandArray = false;
}
*/
#endregion Graph
void OnSceneGUI(SceneView sceneView) {
if (this.gameObject != null) {
Handles.color = Color.yellow;
if (this.selectedSynapseNeuron != null) {
foreach (Cluster sibling in this.selectedSynapseNeuron.parent.instances) {
Neuron siblingNeuron = sibling.GetNucleus(this.selectedSynapseNeuron.name) as Neuron;
Vector3 worldVector = this.gameObject.transform.TransformVector(siblingNeuron.outputValue);
Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
}
// if (this.currentNucleus is Cluster cluster) {
// foreach (Cluster sibling in cluster.siblingClusters) {
// }
// }
// // if (this.currentNucleus is IReceptor receptor) {
// // foreach (Nucleus nucleus in receptor.nucleiArray) {
// // if (nucleus is Neuron neuron) {
// // Vector3 worldVector = this.gameObject.transform.TransformVector(neuron.outputValue);
// // Handles.color = Color.yellow;
// // Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
// // }
// // }
}
else {
if (this.currentNucleus is Neuron currentNeuron) {
Vector3 worldVector = this.gameObject.transform.TransformVector(currentNeuron.outputValue);
Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector);
}
}
}
}
}
}
public class NeuroidLayer {
public int ix = 0;
public List<Nucleus> neuroids = new();
}
}

308
Editor/Cluster_Drawer.cs Normal file
View File

@ -0,0 +1,308 @@
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEditor;
using System;
namespace NanoBrain.Unity {
[CustomPropertyDrawer(typeof(Cluster))]
class Cluster_Drawer : PropertyDrawer {
static Cluster_Drawer() {
if (Application.isPlaying)
SceneView.duringSceneGui += OnSceneGUI;
Selection.selectionChanged += OnSelectionChanged;
if (clusterView != null)
clusterView.initialized = false;
}
private const float padding = 4f;
private const float elementHeight = 64f; // height reserved for the VisualElement
private static ClusterView clusterView;
private static UnityEngine.Object selectedTarget;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
if (Cluster_Drawer.clusterView == null)
return 0;
float height = EditorGUIUtility.singleLineHeight + padding;
SerializedProperty prefabProp = property.FindPropertyRelative(nameof(Cluster.prefab));
if (prefabProp.objectReferenceValue != null && Cluster_Drawer.clusterView.isOpen) {
height += padding + elementHeight;
height = 500;
}
else
height = 18;
return height;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
// Not sure if this works properly with multiple Clusters in a single gameObject
ClusterView clusterView = Cluster_Drawer.clusterView;
if (Cluster_Drawer.clusterView == null)
clusterView = ClusterView.GetClusterView(property);
label = EditorGUI.BeginProperty(position, label, property);
// Begin indent block
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
SerializedProperty prefabProp = property.FindPropertyRelative(nameof(Cluster.prefab));
// Draw the object field on the top line
Rect fieldRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(fieldRect, prefabProp, label);
if (EditorGUI.EndChangeCheck()) {
prefabProp.serializedObject.ApplyModifiedProperties();
ClusterPrefab clusterPrefab = prefabProp.objectReferenceValue as ClusterPrefab;
if (clusterPrefab != null) {
Cluster newCluster = new(clusterPrefab);
SerializedObject serializedObject = property.serializedObject;
foreach (UnityEngine.Object targetObject in serializedObject.targetObjects) {
var parent = SerializedPropertyUtility.GetParentObjectAndMember(targetObject, property.propertyPath, out var memberInfo, out int outIndex);
if (parent != null && memberInfo is FieldInfo fieldInfo) {
fieldInfo.SetValue(parent, newCluster);
EditorUtility.SetDirty(targetObject);
}
}
}
}
if (prefabProp.objectReferenceValue != null) {
// Graph is not shown when multi-editing
if (property.serializedObject.targetObjects.Length == 1) {
UnityEngine.Object targetObject = property.serializedObject.targetObject;
Cluster_Drawer.selectedTarget = targetObject;
Cluster cluster;
if (clusterView.initialized || Application.isPlaying) {
cluster = SerializedPropertyUtility.GetManagedObjectForProperty(targetObject, property.propertyPath) as Cluster;
}
else {
Debug.Log("New cluster instance");
// This does not work properly yet it seems
ClusterPrefab clusterPrefab = prefabProp.objectReferenceValue as ClusterPrefab;
cluster = new(clusterPrefab);
object parent = SerializedPropertyUtility.GetParentObjectAndMember(targetObject, property.propertyPath, out var memberInfo, out int outIndex);
if (parent != null && memberInfo is FieldInfo fieldInfo) {
fieldInfo.SetValue(parent, cluster);
EditorUtility.SetDirty(targetObject);
}
clusterView.initialized = true;
}
// foldout header rect
Rect headerRect = new(fieldRect.x, fieldRect.yMax + 4f, fieldRect.width, EditorGUIUtility.singleLineHeight);
clusterView.isOpen = EditorGUI.Foldout(headerRect, clusterView.isOpen, "Graph", true);
if (clusterView.isOpen) {
// content rect below header
Rect drawRect = new(fieldRect.x, headerRect.yMax + 2f, fieldRect.width, 450f);
if (clusterView.currentCluster == null || clusterView.currentCluster != cluster) {
clusterView.currentCluster = cluster;
clusterView.currentNucleus = cluster.defaultOutput;
clusterView.selectedOutput = clusterView.currentNucleus;
}
Cluster_Drawer.clusterView = clusterView;
clusterView.Render(drawRect);
//Debug.Log(prefab.cluster.defaultOutput.outputMagnitude);
}
}
}
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
private static void OnSceneGUI(SceneView sceneView) {
if (Application.isPlaying == false || selectedTarget == null)
return;
GameObject gameObject = null;
if (selectedTarget is Component c)
gameObject = c.gameObject;
else if (selectedTarget is GameObject g)
gameObject = g;
Handles.color = Color.yellow;
if (Cluster_Drawer.clusterView.selectedSynapseNeuron != null) {
foreach (Cluster sibling in Cluster_Drawer.clusterView.selectedSynapseNeuron.parent.instances) {
Neuron siblingNeuron = sibling.GetNeuron(Cluster_Drawer.clusterView.selectedSynapseNeuron.name);
Vector3 worldVector = gameObject.transform.TransformVector(siblingNeuron.outputValue);
Handles.DrawLine(gameObject.transform.position, gameObject.transform.position + worldVector);
}
}
else {
if (Cluster_Drawer.clusterView.currentNucleus is Neuron currentNeuron) {
Vector3 worldVector = gameObject.transform.TransformVector(currentNeuron.outputValue);
Handles.DrawLine(gameObject.transform.position, gameObject.transform.position + worldVector);
}
}
}
private static void OnSelectionChanged() {
UnityEngine.Object active = Selection.activeObject;
if (active == null)
return;
foreach (ClusterView clusterView in ClusterView.clusterViews.Values)
clusterView.initialized = false;
}
}
public static class SerializedPropertyUtility {
// Walks path like "myField.nested.arrayField.Array.data[0].value"
// Returns parent object that contains the final member, and sets outMember (FieldInfo or PropertyInfo) for the final member.
// If final target is an array/list element, returns parent as the IList and outMember = null, and outIndex set to element index.
public static object GetParentObjectAndMember(object root, string propertyPath, out MemberInfo outMember, out int outIndex) {
outMember = null;
outIndex = -1;
if (root == null || string.IsNullOrEmpty(propertyPath)) return null;
object obj = root;
var parts = propertyPath.Split('.');
for (int i = 0; i < parts.Length; i++) {
string part = parts[i];
// handle array/list: Unity path uses "Array" then "data[index]"
if (part == "Array" && i + 2 < parts.Length && parts[i + 1].StartsWith("data[")) {
// previous part name is the list/array field name
string listFieldName = parts[i - 1];
// get the field info for listFieldName on current obj's type
var fiList = GetFieldInTypeHierarchy(obj.GetType(), listFieldName);
object listObj = fiList != null ? fiList.GetValue(obj) : GetPropValue(obj, listFieldName);
if (listObj == null) return null;
// parse index from e.g. "data[3]"
string indexPart = parts[i + 1]; // like data[3]
int start = indexPart.IndexOf('[') + 1;
int end = indexPart.IndexOf(']');
int index = int.Parse(indexPart.Substring(start, end - start));
// if this is the last element in the path, return parent=the IList and outIndex=index
if (i + 2 == parts.Length - 0) {
outMember = null;
outIndex = index;
return listObj as IList;
}
// otherwise step into the element and continue
if (listObj is IList ilist) {
obj = ilist[index];
}
else {
// not an IList — cannot continue
return null;
}
i += 1; // skip the data[...] part (we already consumed it)
continue;
}
// normal field/property access: if this is the last part, return parent and member
bool isLast = (i == parts.Length - 1);
var fi = GetFieldInTypeHierarchy(obj.GetType(), part);
if (fi != null) {
if (isLast) {
outMember = fi;
return obj;
}
else {
obj = fi.GetValue(obj);
if (obj == null) return null;
continue;
}
}
var pi = GetPropertyInTypeHierarchy(obj.GetType(), part);
if (pi != null) {
if (isLast) {
outMember = pi;
return obj;
}
else {
obj = pi.GetValue(obj);
if (obj == null) return null;
continue;
}
}
// if nothing found, fail
return null;
}
return null;
}
public static object GetManagedObjectForProperty(object root, string propertyPath) {
var obj = root;
var parts = propertyPath.Split('.');
for (int i = 0; i < parts.Length; i++) {
var part = parts[i];
// handle array: "arrayField.Array.data[index]"
if (part == "Array") {
i += 2; // skip "Array" and "data[index]"
var indexPart = parts[i]; // like "data[0]"
int start = indexPart.IndexOf('[') + 1;
int end = indexPart.IndexOf(']');
int idx = int.Parse(indexPart.Substring(start, end - start));
var listField = parts[i - 2];
var fi = obj.GetType().GetField(listField, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
var list = fi.GetValue(obj) as System.Collections.IList;
obj = list[idx];
continue;
}
var field = obj.GetType().GetField(part, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (field == null) {
var prop = obj.GetType().GetProperty(part, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (prop == null) return null;
obj = prop.GetValue(obj);
}
else {
obj = field.GetValue(obj);
}
if (obj == null) return null;
}
return obj;
}
static FieldInfo GetFieldInTypeHierarchy(Type type, string fieldName) {
while (type != null) {
var fi = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (fi != null) return fi;
type = type.BaseType;
}
return null;
}
static PropertyInfo GetPropertyInTypeHierarchy(Type type, string propName) {
while (type != null) {
var pi = type.GetProperty(propName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (pi != null) return pi;
type = type.BaseType;
}
return null;
}
static object GetPropValue(object obj, string name) {
var pi = GetPropertyInTypeHierarchy(obj.GetType(), name);
if (pi != null) return pi.GetValue(obj);
var fi = GetFieldInTypeHierarchy(obj.GetType(), name);
if (fi != null) return fi.GetValue(obj);
return null;
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 4fe58945c76d153edacc220597474ad2 guid: 18e075a03ca2efdb2895079f63eb333a
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@ -1,2 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a755ac8461bd0c714a852df47331048e guid: a755ac8461bd0c714a852df47331048e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,85 +0,0 @@
using UnityEngine;
using UnityEditor;
using Unity.Mathematics;
using System;
using System.Reflection;
using System.Collections;
namespace NanoBrain.Unity {
[CustomPropertyDrawer(typeof(Neuron))]
class Neuron_Drawer : PropertyDrawer {
public static void Insepctor(SerializedObject serializedObject, string propertyName ) {
EditorGUILayout.PropertyField(serializedObject.FindProperty(propertyName));
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
// Draw foldout + properties
label = EditorGUI.BeginProperty(position, label, property);
// Begin indent block
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
object instance = GetTargetObjectOfProperty(property);
float lineHeight = EditorGUIUtility.singleLineHeight;
Rect r = new(position.x, position.y, position.width, lineHeight);
if (instance != null) {
FieldInfo field = typeof(Neuron).GetField("_outputValue", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null) {
float3 val = (float3)field.GetValue(instance);
EditorGUI.Vector3Field(r, $"Neuron: {label}", val);
}
}
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
// height for 1 line
return (EditorGUIUtility.singleLineHeight * 1) + (EditorGUIUtility.standardVerticalSpacing * 0);
}
public static object GetTargetObjectOfProperty(SerializedProperty prop) {
var path = prop.propertyPath.Replace(".Array.data[", "[");
object obj = prop.serializedObject.targetObject;
var elements = path.Split('.');
foreach (var element in elements) {
if (element.Contains("[")) {
var elementName = element.Substring(0, element.IndexOf("["));
var index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
obj = GetValue_Imp(obj, elementName, index);
}
else {
obj = GetValue_Imp(obj, element);
}
}
return obj;
}
static object GetValue_Imp(object source, string name) {
if (source == null)
return null;
Type t = source.GetType();
FieldInfo f = t.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (f != null)
return f.GetValue(source);
PropertyInfo p = t.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
return p?.GetValue(source, null);
}
static object GetValue_Imp(object source, string name, int index) {
if (GetValue_Imp(source, name) is not IEnumerable enumerable)
return null;
IEnumerator en = enumerable.GetEnumerator();
for (int i = 0; i <= index; i++) {
if (!en.MoveNext())
return null;
}
return en.Current;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: aa0e340763ca6299e93d514b271ae38d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,69 +0,0 @@
using System;
using UnityEngine;
/*
namespace NanoBrain.Unity {
/// <summary>
/// A NanoBrain which can be used to control a gameobject
/// </summary>
/// A NanoBrain is a small neural network which can be used to implement functional behaviour.
/// The network consists of neurons which are connected together with synapses.
/// The output values of the neurons are of type Vector3 to support spatial computing.
///
/// This component is basically a Unity representation of a nanobrain cluster.
/// \sa
/// - \ref NanoBrain::Cluster "Cluster"
/// - \ref NanoBrain::Neuron "Neuron"
[HelpURL("https://passer.life/documentation/nanobrain/Documentation/html/class_nano_brain_1_1_unity_1_1_brain.html")]
public class Brain : MonoBehaviour {
/// <summary>
/// The Cluster prefab from which the cluster is created
/// </summary>
public ClusterPrefab brainPrefab;
[NonSerialized]
private Cluster brainInstance;
/// <summary>
/// The cluster isntance
/// </summary>
public Cluster brain {
get {
if (brainInstance == null && brainPrefab != null) {
brainInstance = new Cluster(brainPrefab) {
name = brainPrefab.name
};
} else if (brainInstance != null && brainPrefab == null) {
brainInstance = null;
}
return brainInstance;
}
}
// public Cluster InitializeBrain() {
// brainInstance = new Cluster(brainPrefab) {
// name = brainPrefab.name
// };
// return brainInstance;
// }
/// <summary>
/// Update the weight for all Synapses coming from the Neuron with the given name
/// </summary>
/// <param name="brain">The cluster in which the synapses are updated</param>
/// <param name="name">The name of the Neuron for which the weights are updated</param>
/// <param name="weight">The new Synapse weight</param>
public static void UpdateWeight(Cluster brain, string name, float weight) {
Neuron root = brain.defaultOutput;
foreach (Synapse synapse in root.synapses) {
if (synapse.neuron.name == name) {
if (synapse.weight != weight) {
synapse.weight = weight;
// Debug.Log($"Updated weight for {name}");
}
}
}
}
}
}
*/

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 92f34a5e4027a1dc39efd8ce63cf6aba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -24,6 +24,9 @@ namespace NanoBrain {
/// Cluster should always be created from prefabs /// Cluster should always be created from prefabs
public ClusterPrefab prefab; public ClusterPrefab prefab;
//[HideInInspector]
public int version;
/// <summary> /// <summary>
/// The base name of the cluster. I don't think this is actively used at this moment /// The base name of the cluster. I don't think this is actively used at this moment
/// </summary> /// </summary>
@ -42,7 +45,9 @@ namespace NanoBrain {
/// A cluster is a multi-cluster when there is more than one instance. /// A cluster is a multi-cluster when there is more than one instance.
/// The actual instances are only created at runtime. /// The actual instances are only created at runtime.
/// The value instanceCount determines how many instances will be present at runtime. /// The value instanceCount determines how many instances will be present at runtime.
[NonSerialized] //[NonSerialized]
[SerializeReference]
[HideInInspector]
public Cluster[] instances; public Cluster[] instances;
/// <summary> /// <summary>
@ -62,11 +67,8 @@ namespace NanoBrain {
/// All nuclei in this cluster /// All nuclei in this cluster
/// </summary> /// </summary>
[SerializeReference] [SerializeReference]
[HideInInspector]
public List<Nucleus> nuclei = new(); public List<Nucleus> nuclei = new();
// the nuclei sorted using topological sorting
// to ensure that the cluster is computer in the right order
//public List<Nucleus> sortedNuclei;
#region Init #region Init
@ -77,13 +79,12 @@ namespace NanoBrain {
/// <param name="parent">The cluster in which this new cluster will be placed</param> /// <param name="parent">The cluster in which this new cluster will be placed</param>
public Cluster(ClusterPrefab prefab, Cluster parent) { public Cluster(ClusterPrefab prefab, Cluster parent) {
this.prefab = prefab; this.prefab = prefab;
this.version = prefab.version;
this.name = prefab.name; this.name = prefab.name;
this.parent = parent; this.parent = parent;
this.parent?.nuclei.Add(this); this.parent?.nuclei.Add(this);
ClonePrefab(); ClonePrefab();
// _ = this.inputs;
//this.sortedNuclei = TopologicalSort(this.nuclei);
} }
/// <summary> /// <summary>
@ -93,13 +94,12 @@ namespace NanoBrain {
/// <param name="parent">The prefab in which the new copy is placed</param> /// <param name="parent">The prefab in which the new copy is placed</param>
public Cluster(ClusterPrefab prefab, ClusterPrefab parent = null) { public Cluster(ClusterPrefab prefab, ClusterPrefab parent = null) {
this.prefab = prefab; this.prefab = prefab;
this.version = prefab.version;
this.name = prefab.name; this.name = prefab.name;
if (parent != null) if (parent != null)
this.parent = parent.cluster; this.parent = parent.cluster;
ClonePrefab(); ClonePrefab();
// _ = this.inputs;
//this.sortedNuclei = TopologicalSort(this.nuclei);
} }
/// <summary> /// <summary>
@ -108,6 +108,10 @@ namespace NanoBrain {
/// Strange that this does not take any parameters or return values. /// Strange that this does not take any parameters or return values.
/// Where which the clone be found??? /// Where which the clone be found???
private void ClonePrefab() { private void ClonePrefab() {
//Debug.Log($"Clone Prefab {this.prefab.name} -> {this.name}");
if (this.prefab == null || this.prefab.cluster == null || this.prefab.cluster.nuclei == null)
return;
Nucleus[] prefabNuclei = this.prefab.cluster.nuclei.ToArray(); Nucleus[] prefabNuclei = this.prefab.cluster.nuclei.ToArray();
// first clone the nuclei without their connections // first clone the nuclei without their connections
@ -161,36 +165,30 @@ namespace NanoBrain {
} }
} }
if (Application.isPlaying) { foreach (Nucleus clonedNucleus in clonedNuclei) {
// Only create cluster siblings at runtime if (clonedNucleus is not Cluster clonedCluster)
foreach (Nucleus clonedNucleus in clonedNuclei) { continue;
if (clonedNucleus is not Cluster clonedCluster)
continue;
List<Cluster> siblings = new() { List<Cluster> siblings = new() { clonedCluster };
clonedCluster for (int instanceIx = 1; instanceIx < clonedCluster.instanceCount; instanceIx++) {
// Create another sibling
Cluster sibling = new(clonedCluster.prefab, this) {
name = $"{clonedCluster.baseName}: {instanceIx}",
parent = this.parent,
instanceCount = this.instanceCount,
}; };
for (int instanceIx = 1; instanceIx < clonedCluster.instanceCount; instanceIx++) { siblings.Add(sibling);
// Create another sibling CopyAllExternalReceivers(clonedCluster, sibling, this);
Debug.Log($"create {clonedCluster.prefab.name} sibling");
Cluster sibling = new(clonedCluster.prefab, this) {
name = $"{clonedCluster.baseName}: {instanceIx}",
parent = this.parent,
instanceCount = this.instanceCount,
};
siblings.Add(sibling);
CopyAllExternalReceivers(clonedCluster, sibling, clonedCluster.prefab, this);
}
Cluster[] siblingClusters = siblings.ToArray();
foreach (Cluster sibling in siblings)
sibling.instances = siblingClusters;
} }
Cluster[] siblingClusters = siblings.ToArray();
foreach (Cluster sibling in siblings)
sibling.instances = siblingClusters;
}
// Ensure that all neurons are computed to initialize bias // Ensure that all neurons are computed to initialize bias
foreach (Nucleus clonedNucleus in clonedNuclei) { foreach (Nucleus clonedNucleus in clonedNuclei) {
if (clonedNucleus is not Cluster) if (clonedNucleus is not Cluster)
clonedNucleus.UpdateStateIsolated(); clonedNucleus.UpdateStateIsolated();
}
} }
} }
@ -202,13 +200,11 @@ namespace NanoBrain {
parent = this.parent, parent = this.parent,
instanceCount = this.instanceCount, instanceCount = this.instanceCount,
}; };
// Somehow siblingClusters should be cloned too. Believe I do this in ClonePrefab right now.
return clone; return clone;
} }
private static void CopyAllExternalReceivers(Cluster sourceCluster, Cluster sibling, ClusterPrefab prefabParent, Cluster clonedParent) { private static void CopyAllExternalReceivers(Cluster sourceCluster, Cluster sibling, Cluster clonedParent) {
for (int nucleusIx = 0; nucleusIx < sourceCluster.nuclei.Count; nucleusIx++) { for (int nucleusIx = 0; nucleusIx < sourceCluster.nuclei.Count; nucleusIx++) {
Nucleus sourceNucleus = sourceCluster.nuclei[nucleusIx]; Nucleus sourceNucleus = sourceCluster.nuclei[nucleusIx];
if (sourceNucleus is not Neuron sourceNeuron) if (sourceNucleus is not Neuron sourceNeuron)
@ -237,7 +233,7 @@ namespace NanoBrain {
} }
clonedNeuron.AddReceiver(receiver, weight); clonedNeuron.AddReceiver(receiver, weight);
Debug.Log($"external: {receiver.name} receives from {clonedNeuron.name} {clonedNeuron.GetHashCode()}"); // Debug.Log($"external: {receiver.name} receives from {clonedNeuron.name} {clonedNeuron.GetHashCode()}");
} }
} }
@ -500,6 +496,8 @@ namespace NanoBrain {
foreach (Nucleus receptor in this.nuclei) { foreach (Nucleus receptor in this.nuclei) {
if (receptor is Nucleus nucleus) if (receptor is Nucleus nucleus)
if (nucleus.name == nucleusName) { if (nucleus.name == nucleusName) {
// if (nucleus is Cluster cluster)
// cluster.CheckInstances();
foundNucleus = nucleus; foundNucleus = nucleus;
return true; return true;
} }
@ -509,52 +507,56 @@ namespace NanoBrain {
} }
/// <summary> /// <summary>
/// Get a nucleus in this cluster /// Get a neuron in this cluster
/// </summary> /// </summary>
/// <param name="nucleusName">The name of the nucleus to find</param> /// <param name="neuronName">The name of the neuron to find</param>
/// <returns>The found nucleus or null when it is not found</returns> /// <returns>The found neuron or null when it is not found</returns>
public Nucleus GetNucleus(string nucleusName) { public Neuron GetNeuron(string neuronName) {
int dotPosition = nucleusName.IndexOf('.'); if (this.nuclei == null)
return null;
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron neuron && neuron.name == neuronName)
return neuron;
}
return null;
}
/// <summary>
/// Get a subcluster in this cluster
/// </summary>
/// <param name="clusterName">The name of the cluster to find</param>
/// <returns>The found cluster or null when it is not found</returns>
public Cluster GetCluster(string clusterName) {
int dotPosition = clusterName.IndexOf('.');
if (dotPosition >= 0) { if (dotPosition >= 0) {
string clusterName = nucleusName[..dotPosition]; string clusterBaseName = clusterName[..dotPosition];
string clusterName0 = clusterName + ": 0"; string clusterName0 = clusterBaseName + ": 0";
foreach (Nucleus nucleus in this.nuclei) { foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Cluster cluster) { if (nucleus is Cluster cluster) {
if (cluster.name == clusterName || cluster.name == clusterName0) { if (cluster.name == clusterBaseName || cluster.name == clusterName0) {
string subNucleusName = nucleusName[(dotPosition + 1)..]; // cluster.CheckInstances();
return cluster.GetNucleus(subNucleusName); string subNucleusName = clusterName[(dotPosition + 1)..];
return cluster.GetCluster(subNucleusName);
} }
} }
} }
return null; return null;
} }
else { else {
string nucleusName0 = nucleusName + ": 0"; string nucleusName0 = clusterName + ": 0";
foreach (Nucleus nucleus in this.nuclei) { foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Cluster) { if (nucleus is Cluster cluster) {
if (nucleus.name == nucleusName || nucleus.name == nucleusName0) if (nucleus.name == clusterName || nucleus.name == nucleusName0) {
return nucleus; // cluster.CheckInstances();
return cluster;
}
} }
else if (nucleus.name == nucleusName)
return nucleus;
} }
return null; return null;
} }
} }
/// <summary>
/// Get a neuron in this cluster
/// </summary>
/// <param name="neuronName">The name of the neuron to find</param>
/// <returns>The found neuron or null when it is not found</returns>
public Neuron GetNeuron(string neuronName) {
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron neuron && neuron.name == neuronName)
return neuron;
}
return null;
}
/// <summary> /// <summary>
/// Get a neuron in an instance of a multi-cluster /// Get a neuron in an instance of a multi-cluster
/// </summary> /// </summary>
@ -568,6 +570,7 @@ namespace NanoBrain {
return this.GetNeuron(neuronName); return this.GetNeuron(neuronName);
// See if we are already using a cluster for thingId // See if we are already using a cluster for thingId
thingClusters ??= new();
if (thingClusters.TryGetValue(thingId, out Cluster cluster)) if (thingClusters.TryGetValue(thingId, out Cluster cluster))
return cluster.GetNeuron(neuronName); return cluster.GetNeuron(neuronName);

View File

@ -42,6 +42,7 @@ namespace NanoBrain {
/// </summary> /// </summary>
/// The bias which a value which is always added to the combined value of the neuron /// The bias which a value which is always added to the combined value of the neuron
/// It does not have a synapse and therefore no weight of source nucleus /// It does not have a synapse and therefore no weight of source nucleus
[HideInInspector]
public Vector3 bias = Vector3.zero; public Vector3 bias = Vector3.zero;
#region Synapses #region Synapses
@ -111,6 +112,7 @@ namespace NanoBrain {
/// <summary> /// <summary>
/// The type of combinator used for this Neuron /// The type of combinator used for this Neuron
/// </summary> /// </summary>
[HideInInspector]
public CombinatorType combinator = CombinatorType.Sum; public CombinatorType combinator = CombinatorType.Sum;
/// <summary> /// <summary>
@ -130,6 +132,7 @@ namespace NanoBrain {
/// The activation function /// The activation function
/// </summary> /// </summary>
[SerializeField] [SerializeField]
[HideInInspector]
public ActivationType _activator; public ActivationType _activator;
/// <summary> /// <summary>
/// The activation funtion /// The activation funtion
@ -138,145 +141,7 @@ namespace NanoBrain {
get { return _activator; } get { return _activator; }
set { set {
_activator = value; _activator = value;
this.curve = GenerateCurve(); //this.curve = GenerateCurve();
}
}
/// <summary>
/// The curve representing the activation function
/// </summary>
public AnimationCurve curve;
/// <summary>
/// The maximum value of the curve
/// </summary>
public float curveMax = 1.0f;
/// <summary>
/// Generate the curve for the current activation function
/// </summary>
/// <returns>The curve </returns>
public AnimationCurve GenerateCurve() {
switch (this.activator) {
case ActivationType.Linear:
this.curveMax = 1;
return Presets.Linear(1);
case ActivationType.Power:
this.curveMax = 1;
return Presets.Power(2.0f, 1);
case ActivationType.Sqrt:
this.curveMax = 1;
return Presets.Power(0.5f, 1);
case ActivationType.Reciprocal:
this.curveMax = 1 / 0.01f * 1;
return Presets.Reciprocal(1);
case ActivationType.Tanh:
this.curveMax = 1;
return Presets.Tanh(1);
case ActivationType.Binary:
this.curveMax = 1;
return Presets.Binary();
case ActivationType.Normalized:
this.curveMax = 1;
return Presets.Binary();
default:
this.curveMax = 1;
return this.curve;
}
}
/// <summary>
/// The curve presets for the activation functions
/// </summary>
public static class Presets {
/// <summary>
/// The number of samples in the curve
/// </summary>
private const int samples = 32;
/// <summary>
/// Generate a curve for the linear activation function
/// </summary>
/// <param name="weight">The maximum value for the function</param>
/// <returns>The curve preset</returns>
public static AnimationCurve Linear(float weight) {
return AnimationCurve.Linear(0f, 0f, 1000f, weight * 1000);
}
/// <summary>
/// Generate a curve for the power activation function
/// </summary>
/// <param name="exponent">The exponent of the power function</param>
/// <param name="weight">The maximum value for the function</param>
/// <returns>The curve preset</returns>
public static AnimationCurve Power(float exponent, float weight) {
// build keyframes
Keyframe[] keys = new Keyframe[samples];
for (int i = 0; i < samples; i++) {
float t = i / (float)(samples - 1);
float v = Mathf.Pow(t, exponent) * weight;
keys[i] = new Keyframe(t, v);
}
AnimationCurve curve = new(keys);
// set tangent modes for each key to Auto (smooth). Use Linear if you prefer straight segments.
for (int i = 0; i < curve.length; i++) {
AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Auto);
AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Auto);
}
return curve;
}
/// <summary>
/// Generate a curve for the reciprocal activation function
/// </summary>
/// <param name="weight">The maximum value for the function</param>
/// <returns>The curve preset</returns>
public static AnimationCurve Reciprocal(float weight) {
int samples = 128;
float xMin = 0.001f;
float xMax = 1;
var keys = new Keyframe[samples];
for (int i = 0; i < samples; i++) {
float t = i / (float)(samples - 1);
float x = Mathf.Lerp(xMin, xMax, t);
float y = 1f / x * weight;
keys[i] = new Keyframe(x, y);
}
var curve = new AnimationCurve(keys);
for (int i = 0; i < curve.length; i++) {
AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Linear);
AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Linear);
}
return curve;
}
/// <summary>
/// Generate a curve for the tanh activation function
/// </summary>
/// <param name="weight">The maximum value for the function</param>
/// <returns>The curve preset</returns>
public static AnimationCurve Tanh(float weight) {
//int samples = 128;
float xMin = 0.001f;
float xMax = 1;
var keys = new Keyframe[samples];
for (int i = 0; i < samples; i++) {
float t = i / (float)(samples - 1);
float x = Mathf.Lerp(xMin, xMax, t);
float y = MathF.Tanh(x * weight);
keys[i] = new Keyframe(x, y);
}
var curve = new AnimationCurve(keys);
for (int i = 0; i < curve.length; i++) {
AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Linear);
AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Linear);
}
return curve;
}
/// <summary>
/// Generate a curve for the binary activation function
/// </summary>
/// <returns>The curve preset</returns>
public static AnimationCurve Binary() {
return AnimationCurve.Linear(0, 0, 1, 1);
} }
} }
@ -287,6 +152,7 @@ namespace NanoBrain {
/// <summary> /// <summary>
/// The output value of the neuron /// The output value of the neuron
/// </summary> /// </summary>
[HideInInspector]
protected float3 _outputValue; protected float3 _outputValue;
/// <summary> /// <summary>
/// The output value of the neuron /// The output value of the neuron
@ -351,13 +217,13 @@ namespace NanoBrain {
/// <summary> /// <summary>
/// True when the neuron is not persisting and has not be updated for timeToSleep seconds /// True when the neuron is not persisting and has not be updated for timeToSleep seconds
/// </summary> /// </summary>
public virtual bool isSleeping => !persistOutput && (Time.time - this.lastUpdate > this.timeToSleep); public virtual bool isSleeping => !persistOutput && (Time.time - this.lastUpdate > timeToSleep);
/// <summary> /// <summary>
/// Check if the neuron is sleeping. /// Check if the neuron is sleeping.
/// </summary> /// </summary>
/// This will reset the output value if it is sleeping /// This will reset the output value if it is sleeping
public void SleepCheck() { public void SleepCheck() {
if (this.isSleeping) { if (this.isSleeping && this.outputSqrMagnitude > 0) {
#if UNITY_MATHEMATICS #if UNITY_MATHEMATICS
this._outputValue = new float3(0, 0, 0); this._outputValue = new float3(0, 0, 0);
#else #else
@ -369,11 +235,14 @@ namespace NanoBrain {
/// <summary> /// <summary>
/// The time at which the last update has been done /// The time at which the last update has been done
/// </summary> /// </summary>
[HideInInspector]
public float lastUpdate = 0; public float lastUpdate = 0;
/// <summary> /// <summary>
/// Time in seconds after the last update the neuron can go to sleep /// Time in seconds after the last update the neuron can go to sleep
/// </summary> /// </summary>
public readonly float timeToSleep = 1f; public static readonly float timeToSleep = 0.5f;
public bool breakOnUpdate = false;
/// \copydoc NanoBrain::Nucleus::ShallowCloneTo /// \copydoc NanoBrain::Nucleus::ShallowCloneTo
public override Nucleus ShallowCloneTo(Cluster parent) { public override Nucleus ShallowCloneTo(Cluster parent) {
@ -392,9 +261,8 @@ namespace NanoBrain {
clone.bias = this.bias; clone.bias = this.bias;
clone.persistOutput = this.persistOutput; clone.persistOutput = this.persistOutput;
clone.combinator = this.combinator; clone.combinator = this.combinator;
clone.curve = this.curve;
clone.activator = this.activator; clone.activator = this.activator;
clone.curveMax = this.curveMax; clone.breakOnUpdate = this.breakOnUpdate;
} }
/// <summary> /// <summary>
@ -445,6 +313,9 @@ namespace NanoBrain {
/// \copydoc NanoBrain::Nucleus::UpdateStateIsolated /// \copydoc NanoBrain::Nucleus::UpdateStateIsolated
public override void UpdateStateIsolated() { public override void UpdateStateIsolated() {
if (breakOnUpdate) {
Debug.Break();
}
var combination = Combinator(this.bias, this.synapses); var combination = Combinator(this.bias, this.synapses);
this.outputValue = Activator(combination); this.outputValue = Activator(combination);
this.lastUpdate = Time.time; this.lastUpdate = Time.time;
@ -731,6 +602,7 @@ namespace NanoBrain {
/// The nuclei which have a synapse to this neuron /// The nuclei which have a synapse to this neuron
/// </summary> /// </summary>
[SerializeReference] [SerializeReference]
[HideInInspector]
private List<Nucleus> _receivers = new(); private List<Nucleus> _receivers = new();
/// <summary> /// <summary>
/// The nuclei which have a synapse to this neuron /// The nuclei which have a synapse to this neuron

View File

@ -14,12 +14,14 @@ namespace NanoBrain {
/// <summary> /// <summary>
/// The name of the Nucleus /// The name of the Nucleus
/// </summary> /// </summary>
[HideInInspector]
public string name; public string name;
/// <summary> /// <summary>
/// The cluster instance in which the nucleus is located /// The cluster instance in which the nucleus is located
/// </summary> /// </summary>
[SerializeReference] [SerializeReference]
[HideInInspector]
public Cluster parent; public Cluster parent;
/// <summary> /// <summary>

View File

@ -14,14 +14,8 @@ namespace NanoBrain.Unity {
/// </summary> /// </summary>
public Cluster cluster; public Cluster cluster;
/// <summary> //[HideInInspector]
/// Retrieve a nucleus in this cluster public int version;
/// </summary>
/// <param name="nucleusName">The name of the nucleus</param>
/// <returns>The Nucleus with the given name or null if no such Nucleus could be found</returns>
public Nucleus GetNucleus(string nucleusName) {
return cluster.GetNucleus(nucleusName);
}
/// <summary> /// <summary>
/// Call this function to ensure that there is at least one nucleus /// Call this function to ensure that there is at least one nucleus
@ -36,6 +30,12 @@ namespace NanoBrain.Unity {
new Neuron(this.cluster, "Output"); // Every cluster should have at least 1 neuron new Neuron(this.cluster, "Output"); // Every cluster should have at least 1 neuron
this.cluster.instanceCount = 1; this.cluster.instanceCount = 1;
} }
#if UNITY_EDITOR
private void OnValidate() {
version++;
}
#endif
} }
} }

View File

@ -15,7 +15,7 @@ namespace NanoBrain.Braitenberg {
protected override float SampleSensor() { protected override float SampleSensor() {
float sum = 0f; float sum = 0f;
// Get all active lights in scene (Point lights only) // Get all active lights in scene (Point lights only)
Light[] lights = FindObjectsByType<Light>(); Light[] lights = FindObjectsByType<Light>(FindObjectsSortMode.None);
Vector3 pos = transform.position; Vector3 pos = transform.position;
Vector3 forward = transform.forward; Vector3 forward = transform.forward;

View File

@ -1,2 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: fbdba2c00e2271d7eae755fa49a7958c guid: fbdba2c00e2271d7eae755fa49a7958c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -10,7 +10,7 @@ namespace NanoBrain.Braitenberg {
public WheelCollider wheelCollider; public WheelCollider wheelCollider;
protected ClusterPrefab brain; protected Cluster brain;
public Neuron motorNeuron; public Neuron motorNeuron;
protected virtual void Awake() { protected virtual void Awake() {
@ -18,7 +18,7 @@ namespace NanoBrain.Braitenberg {
if (vehicle != null) if (vehicle != null)
brain = vehicle.brain; brain = vehicle.brain;
if (brain != null) if (brain != null)
motorNeuron = brain.GetNucleus(outputNeuronName) as Neuron; motorNeuron = brain.GetNeuron(outputNeuronName);
wheelCollider = GetComponent<WheelCollider>(); wheelCollider = GetComponent<WheelCollider>();
} }

View File

@ -1,2 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 07c6bf9674b9f9f0bbbf4a37f570ef4d guid: 07c6bf9674b9f9f0bbbf4a37f570ef4d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -25,7 +25,7 @@ namespace NanoBrain.Braitenberg {
public float _output; public float _output;
protected Vehicle vehicle; protected Vehicle vehicle;
protected ClusterPrefab brain; protected Cluster brain;
public Neuron sensoryNeuron; public Neuron sensoryNeuron;
protected virtual void Awake() { protected virtual void Awake() {
@ -34,7 +34,7 @@ namespace NanoBrain.Braitenberg {
if (vehicle != null) if (vehicle != null)
brain = vehicle.brain; brain = vehicle.brain;
if (brain != null) if (brain != null)
sensoryNeuron = brain.GetNucleus(this.name) as Neuron; sensoryNeuron = brain.GetNeuron(this.name);
} }
void OnEnable() => StartCoroutine(SampleRoutine()); void OnEnable() => StartCoroutine(SampleRoutine());

View File

@ -1,2 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: b3d0457beca5df1bba076ac65df90b59 guid: b3d0457beca5df1bba076ac65df90b59
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -4,7 +4,7 @@ namespace NanoBrain.Braitenberg {
[RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(Rigidbody))]
public class Vehicle : MonoBehaviour { public class Vehicle : MonoBehaviour {
public Unity.ClusterPrefab brain; public Cluster brain;
[Header("Motors")] [Header("Motors")]
public Motor motorLeft; public Motor motorLeft;

View File

@ -1,2 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a8d53357b6673864d91bbc5c595d48b9 guid: a8d53357b6673864d91bbc5c595d48b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: