Compare commits
No commits in common. "133804a154650e543cf9a8db553a20193e41a894" and "37261bdce63aef42e9f984837e0b1cab671573b3" have entirely different histories.
133804a154
...
37261bdce6
75
Editor/Brain_Editor.cs
Normal file
75
Editor/Brain_Editor.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 18e075a03ca2efdb2895079f63eb333a
|
guid: f05072314d39990639a2dbf99f322664
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@ -76,15 +76,8 @@ namespace NanoBrain.Unity {
|
|||||||
margin = new RectOffset(10, 0, 4, 4)
|
margin = new RectOffset(10, 0, 4, 4)
|
||||||
};
|
};
|
||||||
// Nucleus type
|
// Nucleus type
|
||||||
System.Type nucleusType = this.view.currentNucleus.GetType();
|
string nucleusType = this.view.currentNucleus.GetType().Name;
|
||||||
if (nucleusType == typeof(Cluster)) {
|
GUILayout.Label(nucleusType, headerStyle);
|
||||||
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);
|
||||||
@ -311,10 +304,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;
|
||||||
|
|||||||
129
Editor/ClusterPrefab_Drawer.cs
Normal file
129
Editor/ClusterPrefab_Drawer.cs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
2
Editor/ClusterPrefab_Drawer.cs.meta
Normal file
2
Editor/ClusterPrefab_Drawer.cs.meta
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5f43b401b7d59dec7ac7d493cbc4927d
|
||||||
@ -7,13 +7,6 @@ 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;
|
||||||
@ -24,17 +17,17 @@ namespace NanoBrain.Unity {
|
|||||||
}
|
}
|
||||||
public Mode mode = Mode.Focus;
|
public Mode mode = Mode.Focus;
|
||||||
|
|
||||||
public static readonly Dictionary<string, ClusterView> clusterViews = new();
|
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.GetInstanceID();//GetEntityId();
|
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetEntityId();
|
||||||
if (!clusterViews.TryGetValue(key, out ClusterView clusterView))
|
if (!clusterViews.TryGetValue(key, out ClusterView clusterView))
|
||||||
clusterView = new(key);// { key = key };
|
clusterView = new() { key = key };
|
||||||
return clusterView;
|
return clusterView;
|
||||||
}
|
}
|
||||||
public static ClusterView GetClusterView(SerializedObject serializedObject) {
|
public static ClusterView GetClusterView(SerializedObject serializedObject) {
|
||||||
string key = serializedObject.targetObject.GetInstanceID().ToString(); //GetEntityId().ToString();
|
string key = serializedObject.targetObject.GetEntityId().ToString();
|
||||||
if (!clusterViews.TryGetValue(key, out ClusterView clusterView))
|
if (!clusterViews.TryGetValue(key, out ClusterView clusterView))
|
||||||
clusterView = new(key); // { key = key };
|
clusterView = new() { key = key };
|
||||||
return clusterView;
|
return clusterView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +37,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 || clusterView.currentCluster != cluster) {
|
if (clusterView.currentCluster == null) {
|
||||||
clusterView.currentCluster = cluster;
|
clusterView.currentCluster = cluster;
|
||||||
clusterView.currentNucleus = cluster.defaultOutput;
|
clusterView.currentNucleus = cluster.defaultOutput;
|
||||||
clusterView.selectedOutput = clusterView.currentNucleus;
|
clusterView.selectedOutput = clusterView.currentNucleus;
|
||||||
@ -53,7 +46,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 || clusterView.currentCluster != cluster) {
|
if (clusterView.currentCluster == null) {
|
||||||
clusterView.currentCluster = cluster;
|
clusterView.currentCluster = cluster;
|
||||||
clusterView.currentNucleus = cluster.defaultOutput;
|
clusterView.currentNucleus = cluster.defaultOutput;
|
||||||
clusterView.selectedOutput = clusterView.currentNucleus;
|
clusterView.selectedOutput = clusterView.currentNucleus;
|
||||||
@ -108,8 +101,6 @@ 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
|
||||||
|
|
||||||
@ -208,7 +199,7 @@ namespace NanoBrain.Unity {
|
|||||||
DrawOutputs(position);
|
DrawOutputs(position);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dag dag = GenerateGraph(this.selectedOutput);
|
Dag dag = GenerateGraph(this.selectedOutput);
|
||||||
Dag.ComputeLayout(dag);
|
Dag.ComputeLayout(dag);
|
||||||
|
|
||||||
@ -276,9 +267,6 @@ 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) {
|
||||||
@ -319,7 +307,7 @@ namespace NanoBrain.Unity {
|
|||||||
if (nucleus == this.selectedOutput) {
|
if (nucleus == this.selectedOutput) {
|
||||||
// Add link to 'Outpus'
|
// Add link to 'Outpus'
|
||||||
nodeCount++;
|
nodeCount++;
|
||||||
if (ClusterView.previousPrefab != null && ClusterView.previousPrefab != nucleus.parent.prefab)
|
if (ClusterViewer.previousPrefab != null)
|
||||||
// Add link to previous editor
|
// Add link to previous editor
|
||||||
nodeCount++;
|
nodeCount++;
|
||||||
}
|
}
|
||||||
@ -353,9 +341,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 (ClusterView.previousPrefab != null && ClusterView.previousPrefab != nucleus.parent.prefab) {
|
if (ClusterViewer.previousPrefab != null) {
|
||||||
DrawEdge(parentPos, pos);
|
DrawEdge(parentPos, pos);
|
||||||
DrawClusterPrefab(ClusterView.previousPrefab, pos);
|
DrawClusterPrefab(ClusterViewer.previousPrefab, pos);
|
||||||
row++;
|
row++;
|
||||||
}
|
}
|
||||||
pos = new(50, margin + row * spacing, 0);
|
pos = new(50, margin + row * spacing, 0);
|
||||||
@ -445,7 +433,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.GetNeuron(nucleus.name);
|
Neuron siblingNeuron = sibling.GetNucleus(nucleus.name) as Neuron;
|
||||||
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;
|
||||||
@ -456,10 +444,9 @@ 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.GetNeuron(nucleus.name);
|
Neuron siblingNeuron = sibling.GetNucleus(nucleus.name) as Neuron;
|
||||||
position = new(250, margin + row * spacing, 0.0f);
|
Vector3 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) {
|
||||||
@ -469,16 +456,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,
|
||||||
};
|
};
|
||||||
Handles.Label(labelPos, name, style);
|
Vector3 labelPos = position - Vector3.down * (discRadius + 5); // below neuron
|
||||||
|
string name = $"{sibling.baseName}\n{nucleus.name}";
|
||||||
|
Handles.Label(labelPos, name, style);
|
||||||
|
row++;
|
||||||
|
}
|
||||||
expandArray = false;
|
expandArray = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -536,6 +523,7 @@ 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;
|
||||||
@ -550,6 +538,7 @@ 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;
|
||||||
@ -707,7 +696,7 @@ namespace NanoBrain.Unity {
|
|||||||
e.Use();
|
e.Use();
|
||||||
Selection.activeObject = prefab;
|
Selection.activeObject = prefab;
|
||||||
EditorGUIUtility.PingObject(prefab);
|
EditorGUIUtility.PingObject(prefab);
|
||||||
ClusterView.previousPrefab = null;
|
ClusterViewer.previousPrefab = null;
|
||||||
Editor.CreateEditor(prefab);
|
Editor.CreateEditor(prefab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -773,16 +762,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
|
||||||
@ -803,7 +792,6 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -811,7 +799,6 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -820,7 +807,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);
|
||||||
ClusterView.previousPrefab = this.currentCluster.prefab;
|
ClusterViewer.previousPrefab = this.currentCluster.prefab;
|
||||||
Editor.CreateEditor(subCluster.prefab);
|
Editor.CreateEditor(subCluster.prefab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,2 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 8a7663ccd347fd78dbdba393c03ed7c7
|
guid: 8a7663ccd347fd78dbdba393c03ed7c7
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
902
Editor/ClusterViewer.cs
Normal file
902
Editor/ClusterViewer.cs
Normal file
@ -0,0 +1,902 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
11
Editor/ClusterViewer.cs.meta
Normal file
11
Editor/ClusterViewer.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4fe58945c76d153edacc220597474ad2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@ -1,308 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +1,2 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: a755ac8461bd0c714a852df47331048e
|
guid: a755ac8461bd0c714a852df47331048e
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
85
Editor/Neuron_Drawer.cs
Normal file
85
Editor/Neuron_Drawer.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
Editor/Neuron_Drawer.cs.meta
Normal file
11
Editor/Neuron_Drawer.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aa0e340763ca6299e93d514b271ae38d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
69
Runtime/Scripts/Brain.cs
Normal file
69
Runtime/Scripts/Brain.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
11
Runtime/Scripts/Brain.cs.meta
Normal file
11
Runtime/Scripts/Brain.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 92f34a5e4027a1dc39efd8ce63cf6aba
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@ -24,9 +24,6 @@ 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>
|
||||||
@ -45,9 +42,7 @@ 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>
|
||||||
@ -67,8 +62,11 @@ 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
|
||||||
|
|
||||||
@ -79,12 +77,13 @@ 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>
|
||||||
@ -94,12 +93,13 @@ 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,10 +108,6 @@ 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
|
||||||
@ -165,30 +161,36 @@ namespace NanoBrain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Nucleus clonedNucleus in clonedNuclei) {
|
if (Application.isPlaying) {
|
||||||
if (clonedNucleus is not Cluster clonedCluster)
|
// Only create cluster siblings at runtime
|
||||||
continue;
|
foreach (Nucleus clonedNucleus in clonedNuclei) {
|
||||||
|
if (clonedNucleus is not Cluster clonedCluster)
|
||||||
|
continue;
|
||||||
|
|
||||||
List<Cluster> siblings = new() { clonedCluster };
|
List<Cluster> siblings = new() {
|
||||||
for (int instanceIx = 1; instanceIx < clonedCluster.instanceCount; instanceIx++) {
|
clonedCluster
|
||||||
// Create another sibling
|
|
||||||
Cluster sibling = new(clonedCluster.prefab, this) {
|
|
||||||
name = $"{clonedCluster.baseName}: {instanceIx}",
|
|
||||||
parent = this.parent,
|
|
||||||
instanceCount = this.instanceCount,
|
|
||||||
};
|
};
|
||||||
siblings.Add(sibling);
|
for (int instanceIx = 1; instanceIx < clonedCluster.instanceCount; instanceIx++) {
|
||||||
CopyAllExternalReceivers(clonedCluster, sibling, this);
|
// Create another sibling
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,11 +202,13 @@ 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, Cluster clonedParent) {
|
private static void CopyAllExternalReceivers(Cluster sourceCluster, Cluster sibling, ClusterPrefab prefabParent, 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)
|
||||||
@ -233,7 +237,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()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,7 +460,7 @@ namespace NanoBrain {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The neurons without outgoing connections
|
/// The neurons without outgoing connections
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -496,8 +500,6 @@ 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;
|
||||||
}
|
}
|
||||||
@ -507,56 +509,52 @@ namespace NanoBrain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a neuron in this cluster
|
/// Get a nucleus in this cluster
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="neuronName">The name of the neuron to find</param>
|
/// <param name="nucleusName">The name of the nucleus to find</param>
|
||||||
/// <returns>The found neuron or null when it is not found</returns>
|
/// <returns>The found nucleus or null when it is not found</returns>
|
||||||
public Neuron GetNeuron(string neuronName) {
|
public Nucleus GetNucleus(string nucleusName) {
|
||||||
if (this.nuclei == null)
|
int dotPosition = nucleusName.IndexOf('.');
|
||||||
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 clusterBaseName = clusterName[..dotPosition];
|
string clusterName = nucleusName[..dotPosition];
|
||||||
string clusterName0 = clusterBaseName + ": 0";
|
string clusterName0 = clusterName + ": 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 == clusterBaseName || cluster.name == clusterName0) {
|
if (cluster.name == clusterName || cluster.name == clusterName0) {
|
||||||
// cluster.CheckInstances();
|
string subNucleusName = nucleusName[(dotPosition + 1)..];
|
||||||
string subNucleusName = clusterName[(dotPosition + 1)..];
|
return cluster.GetNucleus(subNucleusName);
|
||||||
return cluster.GetCluster(subNucleusName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
string nucleusName0 = clusterName + ": 0";
|
string nucleusName0 = nucleusName + ": 0";
|
||||||
foreach (Nucleus nucleus in this.nuclei) {
|
foreach (Nucleus nucleus in this.nuclei) {
|
||||||
if (nucleus is Cluster cluster) {
|
if (nucleus is Cluster) {
|
||||||
if (nucleus.name == clusterName || nucleus.name == nucleusName0) {
|
if (nucleus.name == nucleusName || nucleus.name == nucleusName0)
|
||||||
// cluster.CheckInstances();
|
return nucleus;
|
||||||
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>
|
||||||
@ -570,7 +568,6 @@ 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);
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,6 @@ 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
|
||||||
@ -112,7 +111,6 @@ 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>
|
||||||
@ -132,7 +130,6 @@ 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
|
||||||
@ -141,7 +138,145 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +287,6 @@ 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
|
||||||
@ -217,13 +351,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 > timeToSleep);
|
public virtual bool isSleeping => !persistOutput && (Time.time - this.lastUpdate > this.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 && this.outputSqrMagnitude > 0) {
|
if (this.isSleeping) {
|
||||||
#if UNITY_MATHEMATICS
|
#if UNITY_MATHEMATICS
|
||||||
this._outputValue = new float3(0, 0, 0);
|
this._outputValue = new float3(0, 0, 0);
|
||||||
#else
|
#else
|
||||||
@ -235,14 +369,11 @@ 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 static readonly float timeToSleep = 0.5f;
|
public readonly float timeToSleep = 1f;
|
||||||
|
|
||||||
public bool breakOnUpdate = false;
|
|
||||||
|
|
||||||
/// \copydoc NanoBrain::Nucleus::ShallowCloneTo
|
/// \copydoc NanoBrain::Nucleus::ShallowCloneTo
|
||||||
public override Nucleus ShallowCloneTo(Cluster parent) {
|
public override Nucleus ShallowCloneTo(Cluster parent) {
|
||||||
@ -261,8 +392,9 @@ 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.breakOnUpdate = this.breakOnUpdate;
|
clone.curveMax = this.curveMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -313,9 +445,6 @@ 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;
|
||||||
@ -602,7 +731,6 @@ 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
|
||||||
|
|||||||
@ -14,14 +14,12 @@ 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>
|
||||||
|
|||||||
@ -14,8 +14,14 @@ namespace NanoBrain.Unity {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Cluster cluster;
|
public Cluster cluster;
|
||||||
|
|
||||||
//[HideInInspector]
|
/// <summary>
|
||||||
public int version;
|
/// Retrieve a nucleus in this cluster
|
||||||
|
/// </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
|
||||||
@ -30,12 +36,6 @@ 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>(FindObjectsSortMode.None);
|
Light[] lights = FindObjectsByType<Light>();
|
||||||
Vector3 pos = transform.position;
|
Vector3 pos = transform.position;
|
||||||
Vector3 forward = transform.forward;
|
Vector3 forward = transform.forward;
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,2 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: fbdba2c00e2271d7eae755fa49a7958c
|
guid: fbdba2c00e2271d7eae755fa49a7958c
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -10,7 +10,7 @@ namespace NanoBrain.Braitenberg {
|
|||||||
|
|
||||||
public WheelCollider wheelCollider;
|
public WheelCollider wheelCollider;
|
||||||
|
|
||||||
protected Cluster brain;
|
protected ClusterPrefab 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.GetNeuron(outputNeuronName);
|
motorNeuron = brain.GetNucleus(outputNeuronName) as Neuron;
|
||||||
wheelCollider = GetComponent<WheelCollider>();
|
wheelCollider = GetComponent<WheelCollider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,2 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 07c6bf9674b9f9f0bbbf4a37f570ef4d
|
guid: 07c6bf9674b9f9f0bbbf4a37f570ef4d
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -25,7 +25,7 @@ namespace NanoBrain.Braitenberg {
|
|||||||
public float _output;
|
public float _output;
|
||||||
|
|
||||||
protected Vehicle vehicle;
|
protected Vehicle vehicle;
|
||||||
protected Cluster brain;
|
protected ClusterPrefab 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.GetNeuron(this.name);
|
sensoryNeuron = brain.GetNucleus(this.name) as Neuron;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnEnable() => StartCoroutine(SampleRoutine());
|
void OnEnable() => StartCoroutine(SampleRoutine());
|
||||||
|
|||||||
@ -1,11 +1,2 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: b3d0457beca5df1bba076ac65df90b59
|
guid: b3d0457beca5df1bba076ac65df90b59
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -4,7 +4,7 @@ namespace NanoBrain.Braitenberg {
|
|||||||
|
|
||||||
[RequireComponent(typeof(Rigidbody))]
|
[RequireComponent(typeof(Rigidbody))]
|
||||||
public class Vehicle : MonoBehaviour {
|
public class Vehicle : MonoBehaviour {
|
||||||
public Cluster brain;
|
public Unity.ClusterPrefab brain;
|
||||||
|
|
||||||
[Header("Motors")]
|
[Header("Motors")]
|
||||||
public Motor motorLeft;
|
public Motor motorLeft;
|
||||||
|
|||||||
@ -1,11 +1,2 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: a8d53357b6673864d91bbc5c595d48b9
|
guid: a8d53357b6673864d91bbc5c595d48b9
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
Loading…
x
Reference in New Issue
Block a user