304 lines
9.2 KiB
C#
304 lines
9.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEditor;
|
|
using LinearAlgebra;
|
|
|
|
[System.Serializable]
|
|
public class Nucleus {
|
|
|
|
public int id; // hash code
|
|
|
|
[SerializeField]
|
|
protected string _name;
|
|
public virtual string name {
|
|
get => _name;
|
|
set => _name = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
public List<Synapse> synapses = new();
|
|
[SerializeField]
|
|
public List<Receiver> receivers = new();
|
|
|
|
#region Serialization
|
|
|
|
[SerializeField]
|
|
protected string nucleusType;
|
|
|
|
public virtual void Rebuild(NanoBrain brain) {
|
|
if (this.synapses != null) {
|
|
foreach (Synapse synapse in synapses)
|
|
synapse.Rebuild(brain);
|
|
}
|
|
if (this.receivers == null)
|
|
this.receivers = new();
|
|
else {
|
|
foreach (Receiver receiver in receivers.ToArray()) {
|
|
if (receiver.Rebuild(brain) == false) {
|
|
Debug.Log("Rebuilding failed, removing receiver.");
|
|
receivers.Remove(receiver);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static Nucleus RebuildType(NanoBrain brain, Nucleus nucleus) {
|
|
if (string.IsNullOrEmpty(nucleus.nucleusType) == false) {
|
|
Type nucleusType = Type.GetType(nucleus.nucleusType);
|
|
if (nucleusType != null) {
|
|
object[] args = new object[] { brain, nucleus.name };
|
|
Nucleus rebuiltNucleus = (Nucleus)Activator.CreateInstance(nucleusType, args);
|
|
rebuiltNucleus.Deserialize(nucleus);
|
|
return rebuiltNucleus;
|
|
}
|
|
}
|
|
return nucleus;
|
|
}
|
|
|
|
public virtual void Deserialize(Nucleus nucleus) { }
|
|
|
|
#endregion Serialization
|
|
|
|
#region Runtime state (not serialized)
|
|
|
|
public NanoBrain brain { get; set; }
|
|
|
|
private Vector3 _outputValue;
|
|
public Vector3 outputValue
|
|
{
|
|
get { return _outputValue; }
|
|
set {
|
|
this.stale = 0;
|
|
this.isSleeping = false;
|
|
_outputValue = value;
|
|
}
|
|
}
|
|
|
|
[System.NonSerialized]
|
|
private int stale = 1000;
|
|
|
|
public bool isSleeping = false;
|
|
public void IncreaseAge() {
|
|
this.stale++;
|
|
this.isSleeping = this.stale > 2;
|
|
if (isSleeping)
|
|
_outputValue = Vector3.zero;
|
|
}
|
|
[System.NonSerialized]
|
|
public int layerIx;
|
|
|
|
#endregion Runtime state
|
|
|
|
public Nucleus(string name) {
|
|
this._name = name;
|
|
this.id = this.GetHashCode();
|
|
}
|
|
|
|
public virtual void AddReceiver(Nucleus receiver) {
|
|
this.receivers.Add(new Receiver(receiver));
|
|
receiver.SetWeight(this, 1.0f);
|
|
}
|
|
|
|
public void RemoveReceiver(Nucleus receiverNucleus) {
|
|
this.receivers.RemoveAll(receiver => receiver.nucleus == receiverNucleus);
|
|
receiverNucleus.synapses.RemoveAll(synapse => synapse.nucleus == this);
|
|
}
|
|
|
|
public static void Delete(Nucleus nucleus) {
|
|
foreach (Synapse synapse in nucleus.synapses) {
|
|
if (synapse.nucleus.receivers.Count > 1) {
|
|
// there is another nucleus feeding into this input nucleus
|
|
synapse.nucleus.receivers.RemoveAll(r => r.nucleus == nucleus);
|
|
}
|
|
else {
|
|
// No other links, delete it.
|
|
Nucleus.Delete(synapse.nucleus);
|
|
}
|
|
}
|
|
foreach (Receiver receiver in nucleus.receivers) {
|
|
if (receiver.nucleus != null && receiver.nucleus.synapses != null)
|
|
receiver.nucleus.synapses.RemoveAll(s => s.nucleus == nucleus);
|
|
}
|
|
|
|
if (nucleus.brain != null) {
|
|
nucleus.brain.nuclei.RemoveAll(n => n == nucleus);
|
|
nucleus.brain.GarbageCollection();
|
|
}
|
|
}
|
|
|
|
public void GetInputFrom(Nucleus input, float weight = 1.0f) {
|
|
input.AddReceiver(this);
|
|
this.SetWeight(input, weight);
|
|
}
|
|
|
|
public bool SynapseExists(Nucleus nucleus) {
|
|
foreach (Synapse synapse in synapses) {
|
|
if (synapse.nucleus == nucleus)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void SetWeight(Nucleus nucleus, float weight) {
|
|
foreach (Synapse synapse in synapses) {
|
|
if (synapse.nucleus == nucleus) {
|
|
synapse.weight = weight;
|
|
return;
|
|
}
|
|
}
|
|
Synapse newSynapse = new(nucleus, weight);
|
|
synapses.Add(newSynapse);
|
|
}
|
|
|
|
public virtual void UpdateState() { }
|
|
|
|
public void UpdateResult(Vector3 result) {
|
|
float d = Vector3.Distance(result, this.outputValue);
|
|
if (d < 0.5f) {
|
|
//Debug.Log($"insignificant update: {d}");
|
|
return;
|
|
}
|
|
|
|
this.outputValue = result;
|
|
foreach (Receiver receiver in this.receivers)
|
|
receiver.nucleus.UpdateState();
|
|
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class Synapse {
|
|
[System.NonSerialized]
|
|
public Nucleus nucleus;
|
|
public int nucleusId;
|
|
public float weight;
|
|
|
|
public enum CurvePresets {
|
|
Linear,
|
|
Power,
|
|
Sqrt,
|
|
Reciprocal,
|
|
Custom
|
|
}
|
|
// public CurvePresets curvePreset;
|
|
// public AnimationCurve curve;
|
|
public float curveMax = 1.0f;
|
|
|
|
public Synapse(Nucleus nucleus, float weight) {
|
|
this.nucleus = nucleus;
|
|
this.nucleusId = nucleus.id;
|
|
this.weight = weight;
|
|
}
|
|
|
|
public void Rebuild(NanoBrain brain) {
|
|
if (brain == null) {
|
|
return;
|
|
}
|
|
|
|
foreach (Nucleus nucleus in brain.nuclei) {
|
|
if (nucleus.id == this.nucleusId) {
|
|
this.nucleus = nucleus;
|
|
return;
|
|
}
|
|
}
|
|
foreach (Perceptoid perceptoid in brain.perceptei) {
|
|
if (perceptoid.id == this.nucleusId) {
|
|
this.nucleus = perceptoid;
|
|
return;
|
|
}
|
|
}
|
|
Debug.LogError($"Synapse deserialization error: could not find nucleus with id {this.nucleusId}");
|
|
}
|
|
|
|
// public AnimationCurve GenerateCurve() {
|
|
// switch (this.curvePreset) {
|
|
// case CurvePresets.Linear:
|
|
// this.curveMax = this.weight;
|
|
// return Presets.Linear(this.weight);
|
|
// case CurvePresets.Power:
|
|
// this.curveMax = this.weight;
|
|
// return Presets.Power(2.0f, this.weight);
|
|
// case CurvePresets.Sqrt:
|
|
// this.curveMax = this.weight;
|
|
// return Presets.Power(0.5f, this.weight);
|
|
// case CurvePresets.Reciprocal:
|
|
// this.curveMax = 1 / 0.01f * this.weight;
|
|
// return Presets.Reciprocal(this.weight);
|
|
// default:
|
|
// this.curveMax = weight;
|
|
// return AnimationCurve.Constant(0, 1, weight);
|
|
// }
|
|
// }
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class Receiver {
|
|
[System.NonSerialized]
|
|
public Nucleus nucleus;
|
|
public int nucleusId;
|
|
|
|
public Receiver(Nucleus nucleus) {
|
|
this.nucleus = nucleus;
|
|
this.nucleusId = nucleus.id;
|
|
}
|
|
|
|
public bool Rebuild(NanoBrain brain) {
|
|
if (brain == null) {
|
|
return false;
|
|
}
|
|
|
|
foreach (Nucleus nucleus in brain.nuclei) {
|
|
if (nucleus.id == this.nucleusId) {
|
|
this.nucleus = nucleus;
|
|
return true;
|
|
}
|
|
}
|
|
Debug.LogWarning($"Receiver deserialization error: could not find nucleus with id {this.nucleusId}");
|
|
return false;
|
|
}
|
|
} |