Reorganizing the package and added documentation

This commit is contained in:
Pascal Serrarens 2026-04-07 17:31:57 +02:00
parent bef7ee24e5
commit fbca658b59
94 changed files with 3093 additions and 3940 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
doxygen

View File

@ -1,116 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "Passer/Cluster")]
public class ClusterPrefab : ScriptableObject {
// The ScriptableObject asset from which the runtime object has been created
[SerializeReference]
public List<Nucleus> nuclei = new();
public virtual Nucleus output => this.nuclei[0] as Nucleus;
public List<Nucleus> _inputs = null;
public virtual List<Nucleus> inputs {
get {
if (this._inputs == null) {
this._inputs = new();
foreach (Nucleus receptor in this.nuclei) {
if (receptor is Nucleus nucleus) {
// inputs have no incoming synapses yet.
if (nucleus.synapses.Count == 0)
this._inputs.Add(nucleus);
}
}
}
return this._inputs;
}
}
private List<Nucleus> _outputs = null;
public List<Nucleus> outputs {
get {
if (this._outputs == null)
RefreshOutputs();
return this._outputs;
}
}
public void RefreshOutputs() {
this._outputs = new();
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron neuron && neuron.receivers.Count == 0)
this._outputs.Add(nucleus);
}
}
public Nucleus GetNucleus(string nucleusName) {
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus.name == nucleusName)
return nucleus;
}
return null;
}
// Call this function to ensure that there is at least one nucleus
// This is an invariant and should be ensured before the nucleus is used
// because output requires it.
public void EnsureInitialization() {
nuclei ??= new List<Nucleus>();
if (nuclei.Count == 0)
new Neuron(this, "Output"); // Every cluster should have at least 1 neuron
}
public void GarbageCollection() {
HashSet<Nucleus> visitedNuclei = new();
foreach (Nucleus output in this.outputs)
MarkNuclei(visitedNuclei, output);
//Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei");
this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false);
}
public void MarkNuclei(HashSet<Nucleus> visitedNuclei, Nucleus nucleus) {
if (nucleus is null)
return;
if (nucleus.parent != null && nucleus.parent.prefab != this)
visitedNuclei.Add(nucleus.parent);
else
visitedNuclei.Add(nucleus);
if (nucleus.synapses != null) {
HashSet<Synapse> visitedSynapses = new();
foreach (Synapse synapse in nucleus.synapses) {
if (synapse != null && synapse.neuron != null) {
visitedSynapses.Add(synapse);
if (synapse.neuron is Nucleus synapse_nucleus)
MarkNuclei(visitedNuclei, synapse_nucleus);
}
}
nucleus.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false);
}
if (nucleus is Neuron neuron && neuron.receivers != null) {
HashSet<Nucleus> visitedReceivers = new();
foreach (Nucleus receiver in neuron.receivers) {
if (receiver != null && receiver != null) {
visitedReceivers.Add(receiver);
visitedNuclei.Add(receiver);
}
}
neuron.receivers.RemoveAll(receiver => visitedReceivers.Contains(receiver) == false);
}
}
public virtual void UpdateNuclei() {
foreach (Nucleus nucleus in this.nuclei)
nucleus.UpdateNuclei();
}
public int GetNucleusIndex(Nucleus receiver) {
int ix = 0;
foreach (Nucleus nucleus in this.nuclei) {
if (receiver == nucleus)
return ix;
ix++;
}
return -1;
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 60a957541c24c57e78018c202ebb1d9b

View File

@ -3,363 +3,367 @@ using UnityEditor;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
// Simple DAG data model namespace NanoBrain {
[System.Serializable]
public class DagNode {
public int id;
public string title;
public Vector2 position;
public float radius = 20f; // circle radius
}
[System.Serializable] // Simple DAG data model
public class DagEdge { [System.Serializable]
public int fromId; public class DagNode {
public int toId; public int id;
} public string title;
public Vector2 position;
public class BrainEditorWindow : EditorWindow { public float radius = 20f; // circle radius
readonly List<DagNode> nodes = new();
readonly List<DagEdge> edges = new();
Vector2 pan = Vector2.zero;
float zoom = 1.0f;
const float minZoom = 0.5f;
const float maxZoom = 2.0f;
// Vector2 dragStart;
// bool draggingNode = false;
// int draggingNodeId = -1;
private readonly System.Type acceptedType = typeof(ClusterPrefab);
[MenuItem("Window/Brain Viewer")]
public static void ShowWindow() {
var w = GetWindow<BrainEditorWindow>("Brain Viewer");
w.minSize = new Vector2(500, 300);
} }
void OnEnable() { [System.Serializable]
// if (nodes.Count == 0) public class DagEdge {
// CreateSampleGraph(); public int fromId;
public int toId;
// Register callback so window updates when selection changes
Selection.selectionChanged += OnSelectionChanged;
RefreshSelection();
ComputeLeftToRightLayout();
} }
private void OnDisable() { public class BrainEditorWindow : EditorWindow {
Selection.selectionChanged -= OnSelectionChanged; readonly List<DagNode> nodes = new();
} readonly List<DagEdge> edges = new();
private void OnSelectionChanged() { Vector2 pan = Vector2.zero;
RefreshSelection(); float zoom = 1.0f;
ComputeLeftToRightLayout(); const float minZoom = 0.5f;
Repaint(); const float maxZoom = 2.0f;
}
private void RefreshSelection() { // Vector2 dragStart;
ClusterPrefab prefab = Selection.activeObject as ClusterPrefab; // bool draggingNode = false;
if (prefab != null && acceptedType.IsAssignableFrom(prefab.GetType())) { // int draggingNodeId = -1;
GenerateGraph(prefab);
private readonly System.Type acceptedType = typeof(ClusterPrefab);
[MenuItem("Window/Brain Viewer")]
public static void ShowWindow() {
var w = GetWindow<BrainEditorWindow>("Brain Viewer");
w.minSize = new Vector2(500, 300);
} }
}
private void GenerateGraph(ClusterPrefab prefab) { void OnEnable() {
nodes.Clear(); // if (nodes.Count == 0)
edges.Clear(); // CreateSampleGraph();
int ix = 0;
foreach (Nucleus nucleus in prefab.nuclei) { // Register callback so window updates when selection changes
nodes.Add(new DagNode() { id = ix, title = nucleus.name }); Selection.selectionChanged += OnSelectionChanged;
if (nucleus is Neuron neuron) { RefreshSelection();
foreach (Nucleus receiver in neuron.receivers) { ComputeLeftToRightLayout();
int receiverIx = prefab.GetNucleusIndex(receiver); }
edges.Add(new DagEdge() { fromId = ix, toId = receiverIx });
private void OnDisable() {
Selection.selectionChanged -= OnSelectionChanged;
}
private void OnSelectionChanged() {
RefreshSelection();
ComputeLeftToRightLayout();
Repaint();
}
private void RefreshSelection() {
ClusterPrefab prefab = Selection.activeObject as ClusterPrefab;
if (prefab != null && acceptedType.IsAssignableFrom(prefab.GetType())) {
GenerateGraph(prefab);
}
}
private void GenerateGraph(ClusterPrefab prefab) {
nodes.Clear();
edges.Clear();
int ix = 0;
foreach (Nucleus nucleus in prefab.nuclei) {
nodes.Add(new DagNode() { id = ix, title = nucleus.name });
if (nucleus is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers) {
int receiverIx = prefab.GetNucleusIndex(receiver);
edges.Add(new DagEdge() { fromId = ix, toId = receiverIx });
}
}
ix++;
}
}
// void CreateSampleGraph() {
// nodes.Clear();
// edges.Clear();
// nodes.Add(new DagNode() { id = 0, title = "In1" });
// nodes.Add(new DagNode() { id = 1, title = "In2" });
// nodes.Add(new DagNode() { id = 2, title = "A" });
// nodes.Add(new DagNode() { id = 3, title = "B" });
// nodes.Add(new DagNode() { id = 4, title = "C" });
// nodes.Add(new DagNode() { id = 5, title = "Out1" });
// nodes.Add(new DagNode() { id = 6, title = "Out2" });
// edges.Add(new DagEdge() { fromId = 0, toId = 2 });
// edges.Add(new DagEdge() { fromId = 1, toId = 2 });
// edges.Add(new DagEdge() { fromId = 2, toId = 3 });
// edges.Add(new DagEdge() { fromId = 2, toId = 4 });
// edges.Add(new DagEdge() { fromId = 3, toId = 5 });
// edges.Add(new DagEdge() { fromId = 4, toId = 6 });
// }
void OnGUI() {
HandleInput();
Rect rect = new(0, 0, position.width, position.height);
EditorGUI.DrawRect(rect, new Color(0.11f, 0.11f, 0.11f));
// compute window center
Vector2 windowCenter = new(position.width / 2f, position.height / 2f);
// compute graph bounds center (in graph space)
Rect bounds = GetGraphBounds();
Vector2 graphCenter = bounds.center;
// compute autoPan that recenters the graph (does not modify node positions)
Vector2 autoPan = -graphCenter; // moves graph center to origin
// total translation = windowCenter + autoPan + user pan
Matrix4x4 oldMatrix = GUI.matrix;
GUI.matrix = Matrix4x4.TRS(windowCenter + autoPan + pan, Quaternion.identity, Vector3.one * zoom) *
Matrix4x4.TRS(-windowCenter, Quaternion.identity, Vector3.one);
// Draw edges first
foreach (DagEdge e in edges) {
DagNode from = GetNodeById(e.fromId);
DagNode to = GetNodeById(e.toId);
if (from == null || to == null) continue;
DrawEdgeCircleNodes(from, to);
}
// Draw nodes (circles)
foreach (DagNode n in nodes)
DrawNucleus(n);
GUI.matrix = oldMatrix;
// Footer toolbar
GUILayout.FlexibleSpace();
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
if (GUILayout.Button("Fit", EditorStyles.toolbarButton)) FitToView();
if (GUILayout.Button("Layout LR", EditorStyles.toolbarButton)) ComputeLeftToRightLayout();
EditorGUILayout.EndHorizontal();
}
void HandleInput() {
Event e = Event.current;
// Zoom with scroll
if (e.type == EventType.ScrollWheel) {
float oldZoom = zoom;
float delta = -e.delta.y * 0.01f;
zoom = Mathf.Clamp(zoom + delta, minZoom, maxZoom);
Vector2 mouse = e.mousePosition;
pan += (mouse - new Vector2(position.width / 2, position.height / 2)) * (1 - zoom / oldZoom);
e.Use();
}
// Pan with middle or right+ctrl drag
if (e.type == EventType.MouseDrag && (e.button == 2 || (e.button == 1 && e.control))) {
pan += e.delta;
e.Use();
}
}
DagNode GetNodeById(int id) => nodes.FirstOrDefault(x => x.id == id);
List<DagEdge> GetIncomingEdges(DagNode node) {
List<DagEdge> incoming = new();
foreach (DagEdge e in edges) {
if (e.toId == node.id)
incoming.Add(e);
}
return incoming;
}
List<DagEdge> GetOutgoingEdges(DagNode node) {
List<DagEdge> outgoing = new();
foreach (DagEdge e in edges) {
if (e.fromId == node.id)
outgoing.Add(e);
}
return outgoing;
}
void DrawNucleus(DagNode n) {
Vector3 position = n.position;
Handles.color = Color.white * 0.9f;
Handles.DrawSolidDisc(n.position, Vector3.forward, n.radius);
if (GetIncomingEdges(n).Count == 0)
DrawArrowHead(n.position - new Vector2(n.radius + 10, 0), n.position - new Vector2(n.radius + 5, 0), 10f / zoom, 12f / zoom, Color.white);
if (GetOutgoingEdges(n).Count == 0)
DrawArrowHead(n.position + new Vector2(n.radius + 10, 0), n.position + new Vector2(n.radius + 15, 0), 10f / zoom, 12f / zoom, Color.white);
Handles.color = Color.white;
GUIStyle style = new(EditorStyles.label) {
alignment = TextAnchor.UpperCenter,
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold,
};
Vector3 labelPos = position - Vector3.down * (n.radius + 10f); // below disc along up axis
Handles.Label(labelPos, n.title, style);
}
void DrawEdgeCircleNodes(DagNode from, DagNode to) {
Vector2 a = from.position;
Vector2 b = to.position;
if (a == b) return;
Handles.color = Color.white * 0.9f;
Handles.DrawLine(from.position, to.position);
// Vector2 dir = (b - a).normalized;
// Vector2 start = a + dir * from.radius;
// Vector2 end = b - dir * to.radius;
//DrawArrowHead(end - dir * 2f, end, 10f / zoom, 12f / zoom, Color.white);
}
void DrawArrowHead(Vector2 from, Vector2 to, float headWidth, float headLength, Color color) {
Vector2 dir = (to - from).normalized;
if (dir == Vector2.zero) return;
Vector2 right = new Vector2(-dir.y, dir.x);
Vector3 p1 = to;
Vector3 p2 = to - dir * headLength + right * headWidth * 0.5f;
Vector3 p3 = to - dir * headLength - right * headWidth * 0.5f;
Handles.color = color;
Handles.DrawAAConvexPolygon(p1, p2, p3);
}
// Left-to-right layered layout (sources on the left, sinks on the right)
void ComputeLeftToRightLayout() {
// build adjacency and indegree
var adj = nodes.ToDictionary(n => n.id, n => new List<int>());
var indeg = nodes.ToDictionary(n => n.id, n => 0);
foreach (var e in edges) {
if (!adj.ContainsKey(e.fromId) || !adj.ContainsKey(e.toId)) continue;
adj[e.fromId].Add(e.toId);
indeg[e.toId]++;
}
// Kahn's algorithm to compute topological layers (horizontal layers)
Dictionary<int, int> layer = new();
Queue<int> q = new(indeg.Where(kv => kv.Value == 0).Select(kv => kv.Key));
foreach (var id in q) layer[id] = 0;
while (q.Count > 0) {
int u = q.Dequeue();
int l = layer[u];
foreach (var v in adj[u]) {
// prefer placing v at least one layer after u
if (!layer.ContainsKey(v) || layer[v] < l + 1) layer[v] = l + 1;
indeg[v]--;
if (indeg[v] == 0) q.Enqueue(v);
} }
} }
ix++;
}
}
// Any unreachable nodes -> assign next layers
// void CreateSampleGraph() { int maxLayer = layer.Count > 0 ? layer.Values.Max() : 0;
// nodes.Clear(); foreach (var n in nodes) {
// edges.Clear(); if (!layer.ContainsKey(n.id)) {
maxLayer++;
// nodes.Add(new DagNode() { id = 0, title = "In1" }); layer[n.id] = maxLayer;
// nodes.Add(new DagNode() { id = 1, title = "In2" }); }
// nodes.Add(new DagNode() { id = 2, title = "A" });
// nodes.Add(new DagNode() { id = 3, title = "B" });
// nodes.Add(new DagNode() { id = 4, title = "C" });
// nodes.Add(new DagNode() { id = 5, title = "Out1" });
// nodes.Add(new DagNode() { id = 6, title = "Out2" });
// edges.Add(new DagEdge() { fromId = 0, toId = 2 });
// edges.Add(new DagEdge() { fromId = 1, toId = 2 });
// edges.Add(new DagEdge() { fromId = 2, toId = 3 });
// edges.Add(new DagEdge() { fromId = 2, toId = 4 });
// edges.Add(new DagEdge() { fromId = 3, toId = 5 });
// edges.Add(new DagEdge() { fromId = 4, toId = 6 });
// }
void OnGUI() {
HandleInput();
Rect rect = new(0, 0, position.width, position.height);
EditorGUI.DrawRect(rect, new Color(0.11f, 0.11f, 0.11f));
// compute window center
Vector2 windowCenter = new(position.width / 2f, position.height / 2f);
// compute graph bounds center (in graph space)
Rect bounds = GetGraphBounds();
Vector2 graphCenter = bounds.center;
// compute autoPan that recenters the graph (does not modify node positions)
Vector2 autoPan = -graphCenter; // moves graph center to origin
// total translation = windowCenter + autoPan + user pan
Matrix4x4 oldMatrix = GUI.matrix;
GUI.matrix = Matrix4x4.TRS(windowCenter + autoPan + pan, Quaternion.identity, Vector3.one * zoom) *
Matrix4x4.TRS(-windowCenter, Quaternion.identity, Vector3.one);
// Draw edges first
foreach (DagEdge e in edges) {
DagNode from = GetNodeById(e.fromId);
DagNode to = GetNodeById(e.toId);
if (from == null || to == null) continue;
DrawEdgeCircleNodes(from, to);
}
// Draw nodes (circles)
foreach (DagNode n in nodes)
DrawNucleus(n);
GUI.matrix = oldMatrix;
// Footer toolbar
GUILayout.FlexibleSpace();
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
if (GUILayout.Button("Fit", EditorStyles.toolbarButton)) FitToView();
if (GUILayout.Button("Layout LR", EditorStyles.toolbarButton)) ComputeLeftToRightLayout();
EditorGUILayout.EndHorizontal();
}
void HandleInput() {
Event e = Event.current;
// Zoom with scroll
if (e.type == EventType.ScrollWheel) {
float oldZoom = zoom;
float delta = -e.delta.y * 0.01f;
zoom = Mathf.Clamp(zoom + delta, minZoom, maxZoom);
Vector2 mouse = e.mousePosition;
pan += (mouse - new Vector2(position.width / 2, position.height / 2)) * (1 - zoom / oldZoom);
e.Use();
}
// Pan with middle or right+ctrl drag
if (e.type == EventType.MouseDrag && (e.button == 2 || (e.button == 1 && e.control))) {
pan += e.delta;
e.Use();
}
}
DagNode GetNodeById(int id) => nodes.FirstOrDefault(x => x.id == id);
List<DagEdge> GetIncomingEdges(DagNode node) {
List<DagEdge> incoming = new();
foreach (DagEdge e in edges) {
if (e.toId == node.id)
incoming.Add(e);
}
return incoming;
}
List<DagEdge> GetOutgoingEdges(DagNode node) {
List<DagEdge> outgoing = new();
foreach (DagEdge e in edges) {
if (e.fromId == node.id)
outgoing.Add(e);
}
return outgoing;
}
void DrawNucleus(DagNode n) {
Vector3 position = n.position;
Handles.color = Color.white * 0.9f;
Handles.DrawSolidDisc(n.position, Vector3.forward, n.radius);
if (GetIncomingEdges(n).Count == 0)
DrawArrowHead(n.position - new Vector2(n.radius + 10, 0), n.position - new Vector2(n.radius + 5, 0), 10f / zoom, 12f / zoom, Color.white);
if (GetOutgoingEdges(n).Count == 0)
DrawArrowHead(n.position + new Vector2(n.radius + 10, 0), n.position + new Vector2(n.radius + 15, 0), 10f / zoom, 12f / zoom, Color.white);
Handles.color = Color.white;
GUIStyle style = new(EditorStyles.label) {
alignment = TextAnchor.UpperCenter,
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold,
};
Vector3 labelPos = position - Vector3.down * (n.radius + 10f); // below disc along up axis
Handles.Label(labelPos, n.title, style);
}
void DrawEdgeCircleNodes(DagNode from, DagNode to) {
Vector2 a = from.position;
Vector2 b = to.position;
if (a == b) return;
Handles.color = Color.white * 0.9f;
Handles.DrawLine(from.position, to.position);
// Vector2 dir = (b - a).normalized;
// Vector2 start = a + dir * from.radius;
// Vector2 end = b - dir * to.radius;
//DrawArrowHead(end - dir * 2f, end, 10f / zoom, 12f / zoom, Color.white);
}
void DrawArrowHead(Vector2 from, Vector2 to, float headWidth, float headLength, Color color) {
Vector2 dir = (to - from).normalized;
if (dir == Vector2.zero) return;
Vector2 right = new Vector2(-dir.y, dir.x);
Vector3 p1 = to;
Vector3 p2 = to - dir * headLength + right * headWidth * 0.5f;
Vector3 p3 = to - dir * headLength - right * headWidth * 0.5f;
Handles.color = color;
Handles.DrawAAConvexPolygon(p1, p2, p3);
}
// Left-to-right layered layout (sources on the left, sinks on the right)
void ComputeLeftToRightLayout() {
// build adjacency and indegree
var adj = nodes.ToDictionary(n => n.id, n => new List<int>());
var indeg = nodes.ToDictionary(n => n.id, n => 0);
foreach (var e in edges) {
if (!adj.ContainsKey(e.fromId) || !adj.ContainsKey(e.toId)) continue;
adj[e.fromId].Add(e.toId);
indeg[e.toId]++;
}
// Kahn's algorithm to compute topological layers (horizontal layers)
Dictionary<int, int> layer = new();
Queue<int> q = new(indeg.Where(kv => kv.Value == 0).Select(kv => kv.Key));
foreach (var id in q) layer[id] = 0;
while (q.Count > 0) {
int u = q.Dequeue();
int l = layer[u];
foreach (var v in adj[u]) {
// prefer placing v at least one layer after u
if (!layer.ContainsKey(v) || layer[v] < l + 1) layer[v] = l + 1;
indeg[v]--;
if (indeg[v] == 0) q.Enqueue(v);
} }
}
// Any unreachable nodes -> assign next layers // Group nodes by layer (left to right)
int maxLayer = layer.Count > 0 ? layer.Values.Max() : 0; var layers = layer.GroupBy(kv => kv.Value).OrderBy(g => g.Key).Select(g => g.Select(x => x.Key).ToList()).ToList();
foreach (var n in nodes) {
if (!layer.ContainsKey(n.id)) { // Layout parameters (horizontal spacing drives left->right)
maxLayer++; float hSpacing = 150f;
layer[n.id] = maxLayer; float vSpacing = 100f;
// Place nodes: x increases with layer index, y spaced within layer
for (int li = 0; li < layers.Count; li++) {
var lst = layers[li];
float totalHeight = (lst.Count - 1) * vSpacing;
for (int i = 0; i < lst.Count; i++) {
int id = lst[i];
var n = GetNodeById(id);
if (n == null) continue;
float x = hSpacing + li * hSpacing;
float y = 400 - totalHeight / 2f + i * vSpacing;
// Debug.Log($"({li}, {i}) -> {x}, {y}");
n.position = new Vector2(x, y);
}
} }
Repaint();
} }
// Group nodes by layer (left to right) void FitToView() {
var layers = layer.GroupBy(kv => kv.Value).OrderBy(g => g.Key).Select(g => g.Select(x => x.Key).ToList()).ToList(); if (nodes.Count == 0) return;
// compute bounds including radii
Rect bounds = new Rect(nodes[0].position - Vector2.one * nodes[0].radius, Vector2.one * nodes[0].radius * 2f);
foreach (var n in nodes)
bounds = RectUnion(bounds, new Rect(n.position - Vector2.one * n.radius, Vector2.one * n.radius * 2f));
// Layout parameters (horizontal spacing drives left->right) // center graph at origin (0,0) then set pan so it appears centered in window
float hSpacing = 150f; Vector2 graphCenter = bounds.center;
float vSpacing = 100f; // move nodes so center is at origin
for (int i = 0; i < nodes.Count; i++)
nodes[i].position -= graphCenter;
// Place nodes: x increases with layer index, y spaced within layer // reset pan/zoom so centered
for (int li = 0; li < layers.Count; li++) { pan = Vector2.zero;
var lst = layers[li]; zoom = 1.0f;
float totalHeight = (lst.Count - 1) * vSpacing; Repaint();
for (int i = 0; i < lst.Count; i++) { }
int id = lst[i];
var n = GetNodeById(id);
if (n == null) continue; static Rect RectUnion(Rect a, Rect b) {
float x = hSpacing + li * hSpacing; float xMin = Mathf.Min(a.xMin, b.xMin);
float y = 400 - totalHeight / 2f + i * vSpacing; float xMax = Mathf.Max(a.xMax, b.xMax);
// Debug.Log($"({li}, {i}) -> {x}, {y}"); float yMin = Mathf.Min(a.yMin, b.yMin);
n.position = new Vector2(x, y); float yMax = Mathf.Max(a.yMax, b.yMax);
return Rect.MinMaxRect(xMin, yMin, xMax, yMax);
}
Vector2 ScreenToGraph_old(Vector2 screenPos) {
Vector2 origin = new Vector2(position.width / 2, position.height / 2);
// invert the GUI.matrix transform (approx for current simple transforms)
return (screenPos - (origin + pan)) / zoom + origin * (1 - 1 / zoom);
}
Vector2 ScreenToGraph(Vector2 screenPos) {
Vector2 windowCenter = new Vector2(position.width / 2f, position.height / 2f);
Rect bounds = GetGraphBounds();
Vector2 graphCenter = bounds.center;
Vector2 autoPan = -graphCenter;
// inverse of: screen -> translate by -(windowCenter+autoPan+pan), scale by 1/zoom, translate by windowCenter
return (screenPos - (windowCenter + autoPan + pan)) / zoom + windowCenter;
}
Rect GetGraphBounds() {
if (nodes == null || nodes.Count == 0) return new Rect(Vector2.zero, Vector2.one);
Rect bounds = new(
nodes[0].position - Vector2.one * nodes[0].radius,
2f * nodes[0].radius * Vector2.one);
foreach (var n in nodes)
bounds = RectUnion(bounds,
new Rect(n.position - Vector2.one * n.radius, 2f * n.radius * Vector2.one));
return bounds;
}
int HitTestNode(Vector2 graphPos) {
// returns node id under point or -1
for (int i = nodes.Count - 1; i >= 0; i--) {
var n = nodes[i];
if ((graphPos - n.position).sqrMagnitude <= n.radius * n.radius) return n.id;
} }
return -1;
} }
Repaint();
}
void FitToView() {
if (nodes.Count == 0) return;
// compute bounds including radii
Rect bounds = new Rect(nodes[0].position - Vector2.one * nodes[0].radius, Vector2.one * nodes[0].radius * 2f);
foreach (var n in nodes)
bounds = RectUnion(bounds, new Rect(n.position - Vector2.one * n.radius, Vector2.one * n.radius * 2f));
// center graph at origin (0,0) then set pan so it appears centered in window
Vector2 graphCenter = bounds.center;
// move nodes so center is at origin
for (int i = 0; i < nodes.Count; i++)
nodes[i].position -= graphCenter;
// reset pan/zoom so centered
pan = Vector2.zero;
zoom = 1.0f;
Repaint();
}
static Rect RectUnion(Rect a, Rect b) {
float xMin = Mathf.Min(a.xMin, b.xMin);
float xMax = Mathf.Max(a.xMax, b.xMax);
float yMin = Mathf.Min(a.yMin, b.yMin);
float yMax = Mathf.Max(a.yMax, b.yMax);
return Rect.MinMaxRect(xMin, yMin, xMax, yMax);
}
Vector2 ScreenToGraph_old(Vector2 screenPos) {
Vector2 origin = new Vector2(position.width / 2, position.height / 2);
// invert the GUI.matrix transform (approx for current simple transforms)
return (screenPos - (origin + pan)) / zoom + origin * (1 - 1 / zoom);
}
Vector2 ScreenToGraph(Vector2 screenPos) {
Vector2 windowCenter = new Vector2(position.width / 2f, position.height / 2f);
Rect bounds = GetGraphBounds();
Vector2 graphCenter = bounds.center;
Vector2 autoPan = -graphCenter;
// inverse of: screen -> translate by -(windowCenter+autoPan+pan), scale by 1/zoom, translate by windowCenter
return (screenPos - (windowCenter + autoPan + pan)) / zoom + windowCenter;
}
Rect GetGraphBounds() {
if (nodes == null || nodes.Count == 0) return new Rect(Vector2.zero, Vector2.one);
Rect bounds = new(
nodes[0].position - Vector2.one * nodes[0].radius,
2f * nodes[0].radius * Vector2.one);
foreach (var n in nodes)
bounds = RectUnion(bounds,
new Rect(n.position - Vector2.one * n.radius, 2f * n.radius * Vector2.one));
return bounds;
}
int HitTestNode(Vector2 graphPos) {
// returns node id under point or -1
for (int i = nodes.Count - 1; i >= 0; i--) {
var n = nodes[i];
if ((graphPos - n.position).sqrMagnitude <= n.radius * n.radius) return n.id;
}
return -1;
} }
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -4,390 +4,353 @@ using UnityEditor;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
// Simple DAG data model namespace NanoBrain {
// [System.Serializable]
// public class DagNode
// {
// public int id;
// public string title;
// public Vector2 position;
// public float radius = 36f; // circle radius
// }
// [System.Serializable] // Simple DAG data model
// public class DagEdge // [System.Serializable]
// { // public class DagNode
// public int fromId; // {
// public int toId; // public int id;
// } // public string title;
// public Vector2 position;
// public float radius = 36f; // circle radius
// }
public class DAGEditorWindow : EditorWindow // [System.Serializable]
{ // public class DagEdge
List<DagNode> nodes = new List<DagNode>(); // {
List<DagEdge> edges = new List<DagEdge>(); // public int fromId;
// public int toId;
// }
Vector2 pan = Vector2.zero; public class DAGEditorWindow : EditorWindow {
float zoom = 1.0f; List<DagNode> nodes = new List<DagNode>();
const float minZoom = 0.5f; List<DagEdge> edges = new List<DagEdge>();
const float maxZoom = 2.0f;
GUIStyle labelStyle; Vector2 pan = Vector2.zero;
int selectedNodeId = -1; float zoom = 1.0f;
const float minZoom = 0.5f;
const float maxZoom = 2.0f;
Vector2 dragStart; GUIStyle labelStyle;
bool draggingNode = false; int selectedNodeId = -1;
int draggingNodeId = -1;
[MenuItem("Window/DAG Viewer (LR, Circles)")] Vector2 dragStart;
public static void ShowWindow() bool draggingNode = false;
{ int draggingNodeId = -1;
var w = GetWindow<DAGEditorWindow>("DAG Viewer (LR)");
w.minSize = new Vector2(500, 300);
}
void OnEnable() [MenuItem("Window/DAG Viewer (LR, Circles)")]
{ public static void ShowWindow() {
labelStyle = new GUIStyle(EditorStyles.label); var w = GetWindow<DAGEditorWindow>("DAG Viewer (LR)");
labelStyle.alignment = TextAnchor.MiddleCenter; w.minSize = new Vector2(500, 300);
labelStyle.normal.textColor = Color.white;
labelStyle.fontStyle = FontStyle.Bold;
if (nodes.Count == 0)
CreateSampleGraph();
ComputeLeftToRightLayout();
}
void CreateSampleGraph()
{
nodes.Clear();
edges.Clear();
nodes.Add(new DagNode() { id = 0, title = "In1" });
nodes.Add(new DagNode() { id = 1, title = "In2" });
nodes.Add(new DagNode() { id = 2, title = "A" });
nodes.Add(new DagNode() { id = 3, title = "B" });
nodes.Add(new DagNode() { id = 4, title = "C" });
nodes.Add(new DagNode() { id = 5, title = "Out1" });
nodes.Add(new DagNode() { id = 6, title = "Out2" });
edges.Add(new DagEdge() { fromId = 0, toId = 2 });
edges.Add(new DagEdge() { fromId = 1, toId = 2 });
edges.Add(new DagEdge() { fromId = 2, toId = 3 });
edges.Add(new DagEdge() { fromId = 2, toId = 4 });
edges.Add(new DagEdge() { fromId = 3, toId = 5 });
edges.Add(new DagEdge() { fromId = 4, toId = 6 });
}
void OnGUI()
{
HandleInput();
Rect rect = new Rect(0, 0, position.width, position.height);
EditorGUI.DrawRect(rect, new Color(0.11f, 0.11f, 0.11f));
Matrix4x4 oldMatrix = GUI.matrix;
Vector2 origin = new Vector2(position.width / 2, position.height / 2);
GUI.matrix = Matrix4x4.TRS(origin + pan, Quaternion.identity, Vector3.one * zoom) *
Matrix4x4.TRS(-origin, Quaternion.identity, Vector3.one);
// Draw edges first
foreach (var e in edges)
{
var from = GetNodeById(e.fromId);
var to = GetNodeById(e.toId);
if (from == null || to == null) continue;
DrawEdgeCircleNodes(from, to);
} }
// Draw nodes (circles) void OnEnable() {
foreach (var n in nodes) labelStyle = new GUIStyle(EditorStyles.label);
{ labelStyle.alignment = TextAnchor.MiddleCenter;
DrawNodeCircle(n); labelStyle.normal.textColor = Color.white;
} labelStyle.fontStyle = FontStyle.Bold;
GUI.matrix = oldMatrix; if (nodes.Count == 0)
CreateSampleGraph();
// Footer toolbar
GUILayout.FlexibleSpace();
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
if (GUILayout.Button("Fit", EditorStyles.toolbarButton)) FitToView();
if (GUILayout.Button("Layout LR", EditorStyles.toolbarButton)) ComputeLeftToRightLayout();
if (GUILayout.Button("Add Node", EditorStyles.toolbarButton))
{
AddNode("N" + nodes.Count);
ComputeLeftToRightLayout(); ComputeLeftToRightLayout();
} }
if (GUILayout.Button("Add Edge (selected->new)", EditorStyles.toolbarButton))
{ void CreateSampleGraph() {
if (selectedNodeId != -1) nodes.Clear();
{ edges.Clear();
var newNode = AddNode("N" + nodes.Count);
edges.Add(new DagEdge() { fromId = selectedNodeId, toId = newNode.id }); nodes.Add(new DagNode() { id = 0, title = "In1" });
nodes.Add(new DagNode() { id = 1, title = "In2" });
nodes.Add(new DagNode() { id = 2, title = "A" });
nodes.Add(new DagNode() { id = 3, title = "B" });
nodes.Add(new DagNode() { id = 4, title = "C" });
nodes.Add(new DagNode() { id = 5, title = "Out1" });
nodes.Add(new DagNode() { id = 6, title = "Out2" });
edges.Add(new DagEdge() { fromId = 0, toId = 2 });
edges.Add(new DagEdge() { fromId = 1, toId = 2 });
edges.Add(new DagEdge() { fromId = 2, toId = 3 });
edges.Add(new DagEdge() { fromId = 2, toId = 4 });
edges.Add(new DagEdge() { fromId = 3, toId = 5 });
edges.Add(new DagEdge() { fromId = 4, toId = 6 });
}
void OnGUI() {
HandleInput();
Rect rect = new Rect(0, 0, position.width, position.height);
EditorGUI.DrawRect(rect, new Color(0.11f, 0.11f, 0.11f));
Matrix4x4 oldMatrix = GUI.matrix;
Vector2 origin = new Vector2(position.width / 2, position.height / 2);
GUI.matrix = Matrix4x4.TRS(origin + pan, Quaternion.identity, Vector3.one * zoom) *
Matrix4x4.TRS(-origin, Quaternion.identity, Vector3.one);
// Draw edges first
foreach (var e in edges) {
var from = GetNodeById(e.fromId);
var to = GetNodeById(e.toId);
if (from == null || to == null) continue;
DrawEdgeCircleNodes(from, to);
}
// Draw nodes (circles)
foreach (var n in nodes) {
DrawNodeCircle(n);
}
GUI.matrix = oldMatrix;
// Footer toolbar
GUILayout.FlexibleSpace();
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
if (GUILayout.Button("Fit", EditorStyles.toolbarButton)) FitToView();
if (GUILayout.Button("Layout LR", EditorStyles.toolbarButton)) ComputeLeftToRightLayout();
if (GUILayout.Button("Add Node", EditorStyles.toolbarButton)) {
AddNode("N" + nodes.Count);
ComputeLeftToRightLayout(); ComputeLeftToRightLayout();
} }
} if (GUILayout.Button("Add Edge (selected->new)", EditorStyles.toolbarButton)) {
EditorGUILayout.EndHorizontal(); if (selectedNodeId != -1) {
} var newNode = AddNode("N" + nodes.Count);
edges.Add(new DagEdge() { fromId = selectedNodeId, toId = newNode.id });
void HandleInput() ComputeLeftToRightLayout();
{ }
Event e = Event.current; }
EditorGUILayout.EndHorizontal();
// Zoom with scroll
if (e.type == EventType.ScrollWheel)
{
float oldZoom = zoom;
float delta = -e.delta.y * 0.01f;
zoom = Mathf.Clamp(zoom + delta, minZoom, maxZoom);
Vector2 mouse = e.mousePosition;
pan += (mouse - new Vector2(position.width / 2, position.height / 2)) * (1 - zoom / oldZoom);
e.Use();
} }
// Pan with middle or right+ctrl drag void HandleInput() {
if (e.type == EventType.MouseDrag && (e.button == 2 || (e.button == 1 && e.control))) Event e = Event.current;
{
pan += e.delta;
e.Use();
}
// Node dragging & selection (convert mouse to graph space) // Zoom with scroll
Vector2 graphMouse = ScreenToGraph(e.mousePosition); if (e.type == EventType.ScrollWheel) {
if (e.type == EventType.MouseDown && e.button == 0) float oldZoom = zoom;
{ float delta = -e.delta.y * 0.01f;
int hit = HitTestNode(graphMouse); zoom = Mathf.Clamp(zoom + delta, minZoom, maxZoom);
if (hit != -1) Vector2 mouse = e.mousePosition;
{ pan += (mouse - new Vector2(position.width / 2, position.height / 2)) * (1 - zoom / oldZoom);
selectedNodeId = hit;
draggingNode = true;
draggingNodeId = hit;
dragStart = graphMouse;
e.Use(); e.Use();
} }
else
{
selectedNodeId = -1;
}
}
if (draggingNode && draggingNodeId != -1) // Pan with middle or right+ctrl drag
{ if (e.type == EventType.MouseDrag && (e.button == 2 || (e.button == 1 && e.control))) {
if (e.type == EventType.MouseDrag && e.button == 0) pan += e.delta;
{ e.Use();
Vector2 graphDelta = e.delta / zoom; }
var n = GetNodeById(draggingNodeId);
if (n != null) // Node dragging & selection (convert mouse to graph space)
{ Vector2 graphMouse = ScreenToGraph(e.mousePosition);
n.position += graphDelta; if (e.type == EventType.MouseDown && e.button == 0) {
Repaint(); int hit = HitTestNode(graphMouse);
if (hit != -1) {
selectedNodeId = hit;
draggingNode = true;
draggingNodeId = hit;
dragStart = graphMouse;
e.Use();
}
else {
selectedNodeId = -1;
}
}
if (draggingNode && draggingNodeId != -1) {
if (e.type == EventType.MouseDrag && e.button == 0) {
Vector2 graphDelta = e.delta / zoom;
var n = GetNodeById(draggingNodeId);
if (n != null) {
n.position += graphDelta;
Repaint();
e.Use();
}
}
if (e.type == EventType.MouseUp && e.button == 0) {
draggingNode = false;
draggingNodeId = -1;
e.Use(); e.Use();
} }
} }
if (e.type == EventType.MouseUp && e.button == 0) }
{
draggingNode = false; DagNode AddNode(string title) {
draggingNodeId = -1; int nextId = nodes.Count > 0 ? nodes.Max(n => n.id) + 1 : 0;
e.Use(); var n = new DagNode() { id = nextId, title = title, position = Vector2.zero };
nodes.Add(n);
return n;
}
DagNode GetNodeById(int id) => nodes.FirstOrDefault(x => x.id == id);
void DrawNodeCircle(DagNode n) {
Vector2 center = n.position;
float r = n.radius;
Rect nodeRect = new Rect(center.x - r, center.y - r, r * 2, r * 2);
// circle background
Color bg = (n.id == selectedNodeId) ? new Color(0.15f, 0.5f, 0.9f) : new Color(0.2f, 0.2f, 0.2f);
EditorGUI.DrawRect(nodeRect, bg);
// anti-aliased circle outline
Handles.color = Color.white * 0.9f;
Handles.DrawAAPolyLine(3f / zoom, GetCircleOutlinePoints(center, r, 48).ToArray());
// label
Vector2 labelPos = center - new Vector2(0, 8);
GUI.Label(new Rect(labelPos.x - r, labelPos.y - 8, r * 2, 18), n.title, labelStyle);
}
List<Vector3> GetCircleOutlinePoints(Vector2 center, float radius, int segments) {
var pts = new List<Vector3>(segments + 1);
for (int i = 0; i <= segments; i++) {
float a = (float)i / segments * Mathf.PI * 2f;
pts.Add(new Vector3(center.x + Mathf.Cos(a) * radius, center.y + Mathf.Sin(a) * radius, 0));
} }
} return pts;
}
DagNode AddNode(string title)
{
int nextId = nodes.Count > 0 ? nodes.Max(n => n.id) + 1 : 0;
var n = new DagNode() { id = nextId, title = title, position = Vector2.zero };
nodes.Add(n);
return n;
}
DagNode GetNodeById(int id) => nodes.FirstOrDefault(x => x.id == id);
void DrawNodeCircle(DagNode n)
{
Vector2 center = n.position;
float r = n.radius;
Rect nodeRect = new Rect(center.x - r, center.y - r, r * 2, r * 2);
// circle background
Color bg = (n.id == selectedNodeId) ? new Color(0.15f, 0.5f, 0.9f) : new Color(0.2f, 0.2f, 0.2f);
EditorGUI.DrawRect(nodeRect, bg);
// anti-aliased circle outline
Handles.color = Color.white * 0.9f;
Handles.DrawAAPolyLine(3f / zoom, GetCircleOutlinePoints(center, r, 48).ToArray());
// label
Vector2 labelPos = center - new Vector2(0, 8);
GUI.Label(new Rect(labelPos.x - r, labelPos.y - 8, r * 2, 18), n.title, labelStyle);
}
List<Vector3> GetCircleOutlinePoints(Vector2 center, float radius, int segments)
{
var pts = new List<Vector3>(segments + 1);
for (int i = 0; i <= segments; i++)
{
float a = (float)i / segments * Mathf.PI * 2f;
pts.Add(new Vector3(center.x + Mathf.Cos(a) * radius, center.y + Mathf.Sin(a) * radius, 0));
}
return pts;
}
void DrawEdgeCircleNodes(DagNode from, DagNode to)
{
Vector2 a = from.position;
Vector2 b = to.position;
if (a == b) return;
// Compute edge line that starts/ends at circle circumferences
Vector2 dir = (b - a).normalized;
Vector2 start = a + dir * from.radius;
Vector2 end = b - dir * to.radius;
// Use a simple curved line: start -> control -> end (bezier)
Vector2 control = new Vector2((start.x + end.x) / 2f, (start.y + end.y) / 2f);
// Slight vertical offset to separate overlapping lines based on node ids
float offset = ((from.id * 7 + to.id * 11) % 7 - 3) * 6f / zoom;
control += new Vector2(0, offset);
Handles.color = Color.white * 0.9f;
Handles.DrawAAPolyLine(3f / zoom, 20, GetBezierPoints(start, control, end, 24).ToArray());
// Arrow at end pointing towards 'b'
DrawArrowHead(end - dir * 2f, end, 10f / zoom, 12f / zoom, Color.white);
}
List<Vector3> GetBezierPoints(Vector2 p0, Vector2 p1, Vector2 p2, int seg)
{
var pts = new List<Vector3>(seg + 1);
for (int i = 0; i <= seg; i++)
{
float t = (float)i / seg;
Vector2 p = (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2;
pts.Add(new Vector3(p.x, p.y, 0));
}
return pts;
}
void DrawArrowHead(Vector2 from, Vector2 to, float headWidth, float headLength, Color color)
{
Vector2 dir = (to - from).normalized;
if (dir == Vector2.zero) return;
Vector2 right = new Vector2(-dir.y, dir.x);
Vector3 p1 = to;
Vector3 p2 = to - dir * headLength + right * headWidth * 0.5f;
Vector3 p3 = to - dir * headLength - right * headWidth * 0.5f;
Handles.color = color;
Handles.DrawAAConvexPolygon(p1, p2, p3);
}
// Left-to-right layered layout (sources on the left, sinks on the right)
void ComputeLeftToRightLayout()
{
// build adjacency and indegree
var adj = nodes.ToDictionary(n => n.id, n => new List<int>());
var indeg = nodes.ToDictionary(n => n.id, n => 0);
foreach (var e in edges)
{
if (!adj.ContainsKey(e.fromId) || !adj.ContainsKey(e.toId)) continue;
adj[e.fromId].Add(e.toId);
indeg[e.toId]++;
} }
// Kahn's algorithm to compute topological layers (horizontal layers) void DrawEdgeCircleNodes(DagNode from, DagNode to) {
Dictionary<int, int> layer = new Dictionary<int, int>(); Vector2 a = from.position;
Queue<int> q = new Queue<int>(indeg.Where(kv => kv.Value == 0).Select(kv => kv.Key)); Vector2 b = to.position;
foreach (var id in q) layer[id] = 0; if (a == b) return;
while (q.Count > 0) // Compute edge line that starts/ends at circle circumferences
{ Vector2 dir = (b - a).normalized;
int u = q.Dequeue(); Vector2 start = a + dir * from.radius;
int l = layer[u]; Vector2 end = b - dir * to.radius;
foreach (var v in adj[u])
{ // Use a simple curved line: start -> control -> end (bezier)
// prefer placing v at least one layer after u Vector2 control = new Vector2((start.x + end.x) / 2f, (start.y + end.y) / 2f);
if (!layer.ContainsKey(v) || layer[v] < l + 1) layer[v] = l + 1; // Slight vertical offset to separate overlapping lines based on node ids
indeg[v]--; float offset = ((from.id * 7 + to.id * 11) % 7 - 3) * 6f / zoom;
if (indeg[v] == 0) q.Enqueue(v); control += new Vector2(0, offset);
Handles.color = Color.white * 0.9f;
Handles.DrawAAPolyLine(3f / zoom, 20, GetBezierPoints(start, control, end, 24).ToArray());
// Arrow at end pointing towards 'b'
DrawArrowHead(end - dir * 2f, end, 10f / zoom, 12f / zoom, Color.white);
}
List<Vector3> GetBezierPoints(Vector2 p0, Vector2 p1, Vector2 p2, int seg) {
var pts = new List<Vector3>(seg + 1);
for (int i = 0; i <= seg; i++) {
float t = (float)i / seg;
Vector2 p = (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2;
pts.Add(new Vector3(p.x, p.y, 0));
} }
return pts;
} }
// Any unreachable nodes -> assign next layers void DrawArrowHead(Vector2 from, Vector2 to, float headWidth, float headLength, Color color) {
int maxLayer = layer.Count > 0 ? layer.Values.Max() : 0; Vector2 dir = (to - from).normalized;
foreach (var n in nodes) if (dir == Vector2.zero) return;
{ Vector2 right = new Vector2(-dir.y, dir.x);
if (!layer.ContainsKey(n.id))
{ Vector3 p1 = to;
maxLayer++; Vector3 p2 = to - dir * headLength + right * headWidth * 0.5f;
layer[n.id] = maxLayer; Vector3 p3 = to - dir * headLength - right * headWidth * 0.5f;
Handles.color = color;
Handles.DrawAAConvexPolygon(p1, p2, p3);
}
// Left-to-right layered layout (sources on the left, sinks on the right)
void ComputeLeftToRightLayout() {
// build adjacency and indegree
var adj = nodes.ToDictionary(n => n.id, n => new List<int>());
var indeg = nodes.ToDictionary(n => n.id, n => 0);
foreach (var e in edges) {
if (!adj.ContainsKey(e.fromId) || !adj.ContainsKey(e.toId)) continue;
adj[e.fromId].Add(e.toId);
indeg[e.toId]++;
} }
}
// Group nodes by layer (left to right) // Kahn's algorithm to compute topological layers (horizontal layers)
var layers = layer.GroupBy(kv => kv.Value).OrderBy(g => g.Key).Select(g => g.Select(x => x.Key).ToList()).ToList(); Dictionary<int, int> layer = new Dictionary<int, int>();
Queue<int> q = new Queue<int>(indeg.Where(kv => kv.Value == 0).Select(kv => kv.Key));
foreach (var id in q) layer[id] = 0;
// Layout parameters (horizontal spacing drives left->right) while (q.Count > 0) {
float hSpacing = 220f; int u = q.Dequeue();
float vSpacing = 120f; int l = layer[u];
foreach (var v in adj[u]) {
// Place nodes: x increases with layer index, y spaced within layer // prefer placing v at least one layer after u
for (int li = 0; li < layers.Count; li++) if (!layer.ContainsKey(v) || layer[v] < l + 1) layer[v] = l + 1;
{ indeg[v]--;
var lst = layers[li]; if (indeg[v] == 0) q.Enqueue(v);
float totalHeight = (lst.Count - 1) * vSpacing; }
for (int i = 0; i < lst.Count; i++)
{
int id = lst[i];
var n = GetNodeById(id);
if (n == null) continue;
float x = li * hSpacing;
float y = -totalHeight / 2f + i * vSpacing;
n.position = new Vector2(x, y);
} }
// Any unreachable nodes -> assign next layers
int maxLayer = layer.Count > 0 ? layer.Values.Max() : 0;
foreach (var n in nodes) {
if (!layer.ContainsKey(n.id)) {
maxLayer++;
layer[n.id] = maxLayer;
}
}
// Group nodes by layer (left to right)
var layers = layer.GroupBy(kv => kv.Value).OrderBy(g => g.Key).Select(g => g.Select(x => x.Key).ToList()).ToList();
// Layout parameters (horizontal spacing drives left->right)
float hSpacing = 220f;
float vSpacing = 120f;
// Place nodes: x increases with layer index, y spaced within layer
for (int li = 0; li < layers.Count; li++) {
var lst = layers[li];
float totalHeight = (lst.Count - 1) * vSpacing;
for (int i = 0; i < lst.Count; i++) {
int id = lst[i];
var n = GetNodeById(id);
if (n == null) continue;
float x = li * hSpacing;
float y = -totalHeight / 2f + i * vSpacing;
n.position = new Vector2(x, y);
}
}
Repaint();
} }
Repaint(); void FitToView() {
} if (nodes.Count == 0) return;
Rect bounds = new Rect(nodes[0].position - Vector2.one * nodes[0].radius, Vector2.one * nodes[0].radius * 2f);
foreach (var n in nodes)
bounds = RectUnion(bounds, new Rect(n.position - Vector2.one * n.radius, Vector2.one * n.radius * 2f));
void FitToView() Vector2 center = bounds.center;
{ pan = -center;
if (nodes.Count == 0) return; zoom = 1.0f;
Rect bounds = new Rect(nodes[0].position - Vector2.one * nodes[0].radius, Vector2.one * nodes[0].radius * 2f); Repaint();
foreach (var n in nodes) }
bounds = RectUnion(bounds, new Rect(n.position - Vector2.one * n.radius, Vector2.one * n.radius * 2f));
static Rect RectUnion(Rect a, Rect b) {
Vector2 center = bounds.center; float xMin = Mathf.Min(a.xMin, b.xMin);
pan = -center; float xMax = Mathf.Max(a.xMax, b.xMax);
zoom = 1.0f; float yMin = Mathf.Min(a.yMin, b.yMin);
Repaint(); float yMax = Mathf.Max(a.yMax, b.yMax);
} return Rect.MinMaxRect(xMin, yMin, xMax, yMax);
}
static Rect RectUnion(Rect a, Rect b)
{ Vector2 ScreenToGraph(Vector2 screenPos) {
float xMin = Mathf.Min(a.xMin, b.xMin); Vector2 origin = new Vector2(position.width / 2, position.height / 2);
float xMax = Mathf.Max(a.xMax, b.xMax); // invert the GUI.matrix transform (approx for current simple transforms)
float yMin = Mathf.Min(a.yMin, b.yMin); return (screenPos - (origin + pan)) / zoom + origin * (1 - 1 / zoom);
float yMax = Mathf.Max(a.yMax, b.yMax); }
return Rect.MinMaxRect(xMin, yMin, xMax, yMax);
} int HitTestNode(Vector2 graphPos) {
// returns node id under point or -1
Vector2 ScreenToGraph(Vector2 screenPos) for (int i = nodes.Count - 1; i >= 0; i--) {
{ var n = nodes[i];
Vector2 origin = new Vector2(position.width / 2, position.height / 2); if ((graphPos - n.position).sqrMagnitude <= n.radius * n.radius) return n.id;
// invert the GUI.matrix transform (approx for current simple transforms) }
return (screenPos - (origin + pan)) / zoom + origin * (1 - 1 / zoom); return -1;
}
int HitTestNode(Vector2 graphPos)
{
// returns node id under point or -1
for (int i = nodes.Count - 1; i >= 0; i--)
{
var n = nodes[i];
if ((graphPos - n.position).sqrMagnitude <= n.radius * n.radius) return n.id;
} }
return -1;
} }
} }

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -52,7 +52,7 @@ TextureImporter:
spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1 spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1 alphaUsage: 1
alphaIsTransparency: 0 alphaIsTransparency: 1
spriteTessellationDetail: -1 spriteTessellationDetail: -1
textureType: 0 textureType: 0
textureShape: 1 textureShape: 1

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -52,7 +52,7 @@ TextureImporter:
spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1 spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1 alphaUsage: 1
alphaIsTransparency: 0 alphaIsTransparency: 1
spriteTessellationDetail: -1 spriteTessellationDetail: -1
textureType: 0 textureType: 0
textureShape: 1 textureShape: 1

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -4,46 +4,50 @@ using UnityEditor.UIElements;
using UnityEngine; using UnityEngine;
using UnityEngine.UIElements; using UnityEngine.UIElements;
[CustomEditor(typeof(NanoBrain))] namespace NanoBrain {
public class NanoBrainComponent_Editor : Editor {
protected static VisualElement mainContainer;
protected static VisualElement inspectorContainer;
protected NanoBrain component; [CustomEditor(typeof(NanoBrain))]
private SerializedProperty brainProp; public class NanoBrainComponent_Editor : Editor {
protected static VisualElement mainContainer;
protected static VisualElement inspectorContainer;
ClusterInspector.GraphView board; protected NanoBrain component;
private SerializedProperty brainProp;
public void OnEnable() { ClusterInspector.GraphView board;
component = target as NanoBrain;
if (Application.isPlaying == false && serializedObject != null) { public void OnEnable() {
string propertyName = nameof(NanoBrain.defaultBrain); component = target as NanoBrain;
brainProp = serializedObject.FindProperty(propertyName);
}
}
public override VisualElement CreateInspectorGUI() { if (Application.isPlaying == false && serializedObject != null) {
Cluster brain = component.brain; string propertyName = nameof(NanoBrain.defaultBrain);
brainProp = serializedObject.FindProperty(propertyName);
if (Application.isPlaying == false) }
serializedObject.Update();
VisualElement root = new();
if (Application.isPlaying == false) {
PropertyField brainField = new(brainProp) {
label = "Nano Brain"
};
root.Add(brainField);
} }
if (brain != null) public override VisualElement CreateInspectorGUI() {
ClusterInspector.CreateInspector(root, brain.prefab, brain.defaultOutput, component.gameObject); Cluster brain = component.brain;
if (Application.isPlaying == false)
serializedObject.Update();
VisualElement root = new();
if (Application.isPlaying == false) {
PropertyField brainField = new(brainProp) {
label = "Nano Brain"
};
root.Add(brainField);
}
if (brain != null)
ClusterInspector.CreateInspector(root, brain.prefab, brain.defaultOutput, component.gameObject);
if (Application.isPlaying == false)
serializedObject.ApplyModifiedProperties();
return root;
}
if (Application.isPlaying == false)
serializedObject.ApplyModifiedProperties();
return root;
} }
} }

View File

@ -1,73 +0,0 @@
using UnityEngine;
public interface IReceptor {
public string GetName();
public Nucleus[] nucleiArray { get; set; }
public void AddReceptorElement(ClusterPrefab prefab);
public void RemoveReceptorElement();
public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1);
public void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null);
}
public static class IReceptorHelpers {
public static void AddReceptorElement(IReceptor receptor, ClusterPrefab prefab) {
if (receptor.nucleiArray.Length == 0) {
Debug.LogError("Empty perceptoid array, cannot add");
}
int newLength = receptor.nucleiArray.Length + 1;
Nucleus[] newArray = new Nucleus[newLength];
string baseName = receptor.GetName();
int colonPos = baseName.IndexOf(":");
if (colonPos > 0)
baseName = baseName[..colonPos];
for (int i = 0; i < receptor.nucleiArray.Length; i++)
newArray[i] = receptor.nucleiArray[i];
if (receptor.nucleiArray[0] is Nucleus nucleus) {
newArray[newLength - 1] = nucleus.Clone(prefab);
newArray[newLength - 1].name = $"{baseName}: {newLength - 1}";
}
foreach (Nucleus element in receptor.nucleiArray) {
if (element is IReceptor receptorElement) {
receptorElement.nucleiArray = newArray;
}
}
}
public static void RemoveReceptorElement(IReceptor receptor) {
int newLength = receptor.nucleiArray.Length - 1;
if (newLength == 0) {
Debug.LogWarning("Perceptoid array cannot be empty");
}
Nucleus[] newArray = new Nucleus[newLength];
for (int i = 0; i < newLength; i++)
newArray[i] = receptor.nucleiArray[i];
// Delete the last perception
if (receptor.nucleiArray[newLength] is Nucleus nucleus)
Neuron.Delete(nucleus);
foreach (Nucleus element in receptor.nucleiArray) {
if (element is IReceptor receptorElement) {
receptorElement.nucleiArray = newArray;
}
}
}
public static void AddArrayReceiver(IReceptor receptor, Nucleus receiverToAdd, float weight = 1) {
foreach (Nucleus element in receptor.nucleiArray) {
if (element is Cluster cluster)
cluster.defaultOutput.AddReceiver(receiverToAdd, weight);
if (element is Neuron neuron)
neuron.AddReceiver(receiverToAdd, weight);
}
}
}

View File

@ -1,65 +0,0 @@
using System;
#if UNITY_MATHEMATICS
using Unity.Mathematics;
#endif
[Serializable]
public class MemoryCell : Neuron {
public MemoryCell(ClusterPrefab cluster, string name) : base(cluster, name) { }
public MemoryCell(Cluster parent, string name) : base(parent, name) { }
public bool staticMemory = false;
public override bool isSleeping {
get {
if (staticMemory)
return false;
return base.isSleeping;
}
}
public override Nucleus ShallowCloneTo(Cluster newParent) {
MemoryCell clone = new(newParent, this.name);
CloneFields(clone);
clone.staticMemory = this.staticMemory;
return clone;
}
#region State
private bool initialized = false;
#if UNITY_MATHEMATICS
private float3 _memorizedValue;
#else
private UnityEngine.Vector3 _memorizedValue;
#endif
public override void UpdateStateIsolated() {
// A memorycell does not have an activation function
var result = Combinator();
if (initialized)
// Output the previous, memorized value
this.outputValue = this._memorizedValue;
else {
// The first time, the result is directly set in output
this.outputValue = result;
this.initialized = true;
}
// Store the result for the next time
this._memorizedValue = result;
}
public override void UpdateNuclei() {
if (staticMemory)
// Static memory does not get stale or go to sleep
return;
base.UpdateNuclei();
}
#endregion State
}

View File

@ -1,31 +0,0 @@
using System;
using UnityEngine;
public class NanoBrain : MonoBehaviour {
public ClusterPrefab defaultBrain;
[NonSerialized]
private Cluster brainInstance;
public Cluster brain {
get {
if (brainInstance == null && defaultBrain != null) {
brainInstance = new Cluster(defaultBrain) {
name = defaultBrain.name + " (Instance)"
};
}
return brainInstance;
}
}
public static void UpdateWeight(Cluster brain, string name, float weight) {
Nucleus 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}");
}
}
}
}
}

445
Neuron.cs
View File

@ -1,445 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
#if UNITY_MATHEMATICS
using Unity.Mathematics;
using static Unity.Mathematics.math;
#endif
[Serializable]
public class Neuron : Nucleus {
public Neuron(Cluster parent, string name) {
this.parent = parent;
this.name = name;
this.parent?.clusterNuclei.Add(this);
}
public Neuron(ClusterPrefab prefab, string name) {
this.clusterPrefab = prefab;
this.name = name;
if (this.clusterPrefab != null)
this.clusterPrefab.nuclei.Add(this);
else
Debug.LogError("No prefab when adding neuron to prefab");
}
#region Serialization
public enum CombinatorType {
Sum,
Product,
Max
}
public CombinatorType combinator = CombinatorType.Sum;
public enum CurvePresets {
Linear,
Power,
Sqrt,
Reciprocal,
Custom
}
[SerializeField]
public CurvePresets _curvePreset;
public CurvePresets curvePreset {
get { return _curvePreset; }
set {
_curvePreset = value;
this.curve = GenerateCurve();
}
}
public AnimationCurve curve;
public float curveMax = 1.0f;
public AnimationCurve GenerateCurve() {
switch (this.curvePreset) {
case CurvePresets.Linear:
this.curveMax = 1;
return Presets.Linear(1);
case CurvePresets.Power:
this.curveMax = 1;
return Presets.Power(2.0f, 1);
case CurvePresets.Sqrt:
this.curveMax = 1;
return Presets.Power(0.5f, 1);
case CurvePresets.Reciprocal:
this.curveMax = 1 / 0.01f * 1;
return Presets.Reciprocal(1);
default:
this.curveMax = 1;
return this.curve;
}
}
public static class Presets {
private const int samples = 32;
public static AnimationCurve Linear(float weight) {
return AnimationCurve.Linear(0f, 0f, 1000f, weight * 1000);
}
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;
}
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;
}
}
#endregion Serialization
#if UNITY_MATHEMATICS
protected float3 _outputValue;
public virtual float3 outputValue {
get { return _outputValue; }
set {
_outputValue = value;
if (this.isFiring)
WhenFiring?.Invoke();
}
}
public float outputMagnitude => length(_outputValue);
public bool isFiring => length(_outputValue) > 0.5f;
public virtual bool isSleeping => lengthsq(this.outputValue) == 0;
#else
protected Vector3 _outputValue;
public virtual Vector3 outputValue {
get { return _outputValue; }
set {
_outputValue = value;
if (this.isFiring)
WhenFiring?.Invoke();
}
}
public float outputMagnitude => _outputValue.magnitude;
public bool isFiring => _outputValue.magnitude > 0.5f;
public virtual bool isSleeping => this.outputValue.sqrMagnitude == 0;
#endif
public Action WhenFiring;
[NonSerialized]
public int stale = 1000;
public readonly int staleValueForSleep = 20;
// this clone the nucleus without the synapses and receivers
public override Nucleus ShallowCloneTo(Cluster newParent) {
Neuron clone = new(newParent, this.name);
CloneFields(clone);
return clone;
}
public override Nucleus Clone(ClusterPrefab prefab) {
Neuron clone = new(prefab, this.name);
CloneFields(clone);
foreach (Synapse synapse in this.synapses) {
Synapse clonedSynapse = clone.AddSynapse(synapse.neuron);
clonedSynapse.weight = synapse.weight;
}
foreach (Nucleus receiver in this.receivers) {
clone.AddReceiver(receiver);
}
return clone;
}
protected virtual void CloneFields(Neuron clone) {
clone.clusterPrefab = this.clusterPrefab;
clone.bias = this.bias;
clone.combinator = this.combinator;
clone.curve = this.curve;
clone.curvePreset = this.curvePreset;
clone.curveMax = this.curveMax;
}
public static void Delete(Nucleus nucleus) {
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.neuron is Neuron synapse_nucleus) {
if (synapse_nucleus.receivers.Count > 1) {
// there is another nucleus feeding into this input nucleus
synapse_nucleus.receivers.RemoveAll(r => r == nucleus);
}
else {
// No other links, delete it.
Neuron.Delete(synapse_nucleus);
}
}
}
if (nucleus is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers) {
if (receiver != null && receiver.synapses != null)
receiver.synapses.RemoveAll(s => s.neuron == nucleus);
}
}
else if (nucleus is Cluster cluster) {
// remove all receivers for this cluster
foreach (Neuron output in cluster.outputs) {
foreach (Nucleus receiver in output.receivers) {
receiver.synapses.RemoveAll(s => s.neuron == output);
}
}
}
if (nucleus.clusterPrefab != null) {
nucleus.clusterPrefab.nuclei.RemoveAll(n => n == nucleus);
nucleus.clusterPrefab.RefreshOutputs();
nucleus.clusterPrefab.GarbageCollection();
}
}
public override void UpdateStateIsolated() {
var result = Combinator();
this.outputValue = Activator(result);
}
#region Combinator
#if UNITY_MATHEMATICS
protected Func<float3> Combinator => combinator switch {
CombinatorType.Sum => CombinatorSum,
CombinatorType.Product => CombinatorProduct,
CombinatorType.Max => CombinatorMax,
_ => CombinatorSum
};
public float3 CombinatorSum() {
float3 sum = this.bias;
foreach (Synapse synapse in this.synapses)
sum += synapse.weight * synapse.neuron.outputValue;
return sum;
}
public float3 CombinatorProduct() {
float3 product = this.bias;
foreach (Synapse synapse in this.synapses) {
product *= synapse.weight * synapse.neuron.outputValue;
}
return product;
}
public float3 CombinatorMax() {
float3 max = this.bias;
float maxLength = length(max);
//Applying the weight factors
foreach (Synapse synapse in this.synapses) {
float3 input = synapse.weight * synapse.neuron.outputValue;
float inputLength = length(input);
if (inputLength > maxLength) {
max = input;
maxLength = inputLength;
}
}
return max;
}
#else
protected Func<Vector3> Combinator => combinator switch {
CombinatorType.Sum => CombinatorSum,
CombinatorType.Product => CombinatorProduct,
CombinatorType.Max => CombinatorMax,
_ => CombinatorSum
};
public Vector3 CombinatorSum() {
Vector3 sum = this.bias;
foreach (Synapse synapse in this.synapses)
sum += synapse.weight * synapse.neuron.outputValue;
return sum;
}
public Vector3 CombinatorProduct() {
Vector3 product = this.bias;
foreach (Synapse synapse in this.synapses) {
//product *= synapse.weight * synapse.neuron.outputValue;
product = Vector3.Scale(product, synapse.weight * synapse.neuron.outputValue);
}
return product;
}
public Vector3 CombinatorMax() {
Vector3 max = this.bias;
float maxLength = max.magnitude;
//Applying the weight factors
foreach (Synapse synapse in this.synapses) {
Vector3 input = synapse.weight * synapse.neuron.outputValue;
float inputLength = input.magnitude;
if (inputLength > maxLength) {
max = input;
maxLength = inputLength;
}
}
return max;
}
#endif
#endregion Combinator
#region Activator
#if UNITY_MATHEMATICS
public Func<float3, float3> Activator => this.curvePreset switch {
CurvePresets.Linear => ActivatorLinear,
CurvePresets.Sqrt => ActivatorSqrt,
CurvePresets.Power => ActivatorPower,
CurvePresets.Reciprocal => ActivatorReciprocal,
_ => ActivatorCustom
};
protected float3 ActivatorLinear(float3 input) {
return input;
}
protected float3 ActivatorSqrt(float3 input) {
float3 result = normalize(input) * System.MathF.Sqrt(length(input));
return result;
}
protected float3 ActivatorPower(float3 input) {
float3 result = normalize(input) * System.MathF.Pow(length(input), 2);
return result;
}
protected float3 ActivatorReciprocal(float3 input) {
float magnitude = length(input);
if (magnitude == 0)
return new float3(0, 0, 0);
float3 result = normalize(input) * (1 / magnitude);
return result;
}
protected float3 ActivatorCustom(float3 input) {
float activatedValue = this.curve.Evaluate(length(input));
float3 result = normalize(input) * activatedValue;
return result;
}
#else
public Func<Vector3, Vector3> Activator => this.curvePreset switch {
CurvePresets.Linear => ActivatorLinear,
CurvePresets.Sqrt => ActivatorSqrt,
CurvePresets.Power => ActivatorPower,
CurvePresets.Reciprocal => ActivatorReciprocal,
_ => ActivatorCustom
};
protected Vector3 ActivatorLinear(Vector3 input) {
return input;
}
protected Vector3 ActivatorSqrt(Vector3 input) {
Vector3 result = input.normalized * System.MathF.Sqrt(input.magnitude);
return result;
}
protected Vector3 ActivatorPower(Vector3 input) {
Vector3 result = input.normalized * System.MathF.Pow(input.magnitude, 2);
return result;
}
protected Vector3 ActivatorReciprocal(Vector3 input) {
float magnitude = input.magnitude;
if (magnitude == 0)
return new Vector3(0, 0, 0);
Vector3 result = input.normalized * (1 / magnitude);
return result;
}
protected Vector3 ActivatorCustom(Vector3 input) {
float activatedValue = this.curve.Evaluate(input.magnitude);
Vector3 result = input.normalized * activatedValue;
return result;
}
#endif
#endregion Activator
#region Receivers
[SerializeReference]
private List<Nucleus> _receivers = new();
public virtual List<Nucleus> receivers {
get { return _receivers; }
set { _receivers = value; }
}
public virtual void AddReceiver(Nucleus receiverToAdd, float weight = 1) {
this._receivers.Add(receiverToAdd);
receiverToAdd.AddSynapse(this, weight);
}
public virtual void RemoveReceiver(Nucleus receiverToRemove) {
if (this is IReceptor receptor) {
foreach (Nucleus element in receptor.nucleiArray) {
if (element is Neuron neuron) {
neuron._receivers.RemoveAll(receiver => receiver == receiverToRemove);
receiverToRemove.synapses.RemoveAll(synapse => synapse.neuron == neuron);
}
}
}
else {
this._receivers.RemoveAll(receiver => receiver == receiverToRemove);
receiverToRemove.synapses.RemoveAll(synapse => synapse.neuron == this);
}
}
#endregion Receivers
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
if (this.parent is ClusterReceptor clusterReceptor)
clusterReceptor.ProcessStimulus(this, inputValue, thingId, thingName);
else
ProcessStimulusDirect(inputValue, thingId, thingName);
}
public void ProcessStimulusDirect(Vector3 inputValue, int thingId = 0, string thingName = null) {
this.stale = 0;
this.bias = inputValue;
this.parent.UpdateFromNucleus(this);
}
}

View File

@ -1,72 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public abstract class Nucleus {
public string name;
[SerializeReference]
public ClusterPrefab clusterPrefab;
[SerializeReference]
public Cluster parent;
public bool trace = false;
public abstract Nucleus ShallowCloneTo(Cluster parent);
public abstract Nucleus Clone(ClusterPrefab prefab);
public enum Type {
None,
Neuron,
MemoryCell,
Cluster,
Receptor,
ClusterReceptor,
}
#region Synapses
public Vector3 bias = Vector3.zero;
[SerializeField]
private List<Synapse> _synapses = new();
public List<Synapse> synapses => _synapses;
public Synapse AddSynapse(Neuron sendingNucleus, float weight = 1.0f) {
Synapse synapse = new(sendingNucleus, weight);
this.synapses.Add(synapse);
return synapse;
}
public Synapse GetSynapse(Nucleus sender) {
foreach (Synapse synapse in this.synapses)
if (synapse.neuron == sender)
return synapse;
return null;
}
public void RemoveSynapse(Nucleus sendingNucleus) {
this.synapses.RemoveAll(synapse => synapse.neuron == sendingNucleus);
}
#endregion Synapses
#region Update
public abstract void UpdateStateIsolated();
public virtual void UpdateNuclei() {
}
public virtual void SetBias(Vector3 inputValue) {
this.bias = inputValue;
this.parent.UpdateFromNucleus(this);
}
public virtual void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = "") {
}
#endregion Update
}

View File

@ -1,208 +0,0 @@
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_MATHEMATICS
using Unity.Mathematics;
using static Unity.Mathematics.math;
#endif
[System.Serializable]
public class NucleusArray {
[SerializeReference]
private Nucleus[] _nuclei;
public Nucleus[] nuclei {
get {
return _nuclei;
}
set {
_nuclei = value;
}
}
public NucleusArray(Nucleus nucleus) {
this._nuclei = new Nucleus[1];
this._nuclei[0] = nucleus;
}
public NucleusArray(ClusterPrefab cluster) {
this._nuclei = new Nucleus[0];
}
public NucleusArray(int size, string name) {
this._nuclei = new Nucleus[size];
}
public void AddNucleus(ClusterPrefab prefab) {
if (this._nuclei.Length == 0) {
Debug.LogError("Empty perceptoid array, cannot add");
return;
}
int newLength = this._nuclei.Length + 1;
Nucleus[] newArray = new Nucleus[newLength];
for (int i = 0; i < this._nuclei.Length; i++)
newArray[i] = this._nuclei[i];
if (this._nuclei[0] is Nucleus nucleus) {
newArray[newLength - 1] = nucleus.Clone(prefab);
newArray[newLength - 1].name += $": {newLength - 1}";
}
this._nuclei = newArray;
}
public void RemoveNucleus() {
int newLength = this._nuclei.Length - 1;
if (newLength == 0) {
Debug.LogWarning("Perceptoid array cannot be empty");
return;
}
Nucleus[] newPerceptei = new Nucleus[newLength];
for (int i = 0; i < newLength; i++)
newPerceptei[i] = this._nuclei[i];
// Delete the last perception
if (this._nuclei[newLength] is Nucleus nucleus)
Neuron.Delete(nucleus); //this._nuclei[newLength]);
this._nuclei = newPerceptei;
}
public Dictionary<int, Nucleus> thingReceivers = new();
#if UNITY_MATHEMATICS
private Nucleus FindReceiver(int thingId, float3 inputValue) {
// No existing nucleus for this thing
float inputMagnitude = length(inputValue);
Neuron selectedReceiver = null;
float selectedMagnitude = 0;
foreach (Nucleus nucleusReceiver in this._nuclei) {
if (nucleusReceiver is not Neuron receiver)
continue;
if (thingReceivers.ContainsValue(receiver) == false) {
// We found an unusued receiver
thingReceivers.Add(thingId, receiver);
return receiver;
}
else if (receiver.isSleeping) {
// A sleeping receiver is not active and can therefore always be used
thingReceivers.Add(thingId, receiver);
return receiver;
}
else if (selectedReceiver == null) {
// If we haven't found a receiver yet, just start by taking the first
selectedReceiver = receiver;
selectedMagnitude = length(selectedReceiver.outputValue);
}
// Look for the receiver with the lowest magnitude
else {
float magnitude = length(receiver.outputValue);
if (magnitude < inputMagnitude && length(receiver.outputValue) < selectedMagnitude) {
selectedReceiver = receiver;
selectedMagnitude = length(selectedReceiver.outputValue);
}
}
}
if (selectedReceiver != null) {
// Replace the receiver
// Find the thingId current associated with the receiver
int keyToRemove = thingReceivers.FirstOrDefault(r => r.Value.Equals(selectedReceiver)).Key;
if (keyToRemove != 0 || thingReceivers.ContainsKey(keyToRemove))
thingReceivers.Remove(keyToRemove);
// And add the new association
thingReceivers.Add(thingId, selectedReceiver);
}
return selectedReceiver;
}
#else
private Nucleus FindReceiver(int thingId, Vector3 inputValue) {
// No existing nucleus for this thing
float inputMagnitude = inputValue.magnitude;
Neuron selectedReceiver = null;
float selectedMagnitude = 0;
foreach (Nucleus nucleusReceiver in this._nuclei) {
if (nucleusReceiver is not Neuron receiver)
continue;
if (thingReceivers.ContainsValue(receiver) == false) {
// We found an unusued receiver
thingReceivers.Add(thingId, receiver);
return receiver;
}
else if (receiver.isSleeping) {
// A sleeping receiver is not active and can therefore always be used
thingReceivers.Add(thingId, receiver);
return receiver;
}
else if (selectedReceiver == null) {
// If we haven't found a receiver yet, just start by taking the first
selectedReceiver = receiver;
selectedMagnitude = selectedReceiver.outputMagnitude;
}
// Look for the receiver with the lowest magnitude
else {
float magnitude = receiver.outputMagnitude;
if (magnitude < inputMagnitude && receiver.outputMagnitude < selectedMagnitude) {
selectedReceiver = receiver;
selectedMagnitude = selectedReceiver.outputMagnitude;
}
}
}
if (selectedReceiver != null) {
// Replace the receiver
// Find the thingId current associated with the receiver
int keyToRemove = thingReceivers.FirstOrDefault(r => r.Value.Equals(selectedReceiver)).Key;
if (keyToRemove != 0 || thingReceivers.ContainsKey(keyToRemove))
thingReceivers.Remove(keyToRemove);
// And add the new association
thingReceivers.Add(thingId, selectedReceiver);
}
return selectedReceiver;
}
#endif
public virtual void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) {
CleanupReceivers();
if (this._nuclei[0] is Neuron neuron)
inputValue = neuron.Activator(inputValue);
if (!thingReceivers.TryGetValue(thingId, out Nucleus selectedReceiver)) {
// No existing nucleus for this thing
selectedReceiver = FindReceiver(thingId, inputValue);
}
if (selectedReceiver == null)
return;
if (thingName != null) {
string baseName = selectedReceiver.name;
int colonPos = selectedReceiver.name.IndexOf(":");
if (colonPos > 0)
baseName = selectedReceiver.name[..colonPos];
selectedReceiver.name = baseName + ": " + thingName;
}
if (selectedReceiver is Neuron selectedNucleus)
selectedNucleus.ProcessStimulusDirect(inputValue);
}
private void CleanupReceivers() {
// Remove a thing-receiver connection when the nucleus is inactive
List<int> receiversToRemove = new();
foreach (KeyValuePair<int, Nucleus> item in thingReceivers) {
if (item.Value != null && item.Value is Neuron neuron && neuron.isSleeping)
receiversToRemove.Add(item.Key);
}
foreach (int thingId in receiversToRemove) {
Nucleus selectedReceiver = thingReceivers[thingId];
thingReceivers.Remove(thingId);
int colonPos = selectedReceiver.name.IndexOf(":");
if (colonPos > 0)
selectedReceiver.name = selectedReceiver.name[..colonPos];
}
}
}

View File

@ -1,94 +0,0 @@
using UnityEngine;
#if UNITY_MATHEMATICS
using Unity.Mathematics;
using static Unity.Mathematics.math;
#endif
[System.Serializable]
public class Receptor : Neuron, IReceptor {
public Receptor(Cluster parent, string name) : base(parent, name) {
this.array = new NucleusArray(this);
if (this.name.IndexOf(":") < 0)
this.name += ": 0";
}
public Receptor(ClusterPrefab prefab, string name) : base(prefab, name) {
this.array = new NucleusArray(this);
}
public string GetName() {
return this.name;
}
public override Nucleus ShallowCloneTo(Cluster parent) {
Receptor clone = new(parent, name) {
};
CloneFields(clone);
return clone;
}
public override Nucleus Clone(ClusterPrefab prefab) {
Receptor clone = new(prefab, name) {
array = this._array
};
CloneFields(clone);
// Adding receivers will also add synapses to the receivers
foreach (Nucleus receiver in this.receivers.ToArray())
clone.AddReceiver(receiver);
return clone;
}
[SerializeReference]
private NucleusArray _array;
public NucleusArray array {
set { _array = value; }
}
public Nucleus[] nucleiArray {
get { return _array.nuclei; }
set { _array.nuclei = value; }
}
public void AddReceptorElement(ClusterPrefab prefab) {
IReceptorHelpers.AddReceptorElement(this, prefab);
}
public void RemoveReceptorElement() {
IReceptorHelpers.RemoveReceptorElement(this);
}
public virtual void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) {
IReceptorHelpers.AddArrayReceiver(this, receiverToAdd, weight);
}
public override void UpdateStateIsolated() {
this.outputValue = this.bias;
}
#if UNITY_MATHEMATICS
public override void UpdateNuclei() {
this.stale++;
if (this.stale > staleValueForSleep && lengthsq(this.bias) > 0) {
this.bias = new float3(0, 0, 0);
this.parent.UpdateFromNucleus(this);
}
}
#else
public override void UpdateNuclei() {
this.stale++;
if (this.stale > staleValueForSleep && this.bias.sqrMagnitude > 0) {
this.bias = new Vector3(0, 0, 0);
this.parent.UpdateFromNucleus(this);
}
}
#endif
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
this._array ??= new NucleusArray(this.parent);
this._array.ProcessStimulus(thingId, inputValue, thingName);
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 363b69b84de0e4b729794c10e7c40ab5 guid: cfd403fd558edec539ab9d0a1bed0c72
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -6,9 +6,19 @@ using Unity.Mathematics;
using static Unity.Mathematics.math; using static Unity.Mathematics.math;
#endif #endif
namespace NanoBrain {
/// <summary>
/// A Cluster combines a collection of Nuclei to implement reusable behaviour
/// </summary>
/// A Cluster is an instantiation of a ClusterPrefab.
/// Clusters can be nested inside other clusters.
[Serializable] [Serializable]
public class Cluster : Nucleus { public class Cluster : Nucleus {
/// <summary>
/// The base name of the cluster. I don't think this is actively used at this moment
/// </summary>
public string baseName { public string baseName {
get { get {
int colonPositon = this.name.IndexOf(':'); int colonPositon = this.name.IndexOf(':');
@ -20,6 +30,11 @@ public class Cluster : Nucleus {
#region Init #region Init
/// <summary>
/// Instantiate a new copy of a ClusterPrefab in the given parent
/// </summary>
/// <param name="prefab">The prefab to use</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.name = prefab.name; this.name = prefab.name;
@ -32,6 +47,11 @@ public class Cluster : Nucleus {
this.sortedNuclei = TopologicalSort(this.clusterNuclei); this.sortedNuclei = TopologicalSort(this.clusterNuclei);
} }
/// <summary>
/// Add a new cluster to a ClusterPrefab
/// </summary>
/// <param name="prefab">The prefab to copy</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.name = prefab.name; this.name = prefab.name;
@ -45,6 +65,11 @@ public class Cluster : Nucleus {
this.sortedNuclei = TopologicalSort(this.clusterNuclei); this.sortedNuclei = TopologicalSort(this.clusterNuclei);
} }
/// <summary>
/// Clone a prefab.
/// </summary>
/// Strange that this does not take any parameters or return values.
/// Where which the clone be found???
private void ClonePrefab() { private void ClonePrefab() {
Nucleus[] prefabNuclei = this.prefab.nuclei.ToArray(); Nucleus[] prefabNuclei = this.prefab.nuclei.ToArray();
// first clone the nuclei without their connections // first clone the nuclei without their connections
@ -99,7 +124,7 @@ public class Cluster : Nucleus {
IReceptor clonedNucleus = clonedNuclei[nucleusIx] as IReceptor; IReceptor clonedNucleus = clonedNuclei[nucleusIx] as IReceptor;
if (prefabReceptor == prefabReceptor.nucleiArray[0]) { if (prefabReceptor == prefabReceptor.nucleiArray[0]) {
// We clone the array only for the first entry // We clone the array only for the first entry
NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length, "array"); NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length);
int arrayIx = 0; int arrayIx = 0;
foreach (Nucleus prefabArrayNucleus in prefabReceptor.nucleiArray) { foreach (Nucleus prefabArrayNucleus in prefabReceptor.nucleiArray) {
int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus); int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus);
@ -129,7 +154,12 @@ public class Cluster : Nucleus {
} }
} }
// Sort the nuclei in a correct evaluation order /// <summary>
/// Sort the nuclei in a correct evaluation order
/// </summary>
/// <param name="nodes"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private List<Nucleus> TopologicalSort(List<Nucleus> nodes) { private List<Nucleus> TopologicalSort(List<Nucleus> nodes) {
Dictionary<Nucleus, int> inDegree = new(); Dictionary<Nucleus, int> inDegree = new();
foreach (Nucleus node in nodes) foreach (Nucleus node in nodes)
@ -508,3 +538,5 @@ public class Cluster : Nucleus {
#endregion Update #endregion Update
} }
}

View File

@ -0,0 +1,140 @@
using System.Collections.Generic;
using UnityEngine;
namespace NanoBrain {
/// <summary>
/// The Unity ScriptableObject to implement re-usable Cluster Prefabs
/// </summary>
[CreateAssetMenu(menuName = "Passer/Cluster")]
public class ClusterPrefab : ScriptableObject {
/// The nuclei in this cluster
[SerializeReference]
public List<Nucleus> nuclei = new();
/// <summary>
/// The output of this cluster
/// </summary>
/// <deprecated>This only returens the first(default) nucleus. Use outputs[0] instead</deprecated>
public virtual Nucleus output => this.nuclei[0] as Nucleus;
/// <summary>
/// The nuclei in this cluster which are meant for receiving signals from outside the cluster
/// </summary>
/// <remark>This is currently the nuclei which do not have any incoming synapse</remark>
public List<Nucleus> _inputs = null;
public virtual List<Nucleus> inputs {
get {
if (this._inputs == null) {
this._inputs = new();
foreach (Nucleus receptor in this.nuclei) {
if (receptor is Nucleus nucleus) {
// inputs have no incoming synapses yet.
if (nucleus.synapses.Count == 0)
this._inputs.Add(nucleus);
}
}
}
return this._inputs;
}
}
/// <summary>
/// The nuclei in this cluster which are meant for sending signals onward
/// </summary>
private List<Nucleus> _outputs = null;
public List<Nucleus> outputs {
get {
if (this._outputs == null)
RefreshOutputs();
return this._outputs;
}
}
/// <summary>
/// Redetermine the outpus in the cluster
/// </summary>
public void RefreshOutputs() {
this._outputs = new();
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus is Neuron neuron && neuron.receivers.Count == 0)
this._outputs.Add(nucleus);
}
}
/// <summary>
/// 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) {
foreach (Nucleus nucleus in this.nuclei) {
if (nucleus.name == nucleusName)
return nucleus;
}
return null;
}
// Call this function to ensure that there is at least one nucleus
// This is an invariant and should be ensured before the nucleus is used
// because output requires it.
public void EnsureInitialization() {
nuclei ??= new List<Nucleus>();
if (nuclei.Count == 0)
new Neuron(this, "Output"); // Every cluster should have at least 1 neuron
}
public void GarbageCollection() {
HashSet<Nucleus> visitedNuclei = new();
foreach (Nucleus output in this.outputs)
MarkNuclei(visitedNuclei, output);
//Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei");
this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false);
}
public void MarkNuclei(HashSet<Nucleus> visitedNuclei, Nucleus nucleus) {
if (nucleus is null)
return;
if (nucleus.parent != null && nucleus.parent.prefab != this)
visitedNuclei.Add(nucleus.parent);
else
visitedNuclei.Add(nucleus);
if (nucleus.synapses != null) {
HashSet<Synapse> visitedSynapses = new();
foreach (Synapse synapse in nucleus.synapses) {
if (synapse != null && synapse.neuron != null) {
visitedSynapses.Add(synapse);
if (synapse.neuron is Nucleus synapse_nucleus)
MarkNuclei(visitedNuclei, synapse_nucleus);
}
}
nucleus.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false);
}
if (nucleus is Neuron neuron && neuron.receivers != null) {
HashSet<Nucleus> visitedReceivers = new();
foreach (Nucleus receiver in neuron.receivers) {
if (receiver != null && receiver != null) {
visitedReceivers.Add(receiver);
visitedNuclei.Add(receiver);
}
}
neuron.receivers.RemoveAll(receiver => visitedReceivers.Contains(receiver) == false);
}
}
public virtual void UpdateNuclei() {
foreach (Nucleus nucleus in this.nuclei)
nucleus.UpdateNuclei();
}
public int GetNucleusIndex(Nucleus receiver) {
int ix = 0;
foreach (Nucleus nucleus in this.nuclei) {
if (receiver == nucleus)
return ix;
ix++;
}
return -1;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 60a957541c24c57e78018c202ebb1d9b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 288088fdc016525a59f83f1c608e514d, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -7,6 +7,8 @@ using static Unity.Mathematics.math;
#endif #endif
using System.Linq; using System.Linq;
namespace NanoBrain {
[Serializable] [Serializable]
public class ClusterReceptor : Cluster, IReceptor { public class ClusterReceptor : Cluster, IReceptor {
public ClusterReceptor(ClusterPrefab prefab, Cluster parent, string name) : base(prefab, parent) { public ClusterReceptor(ClusterPrefab prefab, Cluster parent, string name) : base(prefab, parent) {
@ -271,3 +273,5 @@ public class ClusterReceptor : Cluster, IReceptor {
} }
} }
} }
}

View File

@ -0,0 +1,123 @@
using UnityEngine;
namespace NanoBrain {
/// <summary>
/// A Receptor is a Nucleus which can receive input (called Stimulus) from outside the the cluster/brain
/// </summary>
/// It has the ability to distinguish stimuli from different things using an array of Nuclei
public interface IReceptor {
/// <summary>
/// Get the name of the receptor
/// </summary>
/// <returns>The name of the receptor</returns>
public string GetName();
/// <summary>
/// The array of nuclei used to track multiple things sending stimuli
/// </summary>
/// The size of the array determines the maximum number of things which can be distinguished
public Nucleus[] nucleiArray { get; set; }
/// <summary>
/// Extends the nucleiArray with an additional element
/// </summary>
/// <param name="prefab">A prefab of the nucleus to add?</param>
public void AddReceptorElement(ClusterPrefab prefab);
/// <summary>
/// Removes the last element from the nucleiArray
/// </summary>
public void RemoveReceptorElement();
/// <summary>
/// Add a receiver for this receptor array
/// </summary>
/// <param name="receiverToAdd">The receiving Nucleus</param>
/// <param name="weight">The initial weight to use for the synapses</param>
/// This function will add a synapse to the receiver for each element in the nucleiArray.
public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1);
/// <summary>
/// Process an external stimulus
/// </summary>
/// <param name="inputValue">The value of the stimulus</param>
/// <param name="thingId">The id of the thing causing the stimulus</param>
/// <param name="thingName">The name of the thing causing the stimulus</param>
public void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null);
}
public static class IReceptorHelpers {
/// <summary>
/// Implementation for the NanoBrain::IReceptor::AddReceptorElement which can be used for all implementations of IReceptor
/// </summary>
/// <param name="receptor">The IReceptor which needs to extend its nucleiArray</param>
/// <param name="prefab">A prefab of the nucleus to add?</param>
public static void AddReceptorElement(IReceptor receptor, ClusterPrefab prefab) {
if (receptor.nucleiArray.Length == 0) {
Debug.LogError("Empty perceptoid array, cannot add");
}
int newLength = receptor.nucleiArray.Length + 1;
Nucleus[] newArray = new Nucleus[newLength];
string baseName = receptor.GetName();
int colonPos = baseName.IndexOf(":");
if (colonPos > 0)
baseName = baseName[..colonPos];
for (int i = 0; i < receptor.nucleiArray.Length; i++)
newArray[i] = receptor.nucleiArray[i];
if (receptor.nucleiArray[0] is Nucleus nucleus) {
newArray[newLength - 1] = nucleus.Clone(prefab);
newArray[newLength - 1].name = $"{baseName}: {newLength - 1}";
}
foreach (Nucleus element in receptor.nucleiArray) {
if (element is IReceptor receptorElement) {
receptorElement.nucleiArray = newArray;
}
}
}
/// <summary>
/// Implementation for the NanoBrain::IReceptor::RemoteReceptorElement which can be used for all implementations of IReceptor
/// </summary>
/// <param name="receptor">The IReceptor which needs to shorten its nucleiArray</param>
public static void RemoveReceptorElement(IReceptor receptor) {
int newLength = receptor.nucleiArray.Length - 1;
if (newLength == 0) {
Debug.LogWarning("Perceptoid array cannot be empty");
}
Nucleus[] newArray = new Nucleus[newLength];
for (int i = 0; i < newLength; i++)
newArray[i] = receptor.nucleiArray[i];
// Delete the last perception
if (receptor.nucleiArray[newLength] is Nucleus nucleus)
Neuron.Delete(nucleus);
foreach (Nucleus element in receptor.nucleiArray) {
if (element is IReceptor receptorElement) {
receptorElement.nucleiArray = newArray;
}
}
}
/// <summary>
/// Implementation for the NanoBreain::IRceptor::AddArrayReceiver which can be used for all implementations of IReceptor
/// </summary>
/// <param name="receptor">The IReceptor for which a receiving nuclues needs to be added</param>
/// <param name="receiverToAdd">The nucleus to receive input from the receptor</param>
/// <param name="weight">The initial weight for the synapses</param>
public static void AddArrayReceiver(IReceptor receptor, Nucleus receiverToAdd, float weight = 1) {
foreach (Nucleus element in receptor.nucleiArray) {
if (element is Cluster cluster)
cluster.defaultOutput.AddReceiver(receiverToAdd, weight);
if (element is Neuron neuron)
neuron.AddReceiver(receiverToAdd, weight);
}
}
}
}

View File

@ -0,0 +1,73 @@
using System;
#if UNITY_MATHEMATICS
using Unity.Mathematics;
#endif
namespace NanoBrain {
/// <summary>
/// A MemoryCell stored its value for one update
/// </summary>
/// When the input for a Memory Cell changes, it will output the previous value
[Serializable]
public class MemoryCell : Neuron {
public MemoryCell(ClusterPrefab cluster, string name) : base(cluster, name) { }
public MemoryCell(Cluster parent, string name) : base(parent, name) { }
public bool staticMemory = false;
public override bool isSleeping {
get {
if (staticMemory)
return false;
return base.isSleeping;
}
}
public override Nucleus ShallowCloneTo(Cluster newParent) {
MemoryCell clone = new(newParent, this.name);
CloneFields(clone);
clone.staticMemory = this.staticMemory;
return clone;
}
#region State
private bool initialized = false;
#if UNITY_MATHEMATICS
private float3 _memorizedValue;
#else
private UnityEngine.Vector3 _memorizedValue;
#endif
public override void UpdateStateIsolated() {
// A memorycell does not have an activation function
var result = Combinator();
if (initialized)
// Output the previous, memorized value
this.outputValue = this._memorizedValue;
else {
// The first time, the result is directly set in output
this.outputValue = result;
this.initialized = true;
}
// Store the result for the next time
this._memorizedValue = result;
}
public override void UpdateNuclei() {
if (staticMemory)
// Static memory does not get stale or go to sleep
return;
base.UpdateNuclei();
}
#endregion State
}
}

View File

@ -0,0 +1,51 @@
using System;
using UnityEngine;
namespace NanoBrain {
/// <summary>
/// The NanoBrain Unity Componnent
/// </summary>
/// This implements the top-level NanoBrain Cluster
public class NanoBrain : MonoBehaviour {
/// <summary>
/// The Cluster prefab from which the cluster is created
/// </summary>
public ClusterPrefab defaultBrain;
[NonSerialized]
private Cluster brainInstance;
/// <summary>
/// The cluster isntance
/// </summary>
public Cluster brain {
get {
if (brainInstance == null && defaultBrain != null) {
brainInstance = new Cluster(defaultBrain) {
name = defaultBrain.name + " (Instance)"
};
}
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) {
Nucleus 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}");
}
}
}
}
}
}

476
Runtime/Scripts/Neuron.cs Normal file
View File

@ -0,0 +1,476 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
#if UNITY_MATHEMATICS
using Unity.Mathematics;
using static Unity.Mathematics.math;
#endif
namespace NanoBrain {
/// <summary>
/// A neuron is a basic Nucleus
/// </summary>
[Serializable]
public class Neuron : Nucleus {
/// <summary>
/// Create a new Neuron in a Cluster instance
/// </summary>
/// <param name="parent">The parent cluster in which the new Neuron should be created</param>
/// <param name="name">The name of the new Neuron</param>
public Neuron(Cluster parent, string name) {
this.parent = parent;
this.name = name;
this.parent?.clusterNuclei.Add(this);
}
/// <summary>
/// Create a new Neuron in a Cluster Prefab
/// </summary>
/// <param name="prefab">The Cluster Preafb in which the new Neuron should be created</param>
/// <param name="name">The name of the new Neuron</param>
public Neuron(ClusterPrefab prefab, string name) {
this.clusterPrefab = prefab;
this.name = name;
if (this.clusterPrefab != null)
this.clusterPrefab.nuclei.Add(this);
else
Debug.LogError("No prefab when adding neuron to prefab");
}
#region Serialization
/// <summary>
/// The type of combinators
/// </summary>
/// A combinator combines the weighted values of the synapses to a single value
public enum CombinatorType {
/// Add the weighted values together
Sum,
/// Multiply the weighted values
Product,
/// Take the maximum of all the weighted values
Max,
}
/// <summary>
/// The type of combinator used for this Neuron
/// </summary>
public CombinatorType combinator = CombinatorType.Sum;
/// <summary>
/// The type of
/// </summary>
public enum CurvePresets {
Linear,
Power,
Sqrt,
Reciprocal,
Custom
}
[SerializeField]
public CurvePresets _curvePreset;
public CurvePresets curvePreset {
get { return _curvePreset; }
set {
_curvePreset = value;
this.curve = GenerateCurve();
}
}
public AnimationCurve curve;
public float curveMax = 1.0f;
public AnimationCurve GenerateCurve() {
switch (this.curvePreset) {
case CurvePresets.Linear:
this.curveMax = 1;
return Presets.Linear(1);
case CurvePresets.Power:
this.curveMax = 1;
return Presets.Power(2.0f, 1);
case CurvePresets.Sqrt:
this.curveMax = 1;
return Presets.Power(0.5f, 1);
case CurvePresets.Reciprocal:
this.curveMax = 1 / 0.01f * 1;
return Presets.Reciprocal(1);
default:
this.curveMax = 1;
return this.curve;
}
}
public static class Presets {
private const int samples = 32;
public static AnimationCurve Linear(float weight) {
return AnimationCurve.Linear(0f, 0f, 1000f, weight * 1000);
}
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;
}
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;
}
}
#endregion Serialization
#if UNITY_MATHEMATICS
protected float3 _outputValue;
public virtual float3 outputValue {
get { return _outputValue; }
set {
_outputValue = value;
if (this.isFiring)
WhenFiring?.Invoke();
}
}
public float outputMagnitude => length(_outputValue);
public float outputSqrMagnitude => lengthsq(_outputValue);
#else
protected Vector3 _outputValue;
public virtual Vector3 outputValue {
get { return _outputValue; }
set {
_outputValue = value;
if (this.isFiring)
WhenFiring?.Invoke();
}
}
public float outputMagnitude => _outputValue.magnitude;
public float outputSqrMagnitude => _outputValue.sqrMagnitude;
#endif
public bool isFiring => this.outputMagnitude > 0.5f;
public Action WhenFiring;
public virtual bool isSleeping => this.outputMagnitude == 0;
[NonSerialized]
public int stale = 1000;
public readonly int staleValueForSleep = 20;
/// \copydoc NanoBrain::Nucleus::ShallowCloneTo
public override Nucleus ShallowCloneTo(Cluster newParent) {
Neuron clone = new(newParent, this.name);
CloneFields(clone);
return clone;
}
/// \copydoc NanoBrain::Nucleus::Clone
public override Nucleus Clone(ClusterPrefab prefab) {
Neuron clone = new(prefab, this.name);
CloneFields(clone);
foreach (Synapse synapse in this.synapses) {
Synapse clonedSynapse = clone.AddSynapse(synapse.neuron);
clonedSynapse.weight = synapse.weight;
}
foreach (Nucleus receiver in this.receivers) {
clone.AddReceiver(receiver);
}
return clone;
}
protected virtual void CloneFields(Neuron clone) {
clone.clusterPrefab = this.clusterPrefab;
clone.bias = this.bias;
clone.combinator = this.combinator;
clone.curve = this.curve;
clone.curvePreset = this.curvePreset;
clone.curveMax = this.curveMax;
}
public static void Delete(Nucleus nucleus) {
foreach (Synapse synapse in nucleus.synapses) {
if (synapse.neuron is Neuron synapse_nucleus) {
if (synapse_nucleus.receivers.Count > 1) {
// there is another nucleus feeding into this input nucleus
synapse_nucleus.receivers.RemoveAll(r => r == nucleus);
}
else {
// No other links, delete it.
Neuron.Delete(synapse_nucleus);
}
}
}
if (nucleus is Neuron neuron) {
foreach (Nucleus receiver in neuron.receivers) {
if (receiver != null && receiver.synapses != null)
receiver.synapses.RemoveAll(s => s.neuron == nucleus);
}
}
else if (nucleus is Cluster cluster) {
// remove all receivers for this cluster
foreach (Neuron output in cluster.outputs) {
foreach (Nucleus receiver in output.receivers) {
receiver.synapses.RemoveAll(s => s.neuron == output);
}
}
}
if (nucleus.clusterPrefab != null) {
nucleus.clusterPrefab.nuclei.RemoveAll(n => n == nucleus);
nucleus.clusterPrefab.RefreshOutputs();
nucleus.clusterPrefab.GarbageCollection();
}
}
public override void UpdateStateIsolated() {
var result = Combinator();
this.outputValue = Activator(result);
}
#region Combinator
#if UNITY_MATHEMATICS
protected Func<float3> Combinator => combinator switch {
CombinatorType.Sum => CombinatorSum,
CombinatorType.Product => CombinatorProduct,
CombinatorType.Max => CombinatorMax,
_ => CombinatorSum
};
public float3 CombinatorSum() {
float3 sum = this.bias;
foreach (Synapse synapse in this.synapses)
sum += synapse.weight * synapse.neuron.outputValue;
return sum;
}
public float3 CombinatorProduct() {
float3 product = this.bias;
foreach (Synapse synapse in this.synapses) {
product *= synapse.weight * synapse.neuron.outputValue;
}
return product;
}
public float3 CombinatorMax() {
float3 max = this.bias;
float maxLength = length(max);
//Applying the weight factors
foreach (Synapse synapse in this.synapses) {
float3 input = synapse.weight * synapse.neuron.outputValue;
float inputLength = length(input);
if (inputLength > maxLength) {
max = input;
maxLength = inputLength;
}
}
return max;
}
#else
protected Func<Vector3> Combinator => combinator switch {
CombinatorType.Sum => CombinatorSum,
CombinatorType.Product => CombinatorProduct,
CombinatorType.Max => CombinatorMax,
_ => CombinatorSum
};
public Vector3 CombinatorSum() {
Vector3 sum = this.bias;
foreach (Synapse synapse in this.synapses)
sum += synapse.weight * synapse.neuron.outputValue;
return sum;
}
public Vector3 CombinatorProduct() {
Vector3 product = this.bias;
foreach (Synapse synapse in this.synapses) {
//product *= synapse.weight * synapse.neuron.outputValue;
product = Vector3.Scale(product, synapse.weight * synapse.neuron.outputValue);
}
return product;
}
public Vector3 CombinatorMax() {
Vector3 max = this.bias;
float maxLength = max.magnitude;
//Applying the weight factors
foreach (Synapse synapse in this.synapses) {
Vector3 input = synapse.weight * synapse.neuron.outputValue;
float inputLength = input.magnitude;
if (inputLength > maxLength) {
max = input;
maxLength = inputLength;
}
}
return max;
}
#endif
#endregion Combinator
#region Activator
#if UNITY_MATHEMATICS
public Func<float3, float3> Activator => this.curvePreset switch {
CurvePresets.Linear => ActivatorLinear,
CurvePresets.Sqrt => ActivatorSqrt,
CurvePresets.Power => ActivatorPower,
CurvePresets.Reciprocal => ActivatorReciprocal,
_ => ActivatorCustom
};
protected float3 ActivatorLinear(float3 input) {
return input;
}
protected float3 ActivatorSqrt(float3 input) {
float3 result = normalize(input) * System.MathF.Sqrt(length(input));
return result;
}
protected float3 ActivatorPower(float3 input) {
float3 result = normalize(input) * System.MathF.Pow(length(input), 2);
return result;
}
protected float3 ActivatorReciprocal(float3 input) {
float magnitude = length(input);
if (magnitude == 0)
return new float3(0, 0, 0);
float3 result = normalize(input) * (1 / magnitude);
return result;
}
protected float3 ActivatorCustom(float3 input) {
float activatedValue = this.curve.Evaluate(length(input));
float3 result = normalize(input) * activatedValue;
return result;
}
#else
public Func<Vector3, Vector3> Activator => this.curvePreset switch {
CurvePresets.Linear => ActivatorLinear,
CurvePresets.Sqrt => ActivatorSqrt,
CurvePresets.Power => ActivatorPower,
CurvePresets.Reciprocal => ActivatorReciprocal,
_ => ActivatorCustom
};
protected Vector3 ActivatorLinear(Vector3 input) {
return input;
}
protected Vector3 ActivatorSqrt(Vector3 input) {
Vector3 result = input.normalized * System.MathF.Sqrt(input.magnitude);
return result;
}
protected Vector3 ActivatorPower(Vector3 input) {
Vector3 result = input.normalized * System.MathF.Pow(input.magnitude, 2);
return result;
}
protected Vector3 ActivatorReciprocal(Vector3 input) {
float magnitude = input.magnitude;
if (magnitude == 0)
return new Vector3(0, 0, 0);
Vector3 result = input.normalized * (1 / magnitude);
return result;
}
protected Vector3 ActivatorCustom(Vector3 input) {
float activatedValue = this.curve.Evaluate(input.magnitude);
Vector3 result = input.normalized * activatedValue;
return result;
}
#endif
#endregion Activator
#region Receivers
[SerializeReference]
private List<Nucleus> _receivers = new();
public virtual List<Nucleus> receivers {
get { return _receivers; }
set { _receivers = value; }
}
public virtual void AddReceiver(Nucleus receiverToAdd, float weight = 1) {
this._receivers.Add(receiverToAdd);
receiverToAdd.AddSynapse(this, weight);
}
public virtual void RemoveReceiver(Nucleus receiverToRemove) {
if (this is IReceptor receptor) {
foreach (Nucleus element in receptor.nucleiArray) {
if (element is Neuron neuron) {
neuron._receivers.RemoveAll(receiver => receiver == receiverToRemove);
receiverToRemove.synapses.RemoveAll(synapse => synapse.neuron == neuron);
}
}
}
else {
this._receivers.RemoveAll(receiver => receiver == receiverToRemove);
receiverToRemove.synapses.RemoveAll(synapse => synapse.neuron == this);
}
}
#endregion Receivers
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
if (this.parent is ClusterReceptor clusterReceptor)
clusterReceptor.ProcessStimulus(this, inputValue, thingId, thingName);
else
ProcessStimulusDirect(inputValue, thingId, thingName);
}
public void ProcessStimulusDirect(Vector3 inputValue, int thingId = 0, string thingName = null) {
this.stale = 0;
this.bias = inputValue;
this.parent.UpdateFromNucleus(this);
}
}
}

147
Runtime/Scripts/Nucleus.cs Normal file
View File

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// The Nanobrain namespace
/// </summary>
namespace NanoBrain {
/// <summary>
/// A Nucleus is a basic element in a brain cluster
/// </summary>
[Serializable]
public abstract class Nucleus {
/// <summary>
/// The name of the Nucleus
/// </summary>
public string name;
/// <summary>
/// The cluster prefab in which the nucleus is located
/// </summary>
[SerializeReference]
public ClusterPrefab clusterPrefab;
/// <summary>
/// The cluster instance in which the nucleus is located
/// </summary>
[SerializeReference]
public Cluster parent;
/// <summary>
/// Toggle for printing debugging trace data
/// </summary>
public bool trace = false;
/// <summary>
/// Function to make a partial clone of this nucleus
/// </summary>
/// <param name="parent">The cluster in which the cloned nucleus should be placed</param>
/// <returns></returns>
public abstract Nucleus ShallowCloneTo(Cluster parent);
/// <summary>
/// Function to clone a nucleus to a Cluster prefab
/// </summary>
/// <param name="prefab"></param>
/// <returns></returns>
public abstract Nucleus Clone(ClusterPrefab prefab);
/// <summary>
/// The types of Nucleus
/// </summary>
public enum Type {
None,
Neuron,
MemoryCell,
Cluster,
Receptor,
ClusterReceptor,
}
#region Synapses
/// <summary>
/// The bias of the nucleus
/// </summary>
/// The bias which a value which is always added to the combined value of the nucleus
/// It does not have a synapse and therefore no weight of source nucleus
public Vector3 bias = Vector3.zero;
[SerializeField]
private List<Synapse> _synapses = new();
/// <summary>
/// The synapses of the nucleus
/// </summary>
public List<Synapse> synapses => _synapses;
/// <summary>
/// Add a new synapse to this nuclues
/// </summary>
/// <param name="sendingNucleus">The nucleus from which the signals may originate</param>
/// <param name="weight">The weight applied to the input. Default value = 1</param>
/// <returns>The created Synapse</returns>
/// This will add a new input to this nucleus with the given weight.
public Synapse AddSynapse(Neuron sendingNucleus, float weight = 1) {
Synapse synapse = new(sendingNucleus, weight);
this.synapses.Add(synapse);
return synapse;
}
/// <summary>
/// Find a synapse
/// </summary>
/// <param name="sender">The sender of the input to the Synapse</param>
/// <returns>The found Synapse or null when the sender has no synapse to this nucleus.</returns>
public Synapse GetSynapse(Nucleus sender) {
foreach (Synapse synapse in this.synapses)
if (synapse.neuron == sender)
return synapse;
return null;
}
/// <summary>
/// Remove a synapse from a Nucleus
/// </summary>
/// <param name="sendingNucleus">Remote the synapse connecting to this Nucleus</param>
public void RemoveSynapse(Nucleus sendingNucleus) {
this.synapses.RemoveAll(synapse => synapse.neuron == sendingNucleus);
}
#endregion Synapses
#region Update
/// <summary>
/// Update the state without updating other Nuclei
/// </summary>
public abstract void UpdateStateIsolated();
/// <summary>
/// Update the state and recursively all Nuclei receiving data from this Nucleus
/// </summary>
public virtual void UpdateNuclei() {
}
/// <summary>
/// Set the bias, recalculate the output and update all Nuclei receiving from this Nucleus
/// </summary>
/// <param name="inputValue"></param>
public virtual void SetBias(Vector3 inputValue) {
this.bias = inputValue;
this.parent.UpdateFromNucleus(this);
}
/// <summary>
/// Process an external stimulus
/// </summary>
/// <param name="inputValue">The value of the stimulus</param>
/// <param name="thingId">The id of the thing causing the stimulus</param>
/// <param name="thingName">The name of the thing causing the stimulus</param>
public virtual void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = "") {
}
#endregion Update
}
}

View File

@ -0,0 +1,197 @@
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_MATHEMATICS
using Unity.Mathematics;
using static Unity.Mathematics.math;
#endif
namespace NanoBrain {
/// <summary>
/// Class to manage an array of nuclei for an IReceptor
/// </summary>
/// Would love to get rid of this class.
[System.Serializable]
public class NucleusArray {
/// <summary>
/// The nuclei in this array
/// </summary>
[SerializeReference]
private Nucleus[] _nuclei;
public Nucleus[] nuclei {
get {
return _nuclei;
}
set {
_nuclei = value;
}
}
/// <summary>
/// Create a new NucleusArray with the given nucleus
/// </summary>
/// <param name="nucleus">The Nucleus to put in the NucleusArray</param>
/// This results in an nucleus array of size 1
public NucleusArray(Nucleus nucleus) {
this._nuclei = new Nucleus[1];
this._nuclei[0] = nucleus;
}
/// <summary>
/// Create a new NucleusArray of the given size
/// </summary>
/// <param name="size">The size of the nucluesArray</param>
public NucleusArray(int size) {
this._nuclei = new Nucleus[size];
}
// public void AddNucleus(ClusterPrefab prefab) {
// if (this._nuclei.Length == 0) {
// Debug.LogError("Empty perceptoid array, cannot add");
// return;
// }
// int newLength = this._nuclei.Length + 1;
// Nucleus[] newArray = new Nucleus[newLength];
// for (int i = 0; i < this._nuclei.Length; i++)
// newArray[i] = this._nuclei[i];
// if (this._nuclei[0] is Nucleus nucleus) {
// newArray[newLength - 1] = nucleus.Clone(prefab);
// newArray[newLength - 1].name += $": {newLength - 1}";
// }
// this._nuclei = newArray;
// }
// public void RemoveNucleus() {
// int newLength = this._nuclei.Length - 1;
// if (newLength == 0) {
// Debug.LogWarning("Perceptoid array cannot be empty");
// return;
// }
// Nucleus[] newPerceptei = new Nucleus[newLength];
// for (int i = 0; i < newLength; i++)
// newPerceptei[i] = this._nuclei[i];
// // Delete the last perception
// if (this._nuclei[newLength] is Nucleus nucleus)
// Neuron.Delete(nucleus); //this._nuclei[newLength]);
// this._nuclei = newPerceptei;
// }
public Dictionary<int, Nucleus> thingReceivers = new();
#if UNITY_MATHEMATICS
private Nucleus FindReceiver(int thingId, float3 inputValue) {
float inputMagnitude = length(inputValue);
return FindReceiverMagnitude(thingId, inputMagnitude);
}
#else
private Nucleus FindReceiver(int thingId, Vector3 inputValue) {
float inputMagnitude = inputValue.magnitude;
return FindReceiverMagnitude(thingId, inputMagnitude);
}
#endif
private Nucleus FindReceiverMagnitude(int thingId, float inputMagnitude) {
Neuron selectedReceiver = null;
float selectedMagnitude = 0;
foreach (Nucleus nucleusReceiver in this._nuclei) {
if (nucleusReceiver is not Neuron receiver)
continue;
if (thingReceivers.ContainsValue(receiver) == false) {
// We found an unusued receiver
thingReceivers.Add(thingId, receiver);
return receiver;
}
else if (receiver.isSleeping) {
// A sleeping receiver is not active and can therefore always be used
thingReceivers.Add(thingId, receiver);
return receiver;
}
else if (selectedReceiver == null) {
// If we haven't found a receiver yet, just start by taking the first
selectedReceiver = receiver;
selectedMagnitude = selectedReceiver.outputMagnitude;
}
// Look for the receiver with the lowest magnitude
else {
float magnitude = receiver.outputMagnitude;
if (magnitude < inputMagnitude && receiver.outputMagnitude < selectedMagnitude) {
selectedReceiver = receiver;
selectedMagnitude = selectedReceiver.outputMagnitude;
}
}
}
if (selectedReceiver != null) {
// Replace the receiver
// Find the thingId current associated with the receiver
int keyToRemove = thingReceivers.FirstOrDefault(r => r.Value.Equals(selectedReceiver)).Key;
if (keyToRemove != 0 || thingReceivers.ContainsKey(keyToRemove))
thingReceivers.Remove(keyToRemove);
// And add the new association
thingReceivers.Add(thingId, selectedReceiver);
}
return selectedReceiver;
}
/// <summary>
/// Process an external stimulus
/// </summary>
/// <param name="inputValue">The value of the stimulus</param>
/// <param name="thingId">The id of the thing causing the stimulus</param>
/// <param name="thingName">The name of the thing causing the stimulus</param>
public virtual void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) {
CleanupReceivers();
if (this._nuclei[0] is Neuron neuron)
inputValue = neuron.Activator(inputValue);
if (!thingReceivers.TryGetValue(thingId, out Nucleus selectedReceiver)) {
// No existing nucleus for this thing
selectedReceiver = FindReceiver(thingId, inputValue);
}
if (selectedReceiver == null)
return;
if (thingName != null) {
string baseName = selectedReceiver.name;
int colonPos = selectedReceiver.name.IndexOf(":");
if (colonPos > 0)
baseName = selectedReceiver.name[..colonPos];
selectedReceiver.name = baseName + ": " + thingName;
}
if (selectedReceiver is Neuron selectedNucleus)
selectedNucleus.ProcessStimulusDirect(inputValue);
}
/// <summary>
/// Remove a thing-receiver connection when the nucleus is inactive
/// </summary>
private void CleanupReceivers() {
List<int> receiversToRemove = new();
foreach (KeyValuePair<int, Nucleus> item in thingReceivers) {
if (item.Value != null && item.Value is Neuron neuron && neuron.isSleeping)
receiversToRemove.Add(item.Key);
}
foreach (int thingId in receiversToRemove) {
Nucleus selectedReceiver = thingReceivers[thingId];
thingReceivers.Remove(thingId);
int colonPos = selectedReceiver.name.IndexOf(":");
if (colonPos > 0)
selectedReceiver.name = selectedReceiver.name[..colonPos];
}
}
}
}

113
Runtime/Scripts/Receptor.cs Normal file
View File

@ -0,0 +1,113 @@
using UnityEngine;
#if UNITY_MATHEMATICS
using Unity.Mathematics;
using static Unity.Mathematics.math;
#endif
namespace NanoBrain {
/// <summary>
/// Basic IReceptor to receive external input
/// </summary>
[System.Serializable]
public class Receptor : Neuron, IReceptor {
/// <summary>
/// Create a new Receptor in a Cluster instance
/// </summary>
/// <param name="parent">The Cluster in which the Receptor is created</param>
/// <param name="name">The name of the new Receptor</param>
public Receptor(Cluster parent, string name) : base(parent, name) {
this.array = new NucleusArray(this);
if (this.name.IndexOf(":") < 0)
this.name += ": 0";
}
/// <summary>
/// Create a new Receptor in a Cluster Prefab
/// </summary>
/// <param name="prefab">The Cluster Prefab in which the Receptor is created</param>
/// <param name="name">The name of the new Receptor</param>
public Receptor(ClusterPrefab prefab, string name) : base(prefab, name) {
this.array = new NucleusArray(this);
}
public string GetName() {
return this.name;
}
/// \copydoc NanoBrain::Neuron::ShallowCloneTo
public override Nucleus ShallowCloneTo(Cluster parent) {
Receptor clone = new(parent, name) {
};
CloneFields(clone);
return clone;
}
/// \copydoc NanoBrain::Neuron::Clone
public override Nucleus Clone(ClusterPrefab prefab) {
Receptor clone = new(prefab, name) {
array = this._array
};
CloneFields(clone);
// Adding receivers will also add synapses to the receivers
foreach (Nucleus receiver in this.receivers.ToArray())
clone.AddReceiver(receiver);
return clone;
}
[SerializeReference]
private NucleusArray _array;
public NucleusArray array {
set { _array = value; }
}
public Nucleus[] nucleiArray {
get { return _array.nuclei; }
set { _array.nuclei = value; }
}
public void AddReceptorElement(ClusterPrefab prefab) {
IReceptorHelpers.AddReceptorElement(this, prefab);
}
public void RemoveReceptorElement() {
IReceptorHelpers.RemoveReceptorElement(this);
}
public virtual void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) {
IReceptorHelpers.AddArrayReceiver(this, receiverToAdd, weight);
}
public override void UpdateStateIsolated() {
this.outputValue = this.bias;
}
#if UNITY_MATHEMATICS
public override void UpdateNuclei() {
this.stale++;
if (this.stale > staleValueForSleep && lengthsq(this.bias) > 0) {
this.bias = new float3(0, 0, 0);
this.parent.UpdateFromNucleus(this);
}
}
#else
public override void UpdateNuclei() {
this.stale++;
if (this.stale > staleValueForSleep && this.bias.sqrMagnitude > 0) {
this.bias = new Vector3(0, 0, 0);
this.parent.UpdateFromNucleus(this);
}
}
#endif
public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) {
this._array ??= new NucleusArray(this.parent);
this._array.ProcessStimulus(thingId, inputValue, thingName);
}
}
}

View File

@ -0,0 +1,33 @@
using System;
using UnityEngine;
namespace NanoBrain {
/// <summary>
/// A Synapse connects the ouput of a Neuron to another Neuron
/// </summary>
[Serializable]
public class Synapse {
/// <summary>
/// The neuron from which input is received
/// </summary>
[SerializeReference]
public Neuron neuron;
/// <summary>
/// The weight value to apply to the Neuron input
/// </summary>
public float weight;
/// <summary>
/// Create a new Synapse
/// </summary>
/// <param name="nucleus">The neuron from which input is received</param>
/// <param name="weight">The weight value to apply to the Neuron input</param>
public Synapse(Neuron nucleus, float weight = 1.0f) {
this.neuron = nucleus;
this.weight = weight;
}
}
}

View File

@ -1,8 +0,0 @@
using UnityEngine;
#if UNITY_MATHEMATICS
using Unity.Mathematics;
using static Unity.Mathematics.math;
//#endif
#endif

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 76e9f0d4925b7ac278baa9932582ed10

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: bfd7dadd61c0891d8a94db0196e61a8a guid: 9499e0c167c60f8eba614e29833d1bf3
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 2c1e3956a0b70ae6b8d09fb467b73621 guid: c95a1d65d635791c3b05c8fa281b4b94
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -1,487 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 10
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 0
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 13
m_BakeOnSceneLoad: 0
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 2
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 256
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 1
m_PVRDenoiserTypeDirect: 1
m_PVRDenoiserTypeIndirect: 1
m_PVRDenoiserTypeAO: 1
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 1
m_PVRFilteringGaussRadiusAO: 1
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 3
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
buildHeightMesh: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1001 &551770709
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalPosition.x
value: 0.71
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093763, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_Name
value: Boid2
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
--- !u!1 &968074744
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 968074747}
- component: {fileID: 968074746}
- component: {fileID: 968074745}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &968074745
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 968074744}
m_Enabled: 1
--- !u!20 &968074746
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 968074744}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_Iso: 200
m_ShutterSpeed: 0.005
m_Aperture: 16
m_FocusDistance: 10
m_FocalLength: 50
m_BladeCount: 5
m_Curvature: {x: 2, y: 11}
m_BarrelClipping: 0.25
m_Anamorphism: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &968074747
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 968074744}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1342149740
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1342149742}
- component: {fileID: 1342149741}
m_Layer: 0
m_Name: SwamControl
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1342149741
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1342149740}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0464906885ae3494f8fd0314719fb2db, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::SwarmControl
speed: 0.5
inertia: 0.1
alignmentForce: 0
cohesionForce: 1
separationForce: 1
avoidanceForce: 5
separationDistance: 0.5
perceptionDistance: 1
spaceSize: {x: 10, y: 10, z: 10}
boundaryWidth: {x: 1, y: 1, z: 1}
--- !u!4 &1342149742
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1342149740}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -1.00377, y: -1.02283, z: 0.72231}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &2011285159
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2011285161}
- component: {fileID: 2011285160}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!108 &2011285160
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2011285159}
m_Enabled: 1
serializedVersion: 12
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize2D: {x: 0.5, y: 0.5}
m_Shadows:
m_Type: 2
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!4 &2011285161
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2011285159}
serializedVersion: 2
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
--- !u!1001 &4573752827112804207
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7761516481062093763, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
propertyPath: m_Name
value: Boid1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- {fileID: 968074747}
- {fileID: 2011285161}
- {fileID: 4573752827112804207}
- {fileID: 551770709}
- {fileID: 1342149742}

View File

@ -1,365 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 10
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 0
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 13
m_BakeOnSceneLoad: 0
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 2
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 256
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 1
m_PVRDenoiserTypeDirect: 1
m_PVRDenoiserTypeIndirect: 1
m_PVRDenoiserTypeAO: 1
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 1
m_PVRFilteringGaussRadiusAO: 1
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 3
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
buildHeightMesh: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &388118692
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 388118694}
- component: {fileID: 388118693}
m_Layer: 0
m_Name: GameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &388118693
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 388118692}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9051408e82b511584998506096af4bf0, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::SelectorBrain
defaultBrain: {fileID: 11400000, guid: d5b3a22d9bb7d13aeb3174077125967b, type: 2}
input1: {x: 0, y: 0, z: 1}
input2: {x: 0, y: -2, z: 0}
output: {x: 0, y: 0, z: 0}
--- !u!4 &388118694
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 388118692}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -2.01476, y: -0, z: 0.65362}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &968074744
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 968074747}
- component: {fileID: 968074746}
- component: {fileID: 968074745}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &968074745
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 968074744}
m_Enabled: 1
--- !u!20 &968074746
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 968074744}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_Iso: 200
m_ShutterSpeed: 0.005
m_Aperture: 16
m_FocusDistance: 10
m_FocalLength: 50
m_BladeCount: 5
m_Curvature: {x: 2, y: 11}
m_BarrelClipping: 0.25
m_Anamorphism: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &968074747
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 968074744}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &2011285159
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2011285161}
- component: {fileID: 2011285160}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!108 &2011285160
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2011285159}
m_Enabled: 1
serializedVersion: 12
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize2D: {x: 0.5, y: 0.5}
m_Shadows:
m_Type: 2
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!4 &2011285161
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2011285159}
serializedVersion: 2
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- {fileID: 968074747}
- {fileID: 2011285161}
- {fileID: 388118694}

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 1070383882ed0f5379a3b34e8ccb1f75
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View File

@ -1,117 +0,0 @@
fileFormatVersion: 2
guid: cad48149d984d2eddae5808eb1517cb5
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@ -1,117 +0,0 @@
fileFormatVersion: 2
guid: 2e644ed036e8939bf94586314a4f4607
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,15 +0,0 @@
using System;
using UnityEngine;
[Serializable]
public class Synapse {
[SerializeReference]
public Neuron neuron;
public float weight;
public Synapse(Neuron nucleus, float weight = 1.0f) {
this.neuron = nucleus;
this.weight = weight;
}
}

View File

@ -1,5 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 4f343147e37db9eeda3e98058c553c92 guid: b5f21cb978d017236a19dc775f0b1267
folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData: