Scriptable Object nanobrain editor
This commit is contained in:
parent
69c6b1e2b7
commit
5a9aa7161b
@ -48,6 +48,7 @@
|
|||||||
<Analyzer Include="/home/pascal/Unity/Hub/Editor/6000.2.13f1/Editor/Data/Tools/Unity.SourceGenerators/Unity.UIToolkit.SourceGenerator.dll" />
|
<Analyzer Include="/home/pascal/Unity/Hub/Editor/6000.2.13f1/Editor/Data/Tools/Unity.SourceGenerators/Unity.UIToolkit.SourceGenerator.dll" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Assets/NanoBrain/VisualEditor/Editor/NanoBrainEditor.cs" />
|
||||||
<Compile Include="Assets/NanoBrain/Editor/NanoBrain_Editor.cs" />
|
<Compile Include="Assets/NanoBrain/Editor/NanoBrain_Editor.cs" />
|
||||||
<Compile Include="Assets/NanoBrain/Editor/NeuroidWindow.cs" />
|
<Compile Include="Assets/NanoBrain/Editor/NeuroidWindow.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@ -48,6 +48,7 @@
|
|||||||
<Analyzer Include="/home/pascal/Unity/Hub/Editor/6000.2.13f1/Editor/Data/Tools/Unity.SourceGenerators/Unity.UIToolkit.SourceGenerator.dll" />
|
<Analyzer Include="/home/pascal/Unity/Hub/Editor/6000.2.13f1/Editor/Data/Tools/Unity.SourceGenerators/Unity.UIToolkit.SourceGenerator.dll" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Assets/NanoBrain/VisualEditor/NanoBrainObj.cs" />
|
||||||
<Compile Include="Assets/Scenes/Boids/Scripts/SwarmSpawner.cs" />
|
<Compile Include="Assets/Scenes/Boids/Scripts/SwarmSpawner.cs" />
|
||||||
<Compile Include="Assets/NanoBrain/NeuroidBehaviour.cs" />
|
<Compile Include="Assets/NanoBrain/NeuroidBehaviour.cs" />
|
||||||
<Compile Include="Assets/NanoBrain/NanoBrain.cs" />
|
<Compile Include="Assets/NanoBrain/NanoBrain.cs" />
|
||||||
@ -57,9 +58,14 @@
|
|||||||
<Compile Include="Assets/NanoBrain/Perception.cs" />
|
<Compile Include="Assets/NanoBrain/Perception.cs" />
|
||||||
<Compile Include="Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs" />
|
<Compile Include="Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs" />
|
||||||
<Compile Include="Assets/Scenes/Boids/Scripts/Boid.cs" />
|
<Compile Include="Assets/Scenes/Boids/Scripts/Boid.cs" />
|
||||||
|
<Compile Include="Assets/NanoBrain/VisualEditor/NucleusObject.cs" />
|
||||||
<Compile Include="Assets/NanoBrain/Neuroid.cs" />
|
<Compile Include="Assets/NanoBrain/Neuroid.cs" />
|
||||||
|
<Compile Include="Assets/NanoBrain/VisualEditor/NeuroidObject.cs" />
|
||||||
<Compile Include="Assets/NanoBrain/Nucleus.cs" />
|
<Compile Include="Assets/NanoBrain/Nucleus.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="Assets/NanoBrain/VisualEditor/Resources/GraphStyles.uss" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="UnityEngine">
|
<Reference Include="UnityEngine">
|
||||||
<HintPath>/home/pascal/Unity/Hub/Editor/6000.2.13f1/Editor/Data/Managed/UnityEngine/UnityEngine.dll</HintPath>
|
<HintPath>/home/pascal/Unity/Hub/Editor/6000.2.13f1/Editor/Data/Managed/UnityEngine/UnityEngine.dll</HintPath>
|
||||||
|
|||||||
@ -13,6 +13,15 @@ public class NanoBrain_Editor : Editor {
|
|||||||
#region Start
|
#region Start
|
||||||
|
|
||||||
private void OnEnable() {
|
private void OnEnable() {
|
||||||
|
// NanoBrain brain = target as NanoBrain;
|
||||||
|
// if (brain == null)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// if (brain.rootNucleus == null) {
|
||||||
|
// brain.rootNucleus = CreateInstance<NucleusObj>();
|
||||||
|
// EditorUtility.SetDirty(brain.rootNucleus);
|
||||||
|
// }
|
||||||
|
|
||||||
SelectNeuron();
|
SelectNeuron();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +53,6 @@ public class NanoBrain_Editor : Editor {
|
|||||||
|
|
||||||
DrawGraph();
|
DrawGraph();
|
||||||
|
|
||||||
//DrawDefaultInspector();
|
|
||||||
EditorGUILayout.TextField("Name", currentNucleus.name);
|
EditorGUILayout.TextField("Name", currentNucleus.name);
|
||||||
EditorGUILayout.Vector3Field("Output Value", currentNucleus.outputValue);
|
EditorGUILayout.Vector3Field("Output Value", currentNucleus.outputValue);
|
||||||
if (currentNucleus.synapses.Count > 0) {
|
if (currentNucleus.synapses.Count > 0) {
|
||||||
@ -62,6 +70,7 @@ public class NanoBrain_Editor : Editor {
|
|||||||
}
|
}
|
||||||
EditorGUI.indentLevel--;
|
EditorGUI.indentLevel--;
|
||||||
}
|
}
|
||||||
|
DrawDefaultInspector();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildLayers() {
|
private void BuildLayers() {
|
||||||
|
|||||||
@ -40,7 +40,7 @@ public class GraphEditorWindow : EditorWindow {
|
|||||||
return;
|
return;
|
||||||
NeuroidLayer currentLayer = new() { ix = layerIx };
|
NeuroidLayer currentLayer = new() { ix = layerIx };
|
||||||
|
|
||||||
foreach (Neuroid outputNeuroid in selectedNucleus.receivers) {
|
foreach (Nucleus outputNeuroid in selectedNucleus.receivers) {
|
||||||
if (outputNeuroid != null) {
|
if (outputNeuroid != null) {
|
||||||
AddToLayer(currentLayer, outputNeuroid);
|
AddToLayer(currentLayer, outputNeuroid);
|
||||||
Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}");
|
Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}");
|
||||||
|
|||||||
@ -2,6 +2,9 @@ using System.Collections.Generic;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
public class NanoBrain : MonoBehaviour {
|
public class NanoBrain : MonoBehaviour {
|
||||||
|
|
||||||
|
// public NucleusObj rootNucleus;
|
||||||
|
|
||||||
public List<Neuroid> neuroids = new();
|
public List<Neuroid> neuroids = new();
|
||||||
|
|
||||||
public Neuroid AddNeuron(string name) {
|
public Neuroid AddNeuron(string name) {
|
||||||
|
|||||||
@ -13,6 +13,14 @@ public class Neuroid : Nucleus {
|
|||||||
Debug.LogError("No neuroid network");
|
Debug.LogError("No neuroid network");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Neuroid(NanoBrainObj brain, string name) : base(name) {
|
||||||
|
this.newBrain = brain;
|
||||||
|
if (this.newBrain != null)
|
||||||
|
this.newBrain.neuroids.Add(this);
|
||||||
|
else
|
||||||
|
Debug.LogError("No neuroid network");
|
||||||
|
}
|
||||||
|
|
||||||
public void SetWeight(Neuroid input, float weight) {
|
public void SetWeight(Neuroid input, float weight) {
|
||||||
this.synapses[input] = weight;
|
this.synapses[input] = weight;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,12 @@ public class Nucleus {
|
|||||||
public int stale = 0;
|
public int stale = 0;
|
||||||
|
|
||||||
public NanoBrain brain { get; protected set; }
|
public NanoBrain brain { get; protected set; }
|
||||||
|
public NanoBrainObj newBrain { get; protected set; }
|
||||||
|
|
||||||
public virtual string name { get; set; }
|
public virtual string name { get; set; }
|
||||||
|
|
||||||
public readonly Dictionary<Nucleus, float> synapses = new();
|
public readonly Dictionary<Nucleus, float> synapses = new();
|
||||||
public HashSet<Neuroid> receivers = new();
|
public HashSet<Nucleus> receivers = new();
|
||||||
public virtual Vector3 outputValue { get; set; }
|
public virtual Vector3 outputValue { get; set; }
|
||||||
|
|
||||||
public int layerIx;
|
public int layerIx;
|
||||||
@ -18,7 +19,7 @@ public class Nucleus {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void AddReceiver(Neuroid receiver) {
|
public virtual void AddReceiver(Nucleus receiver) {
|
||||||
//Debug.Log($"add receiver to {this} for {receiver} {receiver.GetHashCode()} {this.receivers.Count} {receiver.synapses.Count}");
|
//Debug.Log($"add receiver to {this} for {receiver} {receiver.GetHashCode()} {this.receivers.Count} {receiver.synapses.Count}");
|
||||||
this.receivers.Add(receiver);
|
this.receivers.Add(receiver);
|
||||||
receiver.synapses[this] = 1.0f; // new(this);
|
receiver.synapses[this] = 1.0f; // new(this);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 895037c7e323e03ada7f43d011f1390b
|
guid: 62a58c801eda0c9eab7a49fb1d0840cb
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: e5398245724c5668992c64c27db35040
|
guid: e47ea55fc051fcdcb8ae6197d1105cc0
|
||||||
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
userData:
|
userData:
|
||||||
477
Assets/NanoBrain/VisualEditor/Editor/NanoBrainEditor.cs
Normal file
477
Assets/NanoBrain/VisualEditor/Editor/NanoBrainEditor.cs
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class NucleusLayer {
|
||||||
|
public int ix = 0;
|
||||||
|
public List<Nucleus> neuroids = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NanoBrainEditor : EditorWindow {
|
||||||
|
public static NanoBrainObj brain;
|
||||||
|
|
||||||
|
public static VisualElement inspectorContainer;
|
||||||
|
|
||||||
|
[MenuItem("Window/NanoBrain Editor")]
|
||||||
|
public static void ShowWindow() {
|
||||||
|
GetWindow<NanoBrainEditor>("NanoBrain Editor");
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphBoardView board;
|
||||||
|
|
||||||
|
private void OnEnable() {
|
||||||
|
if (brain == null) {
|
||||||
|
brain = CreateInstance<NanoBrainObj>();
|
||||||
|
EditorUtility.SetDirty(brain);
|
||||||
|
}
|
||||||
|
|
||||||
|
VisualElement root = rootVisualElement;
|
||||||
|
root.styleSheets.Add(Resources.Load<StyleSheet>("GraphStyles"));
|
||||||
|
|
||||||
|
root.Add(board);
|
||||||
|
root.Add(inspectorContainer);
|
||||||
|
|
||||||
|
VisualElement main = new() {
|
||||||
|
name = "main",
|
||||||
|
style = {
|
||||||
|
flexDirection = FlexDirection.Row,
|
||||||
|
flexGrow = 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
board = new GraphBoardView();
|
||||||
|
board.style.flexGrow = 1;
|
||||||
|
inspectorContainer = new VisualElement {
|
||||||
|
name = "inspector",
|
||||||
|
style = {
|
||||||
|
width = 400
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
main.Add(board);
|
||||||
|
main.Add(inspectorContainer);
|
||||||
|
root.Add(main);
|
||||||
|
|
||||||
|
board.SetGraph(brain.rootNucleus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GraphBoardView : VisualElement {
|
||||||
|
Nucleus currentNucleus;
|
||||||
|
private List<NeuroidLayer> layers = new();
|
||||||
|
private Dictionary<Nucleus, Vector2Int> neuroidPositions = new();
|
||||||
|
|
||||||
|
Vector2 pan = Vector2.zero;
|
||||||
|
//float zoom = 1f;
|
||||||
|
bool draggingCanvas = false;
|
||||||
|
Vector2 lastMouse;
|
||||||
|
GraphNodeWrapper currentWrapper;
|
||||||
|
|
||||||
|
public GraphBoardView() {
|
||||||
|
name = "content";
|
||||||
|
style.flexGrow = 1;
|
||||||
|
|
||||||
|
IMGUIContainer imguiContainer = new(OnIMGUI);
|
||||||
|
imguiContainer.style.position = Position.Absolute;
|
||||||
|
imguiContainer.style.left = 0; imguiContainer.style.top = 0;
|
||||||
|
imguiContainer.style.right = 0; imguiContainer.style.bottom = 0;
|
||||||
|
imguiContainer.pickingMode = PickingMode.Position;
|
||||||
|
imguiContainer.focusable = true;
|
||||||
|
Add(imguiContainer);
|
||||||
|
|
||||||
|
//RegisterCallback<WheelEvent>(OnWheel);
|
||||||
|
RegisterCallback<MouseDownEvent>(OnMouseDown);
|
||||||
|
RegisterCallback<MouseMoveEvent>(OnMouseMove);
|
||||||
|
RegisterCallback<MouseUpEvent>(OnMouseUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetGraph(Nucleus nucleus) {
|
||||||
|
this.currentNucleus = nucleus;
|
||||||
|
Rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rebuild() {
|
||||||
|
BuildLayers();
|
||||||
|
|
||||||
|
if (currentNucleus == null) {
|
||||||
|
NanoBrainEditor.inspectorContainer.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentWrapper != null)
|
||||||
|
Object.DestroyImmediate(currentWrapper);
|
||||||
|
currentWrapper = ScriptableObject.CreateInstance<GraphNodeWrapper>().Init(currentNucleus, NanoBrainEditor.brain);
|
||||||
|
DrawInspector();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildLayers() {
|
||||||
|
// A temporary list to track what's been added to layers
|
||||||
|
this.layers = new();
|
||||||
|
int layerIx = 0;
|
||||||
|
|
||||||
|
Nucleus selectedNucleus = this.currentNucleus;
|
||||||
|
if (selectedNucleus == null)
|
||||||
|
return;
|
||||||
|
NeuroidLayer currentLayer = new() { ix = layerIx };
|
||||||
|
|
||||||
|
foreach (Nucleus outputNeuroid in selectedNucleus.receivers) {
|
||||||
|
if (outputNeuroid != null) {
|
||||||
|
AddToLayer(currentLayer, outputNeuroid);
|
||||||
|
// Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentLayer.neuroids.Count > 0) {
|
||||||
|
this.layers.Add(currentLayer);
|
||||||
|
layerIx++;
|
||||||
|
currentLayer = new() { ix = layerIx };
|
||||||
|
}
|
||||||
|
|
||||||
|
AddToLayer(currentLayer, selectedNucleus);
|
||||||
|
this.layers.Add(currentLayer);
|
||||||
|
// Debug.Log($"layer {layerIx} nucleus {selectedNucleus.name}");
|
||||||
|
|
||||||
|
layerIx++;
|
||||||
|
currentLayer = new() { ix = layerIx };
|
||||||
|
|
||||||
|
foreach (Nucleus input in selectedNucleus.synapses.Keys) {
|
||||||
|
AddToLayer(currentLayer, input);
|
||||||
|
// Debug.Log($"layer {layerIx} nucleus {input.name}");
|
||||||
|
}
|
||||||
|
if (currentLayer.neuroids.Count > 0) {
|
||||||
|
this.layers.Add(currentLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddToLayer(NeuroidLayer layer, Nucleus nucleus) {
|
||||||
|
layer.neuroids.Add(nucleus);
|
||||||
|
nucleus.layerIx = layer.ix;
|
||||||
|
// Store its position
|
||||||
|
Vector2Int neuroidPosition = new(layer.ix, layer.neuroids.Count - 1);
|
||||||
|
neuroidPositions[nucleus] = neuroidPosition;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// basic pan/zoom handling
|
||||||
|
// void OnWheel(WheelEvent e) {
|
||||||
|
// if (e.ctrlKey) {
|
||||||
|
// float delta = -e.delta.y * 0.001f;
|
||||||
|
// zoom = Mathf.Clamp(zoom + delta, 0.25f, 2f);
|
||||||
|
// content.transform.rotation = Quaternion.identity; // keep transform accessible
|
||||||
|
// content.transform.scale = new Vector3(zoom, zoom, 1);
|
||||||
|
// e.StopPropagation();
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// pan += e.delta;
|
||||||
|
// content.style.left = pan.x;
|
||||||
|
// content.style.top = pan.y;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
void OnMouseDown(MouseDownEvent e) {
|
||||||
|
if (e.button == 2) { draggingCanvas = true; lastMouse = e.mousePosition; e.StopPropagation(); }
|
||||||
|
}
|
||||||
|
void OnMouseMove(MouseMoveEvent e) {
|
||||||
|
if (draggingCanvas) {
|
||||||
|
var delta = e.mousePosition - lastMouse;
|
||||||
|
pan += delta;
|
||||||
|
//content.style.left = pan.x;
|
||||||
|
//content.style.top = pan.y;
|
||||||
|
lastMouse = e.mousePosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void OnMouseUp(MouseUpEvent e) { if (e.button == 2) draggingCanvas = false; }
|
||||||
|
|
||||||
|
void OnIMGUI() {
|
||||||
|
if (currentNucleus == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Handles.BeginGUI();
|
||||||
|
foreach (NeuroidLayer layer in layers)
|
||||||
|
DrawLayer(layer);
|
||||||
|
Handles.EndGUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawLayer(NeuroidLayer layer) {
|
||||||
|
int nodeCount = layer.neuroids.Count;
|
||||||
|
float maxValue = 0;
|
||||||
|
foreach (Nucleus nucleus in layer.neuroids) {
|
||||||
|
if (nucleus is Neuroid neuroid) {
|
||||||
|
float value = neuroid.outputValue.magnitude;
|
||||||
|
if (value > maxValue)
|
||||||
|
maxValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float spacing = 400f / nodeCount;
|
||||||
|
float margin = 10 + spacing / 2;
|
||||||
|
foreach (Nucleus layerNucleus in layer.neuroids) {
|
||||||
|
Vector2Int layerNeuroidPos = this.neuroidPositions[layerNucleus];
|
||||||
|
Vector3 parentPos = new(100 + layerNeuroidPos.x * 100, margin + layerNeuroidPos.y * spacing, 0.1f);
|
||||||
|
|
||||||
|
//int i = 0;
|
||||||
|
float inputSpacing = 400f / layerNucleus.synapses.Count;
|
||||||
|
float inputMargin = 10 + inputSpacing / 2;
|
||||||
|
int minStale = 10000;
|
||||||
|
foreach ((Nucleus nucleus, float weight) in layerNucleus.synapses) {
|
||||||
|
if (this.neuroidPositions.ContainsKey(nucleus)) {
|
||||||
|
Vector2Int inputNeuroidPos = this.neuroidPositions[nucleus];
|
||||||
|
if (inputNeuroidPos.x == layerNeuroidPos.x + 1) {
|
||||||
|
Vector3 pos = new(100 + inputNeuroidPos.x * 100, inputMargin + inputNeuroidPos.y * inputSpacing, 0.0f);
|
||||||
|
|
||||||
|
float brightness = weight / 10.0f;
|
||||||
|
Handles.color = new Color(brightness, brightness, brightness);
|
||||||
|
Handles.DrawLine(parentPos, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nucleus is Neuroid neuroid && neuroid.stale < minStale)
|
||||||
|
minStale = neuroid.stale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layerNucleus.synapses.Count > 0 && minStale > 2 && layerNucleus.stale < 3)
|
||||||
|
Debug.LogWarning($"Strange {minStale} is big duing update");
|
||||||
|
|
||||||
|
|
||||||
|
float size = 20;
|
||||||
|
if (layerNucleus.isSleeping)
|
||||||
|
Handles.color = Color.darkRed;
|
||||||
|
else {
|
||||||
|
float brightness = layerNucleus.outputValue.magnitude / maxValue;
|
||||||
|
Handles.color = new Color(brightness, brightness, brightness);
|
||||||
|
}
|
||||||
|
Handles.DrawSolidDisc(parentPos, Vector3.forward, size);
|
||||||
|
Vector3 labelPos = parentPos - Vector3.down * (size + 0.2f); // below disc along up axis
|
||||||
|
GUIStyle style = new GUIStyle(EditorStyles.label) {
|
||||||
|
alignment = TextAnchor.UpperCenter,
|
||||||
|
normal = { textColor = Color.white },
|
||||||
|
fontStyle = FontStyle.Bold
|
||||||
|
};
|
||||||
|
Handles.Label(labelPos, layerNucleus.name, style);
|
||||||
|
|
||||||
|
Rect neuronRect = new(parentPos.x - size, parentPos.y - size, size * 2, size * 2);
|
||||||
|
int id = GUIUtility.GetControlID(FocusType.Passive);
|
||||||
|
Event e = Event.current;
|
||||||
|
EventType et = e.GetTypeForControl(id);
|
||||||
|
if (e != null && neuronRect.Contains(e.mousePosition)) {
|
||||||
|
// Process Hover
|
||||||
|
HandleMouseHover(layerNucleus, neuronRect);
|
||||||
|
// Process click
|
||||||
|
Debug.Log($"{et} {e.type}");
|
||||||
|
if (e.type == EventType.MouseDown && e.button == 0) {
|
||||||
|
// Consume the event so the scene doesn't also handle it
|
||||||
|
e.Use();
|
||||||
|
HandleDiscClicked(layerNucleus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleMouseHover(Nucleus neuroid, Rect rect) {
|
||||||
|
GUIContent tooltip;
|
||||||
|
if (neuroid is SensoryNeuroid sensoryNeuroid) {
|
||||||
|
tooltip = new(
|
||||||
|
$"{sensoryNeuroid.name}" +
|
||||||
|
$"\nThing {sensoryNeuroid.receptor.thingId}" +
|
||||||
|
$"\nValue: {neuroid.outputValue}" +
|
||||||
|
$"\nStale: {neuroid.stale}");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tooltip = new(
|
||||||
|
$"{neuroid.name}" +
|
||||||
|
$"\nsynapse count {neuroid.synapses.Count}" +
|
||||||
|
$"\nValue: {neuroid.outputValue}" +
|
||||||
|
$"\nStale: {neuroid.stale}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 mousePosition = Event.current.mousePosition;
|
||||||
|
|
||||||
|
// Display tooltip with some offset
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDiscClicked(Nucleus nucleus) {
|
||||||
|
this.currentNucleus = nucleus;
|
||||||
|
BuildLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DrawInspector() {
|
||||||
|
NanoBrainEditor.inspectorContainer.Clear();
|
||||||
|
if (this.currentNucleus == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// create a SerializedObject wrapper so Unity inspector controls work (and Undo)
|
||||||
|
SerializedObject so = new SerializedObject(currentWrapper);
|
||||||
|
IMGUIContainer container = new IMGUIContainer(() => {
|
||||||
|
so.Update();
|
||||||
|
currentNucleus.name = EditorGUILayout.TextField(currentNucleus.name);
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
EditorGUILayout.LabelField("Output Value", GUILayout.Width(100));
|
||||||
|
EditorGUILayout.Vector3Field(GUIContent.none, currentNucleus.outputValue);
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
if (currentNucleus.synapses.Count > 0) {
|
||||||
|
EditorGUILayout.LabelField("Synapses");
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
|
||||||
|
List<Nucleus> nuclei = currentNucleus.synapses.Keys.ToList();
|
||||||
|
foreach (Nucleus nucleus in nuclei) {
|
||||||
|
EditorGUI.BeginDisabledGroup(nucleus.isSleeping);
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
EditorGUILayout.LabelField(nucleus.name, GUILayout.Width(120));
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
EditorGUILayout.LabelField("Weight", GUILayout.Width(45));
|
||||||
|
float weight = currentNucleus.synapses[nucleus];
|
||||||
|
currentNucleus.synapses[nucleus] = EditorGUILayout.FloatField(weight, GUILayout.Width(40));
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
EditorGUILayout.Vector3Field(GUIContent.none, nucleus.outputValue, GUILayout.Width(180));
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
EditorGUI.EndDisabledGroup();
|
||||||
|
}
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
if (GUILayout.Button("Add Neuron"))
|
||||||
|
AddInputNeuron(currentNucleus);
|
||||||
|
|
||||||
|
});
|
||||||
|
NanoBrainEditor.inspectorContainer.Add(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void AddInputNeuron(Nucleus receiver) {
|
||||||
|
Neuroid newNeuroid = new(NanoBrainEditor.brain, "New neuron");
|
||||||
|
newNeuroid.AddReceiver(receiver);
|
||||||
|
Rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 NodePosition(Nucleus nucleus, int layerNodeCount = 1) {
|
||||||
|
if (this.neuroidPositions.ContainsKey(nucleus)) {
|
||||||
|
Vector2Int nucleusPos = this.neuroidPositions[nucleus];
|
||||||
|
return NodePosition(nucleusPos, layerNodeCount);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Vector3.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Vector3 NodePosition(Vector2Int location, int layerNodeCount = 1) {
|
||||||
|
float spacing = 400f / layerNodeCount;
|
||||||
|
float margin = 10 + spacing / 2;
|
||||||
|
float size = 20;
|
||||||
|
Vector3 parentPos = new(100 + location.x * 100 - size, margin + location.y * spacing - size, 0.1f);
|
||||||
|
return parentPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper to save node position back to graph
|
||||||
|
// public void UpdateNodePosition(NucleusObj node, Vector2 worldPos) {
|
||||||
|
// Undo.RecordObject(graph, "Move Node");
|
||||||
|
// node.position = (worldPos - pan) / zoom;
|
||||||
|
// EditorUtility.SetDirty(graph);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// create & delete APIs called by NodeView or toolbar
|
||||||
|
public void CreateNode(Vector2 worldPos) {
|
||||||
|
// Undo.RecordObject(graph, "Create Node");
|
||||||
|
// var n = new NucleusObj("New Node"); //, position = (worldPos - pan) / zoom };
|
||||||
|
// graph.nodes.Add(n);
|
||||||
|
// EditorUtility.SetDirty(graph);
|
||||||
|
// Rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
// public void CreateEdge(string fromId, string toId) {
|
||||||
|
// if (fromId == toId) return;
|
||||||
|
// Undo.RecordObject(graph, "Create Edge");
|
||||||
|
// graph.edges.Add(new GraphEdge { fromNodeId = fromId, toNodeId = toId });
|
||||||
|
// EditorUtility.SetDirty(graph);
|
||||||
|
// Rebuild();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public class NodeView : VisualElement {
|
||||||
|
Nucleus data;
|
||||||
|
GraphBoardView board;
|
||||||
|
Label titleLabel;
|
||||||
|
//bool dragging = false;
|
||||||
|
Vector2 localDragStart;
|
||||||
|
|
||||||
|
public NodeView(Nucleus node, GraphBoardView boardView) {
|
||||||
|
data = node;
|
||||||
|
board = boardView;
|
||||||
|
name = "node";
|
||||||
|
style.width = 20; //node.size.x;
|
||||||
|
style.height = 20; //node.size.y;
|
||||||
|
|
||||||
|
titleLabel = new Label(node.name) { name = "title" };
|
||||||
|
Add(titleLabel);
|
||||||
|
|
||||||
|
// ports
|
||||||
|
// var outPort = new Button(() => StartEdgeDrag(true)) { text = "◀", name = "out" };
|
||||||
|
// var inPort = new Button(() => StartEdgeDrag(false)) { text = "▶", name = "in" };
|
||||||
|
// Add(outPort);
|
||||||
|
// Add(inPort);
|
||||||
|
|
||||||
|
RegisterCallback<MouseDownEvent>(OnMouseDown);
|
||||||
|
// RegisterCallback<MouseMoveEvent>(OnMouseMove);
|
||||||
|
RegisterCallback<MouseUpEvent>(OnMouseUp);
|
||||||
|
//RegisterCallback<MouseLeaveEvent>(e => dragging = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// void StartEdgeDrag(bool isOutput) {
|
||||||
|
// // simplified: on first click store source; on second click on target port call board.CreateEdge
|
||||||
|
// if (EdgeDragState.active == null) EdgeDragState.active = new EdgeDragState { fromNode = data, fromIsOutput = isOutput };
|
||||||
|
// else {
|
||||||
|
// var src = EdgeDragState.active.fromNode;
|
||||||
|
// if (src != null && src.id != data.id) board.CreateEdge(src.id, data.id);
|
||||||
|
// EdgeDragState.active = null;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
void OnMouseDown(MouseDownEvent e) {
|
||||||
|
if (e.button == 0 && e.target == this) {
|
||||||
|
//dragging = true;
|
||||||
|
localDragStart = e.mousePosition;
|
||||||
|
e.StopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// void OnMouseMove(MouseMoveEvent e) {
|
||||||
|
// if (!dragging) return;
|
||||||
|
// var delta = e.mousePosition - localDragStart;
|
||||||
|
// var worldPos = new Vector2(layout.x + delta.x, layout.y + delta.y);
|
||||||
|
// style.left = worldPos.x;
|
||||||
|
// style.top = worldPos.y;
|
||||||
|
// // commit on every move
|
||||||
|
// board.UpdateNodePosition(data, worldPos);
|
||||||
|
// }
|
||||||
|
void OnMouseUp(MouseUpEvent e) {
|
||||||
|
//dragging = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class GraphNodeWrapper : ScriptableObject {
|
||||||
|
// expose fields that map to GraphNode
|
||||||
|
public string title;
|
||||||
|
public Vector2 position;
|
||||||
|
Nucleus node;
|
||||||
|
NanoBrainObj graph; // needed to write back and mark dirty
|
||||||
|
|
||||||
|
public GraphNodeWrapper Init(Nucleus node, NanoBrainObj graphAsset) {
|
||||||
|
this.node = node;
|
||||||
|
this.graph = graphAsset;
|
||||||
|
this.title = " A " + node.name;
|
||||||
|
//position = node.position;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
void OnValidate() {
|
||||||
|
if (node != null) {
|
||||||
|
node.name = title;
|
||||||
|
//node.position = position;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.EditorUtility.SetDirty(graph);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//static class EdgeDragState { public static EdgeDragState active; public GraphNode fromNode; public bool fromIsOutput; }
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c57f78e25f0e55b96a50fd5592b26317
|
||||||
22
Assets/NanoBrain/VisualEditor/NanoBrainObj.cs
Normal file
22
Assets/NanoBrain/VisualEditor/NanoBrainObj.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class NanoBrainObj : ScriptableObject {
|
||||||
|
public Nucleus rootNucleus = new("root");
|
||||||
|
|
||||||
|
public List<Neuroid> neuroids = new();
|
||||||
|
|
||||||
|
public Neuroid AddNeuron(string name) {
|
||||||
|
Neuroid neuroid = new(this, name);
|
||||||
|
return neuroid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateNeurons() {
|
||||||
|
foreach (Neuroid neuroid in neuroids) {
|
||||||
|
neuroid.stale++;
|
||||||
|
if (neuroid.isSleeping)
|
||||||
|
neuroid.outputValue = Vector3.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
2
Assets/NanoBrain/VisualEditor/NanoBrainObj.cs.meta
Normal file
2
Assets/NanoBrain/VisualEditor/NanoBrainObj.cs.meta
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 36081359186edfec998d891a1feeb17b
|
||||||
0
Assets/NanoBrain/VisualEditor/NeuroidObject.cs
Normal file
0
Assets/NanoBrain/VisualEditor/NeuroidObject.cs
Normal file
2
Assets/NanoBrain/VisualEditor/NeuroidObject.cs.meta
Normal file
2
Assets/NanoBrain/VisualEditor/NeuroidObject.cs.meta
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d14e756f390f41a1c9923a0015329389
|
||||||
28
Assets/NanoBrain/VisualEditor/NucleusObject.cs
Normal file
28
Assets/NanoBrain/VisualEditor/NucleusObject.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// using System.Collections.Generic;
|
||||||
|
// using UnityEngine;
|
||||||
|
|
||||||
|
// public class NucleusObj : ScriptableObject {
|
||||||
|
// public virtual string objName { get; set; }
|
||||||
|
|
||||||
|
// public readonly Dictionary<NucleusObj, float> synapses = new();
|
||||||
|
// public HashSet<NucleusObj> receivers = new();
|
||||||
|
// public virtual Vector3 outputValue { get; set; }
|
||||||
|
|
||||||
|
// public int stale = 0;
|
||||||
|
|
||||||
|
// public NucleusObj(string name) {
|
||||||
|
// this.objName = name;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public virtual void AddReceiver(NucleusObj receiver) {
|
||||||
|
// this.receivers.Add(receiver);
|
||||||
|
// receiver.synapses[this] = 1.0f;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public bool isSleeping {
|
||||||
|
// get {
|
||||||
|
// return this.stale > 2;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
2
Assets/NanoBrain/VisualEditor/NucleusObject.cs.meta
Normal file
2
Assets/NanoBrain/VisualEditor/NucleusObject.cs.meta
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ca2a41cdae50b5005b5cf10cebb28de6
|
||||||
8
Assets/NanoBrain/VisualEditor/Resources.meta
Normal file
8
Assets/NanoBrain/VisualEditor/Resources.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7b61a93fc9332d2adae74fe4abe92d53
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
15
Assets/NanoBrain/VisualEditor/Resources/GraphStyles.uss
Normal file
15
Assets/NanoBrain/VisualEditor/Resources/GraphStyles.uss
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#main {
|
||||||
|
|
||||||
|
}
|
||||||
|
#content {
|
||||||
|
background-color: #2b2b2b,
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
#inspector {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-left-color: #000;
|
||||||
|
border-left_style: solid;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
.node { background-color: #222; border-radius:4px; padding:6px; }
|
||||||
|
#title { unity-font-style: bold; color: white; }
|
||||||
11
Assets/NanoBrain/VisualEditor/Resources/GraphStyles.uss.meta
Normal file
11
Assets/NanoBrain/VisualEditor/Resources/GraphStyles.uss.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 28268b644fa8f3948851b25e41f5b03b
|
||||||
|
ScriptedImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
disableValidation: 0
|
||||||
@ -11,7 +11,7 @@ public class Roaming : Nucleus {
|
|||||||
output.GetInputFrom(avoidance, -sc.avoidanceForce);
|
output.GetInputFrom(avoidance, -sc.avoidanceForce);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void AddReceiver(Neuroid receiver) {
|
public override void AddReceiver(Nucleus receiver) {
|
||||||
output.AddReceiver(receiver);
|
output.AddReceiver(receiver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ public class Swarming : Nucleus {
|
|||||||
this.output.GetInputFrom(boundary, -sc.avoidanceForce);
|
this.output.GetInputFrom(boundary, -sc.avoidanceForce);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void AddReceiver(Neuroid receiver) {
|
public override void AddReceiver(Nucleus receiver) {
|
||||||
this.output.AddReceiver(receiver);
|
this.output.AddReceiver(receiver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
<Solution>
|
<Solution>
|
||||||
<Project Path="Assembly-CSharp.csproj" />
|
|
||||||
<Project Path="Assembly-CSharp-Editor.csproj" />
|
<Project Path="Assembly-CSharp-Editor.csproj" />
|
||||||
|
<Project Path="Assembly-CSharp.csproj" />
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user