Boids seem to work, but don't flock together

This commit is contained in:
Pascal Serrarens 2025-11-27 14:08:24 +01:00
parent 2e803179e3
commit 1771ab7d23
12 changed files with 1289 additions and 37 deletions

View File

@ -37,6 +37,11 @@ public class GraphEditorWindow : EditorWindow {
int neuroidIx = 0;
foreach (Neuroid neuroid in neuroids) {
if (neuroid.IsStale()) {
neuronVisited.Add(neuroid);
continue;
}
// If this neuroid is not visited while its output neuroid is visited
if (!neuronVisited.Contains(neuroid) && (neuroid.outputNeuroid == null ||
(neuronVisited.Contains(neuroid.outputNeuroid) && neuroid.outputNeuroid.layerIx == layerIx - 1))) {
@ -109,17 +114,23 @@ public class GraphEditorWindow : EditorWindow {
float inputMargin = 100 + inputSpacing / 2;
foreach (Synapse synapse in layerNeuroid.synapses.Values) {
if (synapse.neuroid != null) {
Vector2Int inputNeuroidPos = this.neuroidPositions[synapse.neuroid];
Vector3 pos = new(100 + inputNeuroidPos.x * 100, inputMargin + inputNeuroidPos.y * inputSpacing, 0.0f);
if (this.neuroidPositions.ContainsKey(synapse.neuroid)) {
float brightness = synapse.weight / 10.0f;
Handles.color = new Color(brightness, brightness, brightness);
Handles.DrawLine(parentPos, pos);
Vector2Int inputNeuroidPos = this.neuroidPositions[synapse.neuroid];
Vector3 pos = new(100 + inputNeuroidPos.x * 100, inputMargin + inputNeuroidPos.y * inputSpacing, 0.0f);
float brightness = synapse.weight / 10.0f;
Handles.color = new Color(brightness, brightness, brightness);
Handles.DrawLine(parentPos, pos);
}
}
}
float size = layerNeuroid.outputValue.magnitude / maxValue * 20;
Handles.color = Color.white;
if (layerNeuroid.IsStale())
Handles.color = Color.yellow;
else
Handles.color = Color.white;
Handles.DrawSolidDisc(parentPos, Vector3.forward, size);
Rect neuronRect = new(parentPos.x - size, parentPos.y - size, size * 2, size * 2);
if (neuronRect.Contains(Event.current.mousePosition))
@ -131,7 +142,12 @@ public class GraphEditorWindow : EditorWindow {
private void HandleMouseHover(Neuroid neuroid, Rect rect) {
// Draw the tooltip
GUIContent tooltip = new($"{neuroid.name}\n synapse count {neuroid.synapses.Count} \n Value: {neuroid.outputValue}");
GUIContent tooltip = new(
$"{neuroid.name}" +
$"\nsynapse count {neuroid.synapses.Count}" +
$"\nValue: {neuroid.outputValue}" +
$"\nStale: {neuroid.stale}");
Vector2 mousePosition = Event.current.mousePosition;
// Display tooltip with some offset

View File

@ -20,6 +20,12 @@ public class NeuroidNetwork {
Neuroid neuroid = new(this);
return neuroid;
}
public void Update() {
foreach (Neuroid neuroid in neuroids) {
neuroid.stale++;
}
}
}
public class Neuroid {
@ -27,6 +33,7 @@ public class Neuroid {
public string name;
public int layerIx;
public int stale = 0;
public readonly Dictionary<int, Synapse> synapses = new();
@ -97,6 +104,7 @@ public class Neuroid {
this.outputValue = Activation(sum);
this.outputNeuroid?.SetInput(this.outputNeurix, this.outputValue);
this.stale = 0;
}
Vector3 Activation(Vector3 sum) {
@ -111,5 +119,9 @@ public class Neuroid {
};
//return sum; //(sum.magnitude > 0.5f) ? sum : Vector3.zero;
}
public bool IsStale() {
return this.stale > 2;
}
}

View File

@ -7,15 +7,23 @@ public class Receptor {
neuroid.SetInput(neuroid.id, value);
}
}
public Vector3 GetValue() {
if (neuroid != null)
return neuroid.synapses[neuroid.id].value;
else
return Vector3.zero;
}
}
public class SensoryNeuroid : Neuroid {
public Receptor receptor;
public int thingId;
public SensoryNeuroid(NeuroidNetwork net, int id) : base(net) {
this.name = "sensory neuroid";
this.id = id;
// this.id = id;
this.thingId = id;
this.receptor = new Receptor {
neuroid = this
};

View File

@ -371,14 +371,15 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 0464906885ae3494f8fd0314719fb2db, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::SwarmControl
speed: 0.5
speed: 1
inertia: 0.1
alignmentForce: 1
cohesionForce: 10
alignmentForce: 5
cohesionForce: 5
separationForce: 5
separationDistance: 0.5
separationDistance: 0.3
bodyForce: 20
boundaryForce: 2
perceptionDistance: 2
boundaryForce: 5
spaceSize: {x: 10, y: 10, z: 10}
boundaryWidth: {x: 1, y: 1, z: 1}
--- !u!114 &301943979
@ -393,11 +394,11 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: ec888ca5333d45a438f9f417fa5ce135, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::SwarmSpawn
count: 100
count: 1000
boidPrefab: {fileID: 8702527964058765413, guid: f9c706268554ce449a8773675b2864b8, type: 3}
spawnAreaSize: {x: 0.5, y: 0.5, z: 0.5}
minDelay: 0.1
maxDelay: 1
minDelay: 0.05
maxDelay: 0.2
--- !u!4 &301943980
Transform:
m_ObjectHideFlags: 0

View File

@ -21,17 +21,17 @@ Material:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: White
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_ValidKeywords:
- _GLOSSYREFLECTIONS_OFF
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap:
RenderType: Opaque
stringTagMap: {}
disabledShaderPasses:
- MOTIONVECTORS
m_LockedProperties:
@ -115,6 +115,7 @@ Material:
- _Glossiness: 0
- _GlossyReflections: 0
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.005
- _QueueOffset: 0
@ -125,12 +126,13 @@ Material:
- _SrcBlend: 1
- _SrcBlendAlpha: 1
- _Surface: 0
- _UVSec: 0
- _WorkflowMode: 1
- _XRMotionVectorsPass: 1
- _ZWrite: 1
m_Colors:
- _BaseColor: {r: 1, g: 1, b: 1, a: 1}
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _BaseColor: {r: 1, g: 0.9858491, b: 0.9858491, a: 1}
- _Color: {r: 0.41509432, g: 0.41509432, b: 0.41509432, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
m_BuildTextureStacks: []

View File

@ -59,12 +59,12 @@ public class Boid : MonoBehaviour {
}
void Update() {
Physics.OverlapSphereNonAlloc(this.transform.position, 10, results);
Physics.OverlapSphereNonAlloc(this.transform.position, sc.perceptionDistance, results);
neighbourCount = 0;
cohesion.ResetWeights();
alignment.ResetWeights();
separation.ResetWeights();
//separation.ResetWeights();
foreach (Collider c in results) {
if (c == null)
@ -79,18 +79,16 @@ public class Boid : MonoBehaviour {
Vector3 relativeVelocity = neighbour.velocity - this.velocity;
int id = neighbour.GetInstanceID();
Receptor receptor = GetReceptor(id);
if (receptor != null) {
receptor.SetValue(localPosition);
}
ProcessStimulus(id, localPosition);
Vector3 separationForce = -localPosition / localPosition.sqrMagnitude;
// which is equivalent to -(localPosition.normalized / localPosition.magnitude)
separation.SetInput(id, separationForce, sc.separationDistance);
//cohesion.SetInput(id, localPosition, sc.cohesionForce);
alignment.SetInput(id, relativeVelocity, sc.alignmentForce);
neighbourCount++;
}
}
@ -126,24 +124,81 @@ public class Boid : MonoBehaviour {
Quaternion targetRotation = Quaternion.LookRotation(this.velocity);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 2f); // Adjust the speed of rotation
}
//Debug.Log($"neighbours: {neighbourCount} synapses: {cohesion.synapses.Count}");
neuroidNet.Update();
}
Receptor GetReceptor(int id) {
Receptor GetReceptor(Neuroid perceptionNeuroid, int id) {
int availableIx = -1;
for (int i = 0; i < neighbourSensor.Length; i++) {
if (neighbourSensor[i] == null)
if (neighbourSensor[i] == null || neighbourSensor[i].IsStale())
availableIx = i;
else if (neighbourSensor[i].id == id)
else if (neighbourSensor[i].thingId == id)
return neighbourSensor[i].receptor;
}
if (availableIx != -1) {
Debug.Log($"new receptor for {id}");
SensoryNeuroid neuroid = new(neuroidNet, id);
cohesion.GetInputFrom(neuroid);
neighbourSensor[availableIx] = neuroid;
return neuroid.receptor;
if (neighbourSensor[availableIx] != null) {
Debug.Log($"revived receptor {availableIx} for {id}");
neighbourSensor[availableIx].thingId = id;
return neighbourSensor[availableIx].receptor;
}
else {
Debug.Log($"new receptor for {id}");
SensoryNeuroid neuroid = new(neuroidNet, id);
perceptionNeuroid.GetInputFrom(neuroid);
neighbourSensor[availableIx] = neuroid;
return neuroid.receptor;
}
}
//Debug.LogWarning($"No available receptor for {id}");
return null;
}
void ProcessStimulus(int thingId, Vector3 value) {
int availableIx = -1;
SensoryNeuroid leastInterestingNeuroid = null;
for (int i = 0; i < neighbourSensor.Length; i++) {
if (neighbourSensor[i] == null || neighbourSensor[i].IsStale())
availableIx = i;
else if (neighbourSensor[i].thingId == thingId) {
neighbourSensor[i].receptor.SetValue(value);
return;
}
// if (leastInterestingIx == -1 || neighbourSensor[leastInterestingIx].receptor.GetValue().magnitude > neighbourSensor[i].receptor.GetValue().magnitude)
// leastInterestingIx = i;
if (neighbourSensor[i] != null) {
if (leastInterestingNeuroid == null || leastInterestingNeuroid.receptor.GetValue().magnitude > neighbourSensor[i].receptor.GetValue().magnitude)
leastInterestingNeuroid = neighbourSensor[i];
}
}
if (availableIx != -1) {
if (neighbourSensor[availableIx] != null) {
// Debug.Log($"revived receptor {availableIx} for {thingId}");
neighbourSensor[availableIx].thingId = thingId;
neighbourSensor[availableIx].receptor.SetValue(value);
}
else {
// Debug.Log($"new receptor for {thingId}");
SensoryNeuroid neuroid = new(neuroidNet, thingId);
cohesion.GetInputFrom(neuroid);
neighbourSensor[availableIx] = neuroid;
neuroid.receptor.SetValue(value);
}
}
else if (leastInterestingNeuroid != null) {
//Debug.Log($"replaced receptor {leastInterestingNeuroid.thingId} for {thingId}");
leastInterestingNeuroid.thingId = thingId;
leastInterestingNeuroid.receptor.SetValue(value);
}
//Debug.LogWarning($"No available receptor for {id}");
}
void OnDrawGizmosSelected() {
Gizmos.DrawWireSphere(transform.position, sc.perceptionDistance);
}
}

View File

@ -11,6 +11,7 @@ public class SwarmControl : MonoBehaviour
public float separationForce = 5.0f;
public float separationDistance = 0.5f;
public float bodyForce = 20;
public float perceptionDistance = 1.0f;
public float boundaryForce = 2.0f;
public Vector3 spaceSize = new (10, 10, 10);

8
Assets/_Recovery.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 895037c7e323e03ada7f43d011f1390b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

1123
Assets/_Recovery/0.unity Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,19 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"files.associations": {
"*.asset": "yaml",
"*.meta": "yaml",
"*.prefab": "yaml",
"*.unity": "yaml"
},
"dotnet.defaultSolution": "NanoBrain-Unity.sln",
"dotnet.server.useOmnisharp": true,
"omnisharp.useModernNet": false,
"dotnet.automaticallyCreateSolutionInWorkspace": false
}
}

BIN
mono_crash.mem.19211.1.blob Normal file

Binary file not shown.