From bb5939b6ab73ae8168da352f16d310767e0e454e Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Mon, 1 Dec 2025 17:37:43 +0100 Subject: [PATCH] new boundary perception --- Assembly-CSharp.csproj | 1 + Assets/NanoBrain/Neuroid.cs | 12 ++- Assets/NanoBrain/Nucleus.cs | 7 ++ Assets/NanoBrain/Nucleus.cs.meta | 2 + Assets/NanoBrain/Perception.cs | 58 ++++++++--- Assets/Scenes/Boids/Boids.unity | 2 +- Assets/Scenes/Boids/Scripts/Boid.cs | 110 +++++++++++--------- Assets/Scenes/Boids/Scripts/SwarmControl.cs | 2 +- 8 files changed, 130 insertions(+), 64 deletions(-) create mode 100644 Assets/NanoBrain/Nucleus.cs create mode 100644 Assets/NanoBrain/Nucleus.cs.meta diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj index 97530fd..526dc5f 100644 --- a/Assembly-CSharp.csproj +++ b/Assembly-CSharp.csproj @@ -55,6 +55,7 @@ + diff --git a/Assets/NanoBrain/Neuroid.cs b/Assets/NanoBrain/Neuroid.cs index 09c5606..2eb26d8 100644 --- a/Assets/NanoBrain/Neuroid.cs +++ b/Assets/NanoBrain/Neuroid.cs @@ -102,6 +102,16 @@ public class Neuroid { UpdateState(); } + public void RemoveInputFrom(Neuroid input) { + this.synapses.Remove(input); + if (this.synapses.Count == 0) { + // In case this was the last synapse, we reset the output because in this case no updates from synapses will follow. + this.outputValue = Vector3.zero; + foreach (Neuroid neuroid in this.outputNeuroids) + neuroid.SetInput(this, this.outputValue); + } + } + // public readonly Dictionary fakeNeuroids = new(); // public void SetInput(int thingId, Vector3 value, float weight, NeuroidNetwork net) { // if (fakeNeuroids.ContainsKey(thingId)) { @@ -131,7 +141,7 @@ public class Neuroid { result /= this.synapses.Count; this.outputValue = result; - foreach (Neuroid neuroid in outputNeuroids) + foreach (Neuroid neuroid in this.outputNeuroids) neuroid.SetInput(this, this.outputValue); this.stale = 0; } diff --git a/Assets/NanoBrain/Nucleus.cs b/Assets/NanoBrain/Nucleus.cs new file mode 100644 index 0000000..e3a398d --- /dev/null +++ b/Assets/NanoBrain/Nucleus.cs @@ -0,0 +1,7 @@ +public class Nucleus { + public class State { + + } + + public State state; +} \ No newline at end of file diff --git a/Assets/NanoBrain/Nucleus.cs.meta b/Assets/NanoBrain/Nucleus.cs.meta new file mode 100644 index 0000000..e520090 --- /dev/null +++ b/Assets/NanoBrain/Nucleus.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 750748f3f0e7d472fbf88ab02987074c \ No newline at end of file diff --git a/Assets/NanoBrain/Perception.cs b/Assets/NanoBrain/Perception.cs index 99729c3..b83b402 100644 --- a/Assets/NanoBrain/Perception.cs +++ b/Assets/NanoBrain/Perception.cs @@ -1,13 +1,19 @@ using System.Collections.Generic; using UnityEngine; -public class Perception { + +public class Perception : Nucleus { public SensoryNeuroid[] sensoryNeuroids = new SensoryNeuroid[7]; public NeuroidNetwork neuroidNet { get; protected set; } - public HashSet positionReceivers { get; protected set; } - public HashSet velocityReceivers { get; protected set; } + public class Receiver { + public int thingType = 0; + public Neuroid neuroid; + } + + public HashSet positionReceivers { get; protected set; } + public HashSet velocityReceivers { get; protected set; } public Perception(NeuroidNetwork neuroidNet) { this.neuroidNet = neuroidNet; @@ -15,26 +21,34 @@ public class Perception { this.velocityReceivers = new(); } - public void SendPositions(Neuroid receiver, float weight = 1.0f) { + public void SendPositions(Neuroid receivingNeuroid, float weight = 1.0f, int thingType = 0) { + Receiver receiver = new() { + thingType = thingType, + neuroid = receivingNeuroid + }; positionReceivers.Add(receiver); foreach (SensoryNeuroid neuroid in sensoryNeuroids) { if (neuroid != null) { - neuroid.AddReceiver(receiver); - receiver.synapses[neuroid] = new (neuroid, Vector3.zero, weight); + neuroid.AddReceiver(receivingNeuroid); + receivingNeuroid.synapses[neuroid] = new(neuroid, Vector3.zero, weight); } } } - public void SendVelocities(Neuroid receiver) { + public void SendVelocities(Neuroid receivingNeuroid, float weight = 1.0f, int thingType = 0) { + Receiver receiver = new() { + thingType = thingType, + neuroid = receivingNeuroid + }; velocityReceivers.Add(receiver); foreach (SensoryNeuroid neuroid in sensoryNeuroids) { if (neuroid != null && neuroid.velocityNeuroid != null) { - neuroid.velocityNeuroid.AddReceiver(receiver); - receiver.synapses[neuroid] = new (neuroid, Vector3.zero, 1.0f); + neuroid.velocityNeuroid.AddReceiver(receivingNeuroid); + receivingNeuroid.synapses[neuroid] = new(neuroid, Vector3.zero, 1.0f); } } } - public void ProcessStimulus(int thingId, Vector3 localPosition) { + public void ProcessStimulus(int thingId, int thingType, Vector3 localPosition) { int availableIx = -1; SensoryNeuroid leastInterestingNeuroid = null; for (int i = 0; i < sensoryNeuroids.Length; i++) { @@ -58,10 +72,14 @@ public class Perception { else { // Debug.Log($"new receptor for {thingId}"); SensoryNeuroid neuroid = new(neuroidNet, thingId); - foreach (Neuroid receiver in positionReceivers) - receiver.GetInputFrom(neuroid); - foreach (Neuroid receiver in velocityReceivers) - receiver.GetInputFrom(neuroid.velocityNeuroid); + foreach (Receiver receiver in positionReceivers) { + if (receiver.thingType == 0 || receiver.thingType == thingType) + receiver.neuroid.GetInputFrom(neuroid); + } + foreach (Receiver receiver in velocityReceivers) { + if (receiver.thingType == 0 || receiver.thingType == thingType) + receiver.neuroid.GetInputFrom(neuroid.velocityNeuroid); + } sensoryNeuroids[availableIx] = neuroid; neuroid.receptor.position = localPosition; @@ -75,4 +93,16 @@ public class Perception { //Debug.LogWarning($"No available receptor for {id}"); } + + public void RemoveStimulus(int thingId) { + for (int i = 0; i < sensoryNeuroids.Length; i++) { + if (sensoryNeuroids[i] != null && sensoryNeuroids[i].receptor.thingId == thingId) { + foreach (Neuroid outputNeuroid in sensoryNeuroids[i].outputNeuroids) + outputNeuroid.RemoveInputFrom(sensoryNeuroids[i]); + sensoryNeuroids[i] = null; + return; + } + } + + } } \ No newline at end of file diff --git a/Assets/Scenes/Boids/Boids.unity b/Assets/Scenes/Boids/Boids.unity index 0f3df0d..ee7a797 100644 --- a/Assets/Scenes/Boids/Boids.unity +++ b/Assets/Scenes/Boids/Boids.unity @@ -393,7 +393,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ec888ca5333d45a438f9f417fa5ce135, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::SwarmSpawn - count: 10 + count: 2 boidPrefab: {fileID: 8702527964058765413, guid: f9c706268554ce449a8773675b2864b8, type: 3} spawnAreaSize: {x: 0.5, y: 0.5, z: 0.5} minDelay: 0.05 diff --git a/Assets/Scenes/Boids/Scripts/Boid.cs b/Assets/Scenes/Boids/Scripts/Boid.cs index 1375e50..f239e3e 100644 --- a/Assets/Scenes/Boids/Scripts/Boid.cs +++ b/Assets/Scenes/Boids/Scripts/Boid.cs @@ -11,13 +11,17 @@ public class Boid : MonoBehaviour { public float separationDistance = 0.5f; public float bodyForce = 1; + public const int BoundaryType = 1; + public const int BoidType = 2; + public bool debug = false; public SwarmControl sc; public Vector3 velocity = Vector3.zero; public Vector3 acceleration = Vector3.zero; - private Bounds bounds; + private Bounds innerBounds; + private Bounds outerBounds; readonly Collider[] results = new Collider[10]; @@ -28,7 +32,7 @@ public class Boid : MonoBehaviour { public Neuroid bodyVector; public Neuroid cohesion; public Neuroid alignment; - public Neuroid separation; + public Neuroid avoidance; // public Neuroid target; public Neuroid boundary; @@ -41,76 +45,73 @@ public class Boid : MonoBehaviour { sc = FindFirstObjectByType(); - bounds = new(sc.transform.position, sc.spaceSize - 2 * sc.boundaryWidth); + innerBounds = new(sc.transform.position, sc.spaceSize - 2 * sc.boundaryWidth); + outerBounds = new(sc.transform.position, sc.spaceSize); perception = new Perception(neuroidNet); cohesion = new(neuroidNet, "Cohesion"); - perception.SendPositions(cohesion); + perception.SendPositions(cohesion, 1.0f, BoidType); - alignment = new(neuroidNet, "Alignment") { average = true }; - perception.SendVelocities(alignment); + // alignment = new(neuroidNet, "Alignment") { average = true }; + // perception.SendVelocities(alignment); - separation = new(neuroidNet, "Separation") { inverse = true, exponent = 2 }; - perception.SendPositions(separation, sc.separationForce); + avoidance = new(neuroidNet, "Separation") { inverse = true }; + perception.SendPositions(avoidance, sc.avoidanceForce); boundary = new(neuroidNet, "Boundary"); totalForce = new(neuroidNet, "Total"); - totalForce.GetInputFrom(alignment, sc.alignmentForce); - totalForce.GetInputFrom(cohesion, sc.cohesionForce); - totalForce.GetInputFrom(separation, -sc.separationForce); - totalForce.GetInputFrom(boundary, sc.boundaryForce); + //totalForce.GetInputFrom(alignment, sc.alignmentForce); + //totalForce.GetInputFrom(cohesion, sc.cohesionForce); + totalForce.GetInputFrom(avoidance, -sc.avoidanceForce); + //totalForce.GetInputFrom(boundary, sc.boundaryForce); } void Update() { Physics.OverlapSphereNonAlloc(this.transform.position, sc.perceptionDistance, results); - foreach (Collider c in results) { - if (c == null) - continue; + // foreach (Collider c in results) { + // if (c == null) + // continue; - if (c as CapsuleCollider != null) { - Boid neighbour = c.GetComponentInParent(); - if (neighbour == null || neighbour == this) - continue; + // if (c as CapsuleCollider != null) { + // Boid neighbour = c.GetComponentInParent(); + // if (neighbour == null || neighbour == this) + // continue; - Vector3 localPosition = neighbour.transform.position - this.transform.position; - if (debug) - Debug.Log($" distance {localPosition.magnitude}"); + // Vector3 localPosition = neighbour.transform.position - this.transform.position; + // if (debug) + // Debug.Log($" distance {localPosition.magnitude}"); - int thingId = neighbour.GetInstanceID(); - perception.ProcessStimulus(thingId, localPosition); - } - } - - // if (!bounds.Contains(this.transform.position)) { - // Vector3 point = this.transform.position; - // // Vector3 distanceOutside = Vector3.Max(bounds.min - this.transform.position, this.transform.position - bounds.max); - // // // Ensure value is > 0 (but isn't this already) - // // Vector3 outside = distanceOutside; // Vector3.Max(Vector3.zero, distanceOutside); - - // Vector3 below = bounds.min - point; // positive where point < min - // Vector3 above = point - bounds.max; // positive where point > max - - // // outside distances per axis (0 if inside on that axis) - // Vector3 outside = Vector3.Max(Vector3.zero, Vector3.Max(below, above)); - // float magnitude = outside.magnitude; - // Vector3 direction = (sc.transform.position - this.transform.position).normalized; - // outside = direction * magnitude; - - // boundary.SetInput(id, outside, sc.boundaryForce, neuroidNet); - // // Debug.Log($"boundary {this.transform.position} {outside} force = {outside * sc.boundaryForce}"); + // int thingId = neighbour.GetInstanceID(); + // perception.ProcessStimulus(thingId, localPosition); + // } // } + if (!innerBounds.Contains(this.transform.position)) { + Vector3 point = this.transform.position; + Vector3 toBounds; + if (outerBounds.Contains(this.transform.position)) { + Vector3 pointOnBounds = ClosestPointOnBoundsSurface(outerBounds, point); + toBounds = this.transform.InverseTransformPoint(pointOnBounds); + } + else { + Vector3 pointOnBounds = innerBounds.ClosestPoint(point); + toBounds = -this.transform.InverseTransformPoint(pointOnBounds); + } + perception.ProcessStimulus(777, BoundaryType, toBounds); + } else + perception.RemoveStimulus(777); + Vector3 totalForceVector = totalForce.outputValue; - //Debug.DrawRay(this.transform.position, totalForceVector, Color.magenta); if (this.debug) { - Debug.Log($"Cohesion {cohesion.outputValue.magnitude} separation {separation.outputValue.magnitude} alignment {alignment.outputValue.magnitude}"); + Debug.Log($"Cohesion {cohesion.outputValue.magnitude} separation {avoidance.outputValue.magnitude} alignment {alignment.outputValue.magnitude}"); } - this.velocity = (1 - sc.inertia) * (totalForceVector * Time.deltaTime) + sc.inertia * velocity + (sc.speed * transform.forward); + Vector3 worldForce = this.transform.TransformDirection(totalForceVector); + this.velocity = (1 - sc.inertia) * (worldForce * Time.deltaTime) + sc.inertia * velocity + (sc.speed * transform.forward); //this.velocity = Vector3.ClampMagnitude(this.velocity, sc.speed); this.transform.position += this.velocity * Time.deltaTime; @@ -124,7 +125,22 @@ public class Boid : MonoBehaviour { neuroidNet.Update(); } + Vector3 ClosestPointOnBoundsSurface(Bounds b, Vector3 p) { + if (!b.Contains(p)) return b.ClosestPoint(p); + Vector3 d = p - b.center; + Vector3 ext = b.extents; + float sx = ext.x / Mathf.Abs(d.x); + float sy = ext.y / Mathf.Abs(d.y); + float sz = ext.z / Mathf.Abs(d.z); + float m = Mathf.Min(sx, Mathf.Min(sy, sz)); + return b.center + d * m; + } + void OnDrawGizmosSelected() { Gizmos.DrawWireSphere(transform.position, sc.perceptionDistance); + Gizmos.color = Color.yellow; + Gizmos.DrawRay(transform.position, this.transform.TransformDirection(totalForce.outputValue) * 10); + Gizmos.color = Color.magenta; + Gizmos.DrawRay(transform.position, this.transform.TransformDirection(avoidance.outputValue) * 10); } } diff --git a/Assets/Scenes/Boids/Scripts/SwarmControl.cs b/Assets/Scenes/Boids/Scripts/SwarmControl.cs index fa502ab..c98a403 100644 --- a/Assets/Scenes/Boids/Scripts/SwarmControl.cs +++ b/Assets/Scenes/Boids/Scripts/SwarmControl.cs @@ -8,7 +8,7 @@ public class SwarmControl : MonoBehaviour public float inertia = 0.1f; public float alignmentForce = 0.0f; public float cohesionForce = 10.0f; - public float separationForce = 5.0f; + public float avoidanceForce = 5.0f; public float separationDistance = 0.5f; // public float bodyForce = 20; public float perceptionDistance = 1.0f;