Reorganizing the package and added documentation
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
doxygen
|
||||||
116
ClusterPrefab.cs
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 60a957541c24c57e78018c202ebb1d9b
|
|
||||||
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
@ -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
|
||||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
@ -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
|
||||||
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
73
IReceptor.cs
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
31
NanoBrain.cs
@ -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
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
72
Nucleus.cs
@ -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
|
|
||||||
|
|
||||||
}
|
|
||||||
208
NucleusArray.cs
@ -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];
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
94
Receptor.cs
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 363b69b84de0e4b729794c10e7c40ab5
|
guid: cfd403fd558edec539ab9d0a1bed0c72
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
140
Runtime/Scripts/ClusterPrefab.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
Runtime/Scripts/ClusterPrefab.cs.meta
Normal 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:
|
||||||
@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
123
Runtime/Scripts/IReceptor.cs
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
73
Runtime/Scripts/MemoryCell.cs
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
51
Runtime/Scripts/NanoBrain.cs
Normal 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
@ -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
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
197
Runtime/Scripts/NucleusArray.cs
Normal 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
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
33
Runtime/Scripts/Synapse.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,8 +0,0 @@
|
|||||||
using UnityEngine;
|
|
||||||
|
|
||||||
#if UNITY_MATHEMATICS
|
|
||||||
using Unity.Mathematics;
|
|
||||||
using static Unity.Mathematics.math;
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 76e9f0d4925b7ac278baa9932582ed10
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: bfd7dadd61c0891d8a94db0196e61a8a
|
guid: 9499e0c167c60f8eba614e29833d1bf3
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 2c1e3956a0b70ae6b8d09fb467b73621
|
guid: c95a1d65d635791c3b05c8fa281b4b94
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
@ -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}
|
|
||||||
@ -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}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 1070383882ed0f5379a3b34e8ccb1f75
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
Before Width: | Height: | Size: 62 KiB |
@ -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:
|
|
||||||
|
Before Width: | Height: | Size: 38 KiB |
@ -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:
|
|
||||||
15
Synapse.cs
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 4f343147e37db9eeda3e98058c553c92
|
guid: b5f21cb978d017236a19dc775f0b1267
|
||||||
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
userData:
|
userData:
|
||||||