205 lines
8.3 KiB
C#
205 lines
8.3 KiB
C#
using UnityEngine;
|
|
|
|
|
|
public class Boid : MonoBehaviour {
|
|
public float speed = 0.2f;
|
|
public int neighbourCount = 0;
|
|
public float inertia = 0.2f;
|
|
public float alignmentForce = 1.0f;
|
|
public float cohesionForce = 1.0f;
|
|
public float separationForce = 1.0f;
|
|
public float separationDistance = 0.5f;
|
|
public float bodyForce = 1;
|
|
|
|
public SwarmControl sc;
|
|
public Vector3 velocity = Vector3.zero;
|
|
public Vector3 acceleration = Vector3.zero;
|
|
|
|
private Bounds bounds;
|
|
|
|
readonly Collider[] results = new Collider[10];
|
|
|
|
public SensoryNeuroid[] neighbourSensor = new SensoryNeuroid[6];
|
|
|
|
public NeuroidNetwork neuroidNet = new();
|
|
public Neuroid bodyVector;
|
|
public Neuroid cohesion;
|
|
public Neuroid alignment;
|
|
public Neuroid separation;
|
|
public Neuroid target;
|
|
public Neuroid boundary;
|
|
|
|
public Neuroid totalForce;
|
|
|
|
public int id;
|
|
|
|
void Awake() {
|
|
this.id = this.GetInstanceID();
|
|
|
|
sc = FindFirstObjectByType<SwarmControl>();
|
|
|
|
bounds = new(sc.transform.position, sc.spaceSize - 2 * sc.boundaryWidth);
|
|
|
|
//neighbourSensor = new(neuroidNet) { name = "Neighbour", id = 879 };
|
|
|
|
cohesion = new(neuroidNet) { name = "Cohesion", mode = Neuroid.Mode.Sum };
|
|
//cohesion.GetInputFrom(neighbourSensor);
|
|
alignment = new(neuroidNet) { name = "Alignment", mode = Neuroid.Mode.Average };
|
|
separation = new(neuroidNet) { name = "Separation", mode = Neuroid.Mode.Sum };
|
|
target = new(neuroidNet) { name = "Target", mode = Neuroid.Mode.Sum };
|
|
boundary = new(neuroidNet) { name = "Boundary", mode = Neuroid.Mode.Sum };
|
|
|
|
|
|
totalForce = new(neuroidNet) { name = "Total force", mode = Neuroid.Mode.Sum };
|
|
totalForce.GetInputFrom(alignment, sc.alignmentForce);
|
|
totalForce.GetInputFrom(cohesion, sc.cohesionForce);
|
|
totalForce.GetInputFrom(separation, sc.separationForce);
|
|
totalForce.GetInputFrom(target, sc.bodyForce);
|
|
totalForce.GetInputFrom(boundary, sc.boundaryForce);
|
|
}
|
|
|
|
void Update() {
|
|
Physics.OverlapSphereNonAlloc(this.transform.position, sc.perceptionDistance, results);
|
|
neighbourCount = 0;
|
|
|
|
cohesion.ResetWeights();
|
|
alignment.ResetWeights();
|
|
//separation.ResetWeights();
|
|
|
|
foreach (Collider c in results) {
|
|
if (c == null)
|
|
continue;
|
|
|
|
if (c as CapsuleCollider != null) {
|
|
Boid neighbour = c.GetComponentInParent<Boid>();
|
|
if (neighbour == null || neighbour == this)
|
|
continue;
|
|
|
|
Vector3 localPosition = neighbour.transform.position - this.transform.position;
|
|
Vector3 relativeVelocity = neighbour.velocity - this.velocity;
|
|
|
|
int id = neighbour.GetInstanceID();
|
|
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++;
|
|
|
|
}
|
|
}
|
|
|
|
//Vector3 spaceLocalPosition = sc.transform.InverseTransformPoint(this.transform.position);
|
|
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);
|
|
// Debug.Log($"boundary {this.transform.position} {outside} force = {outside * sc.boundaryForce}");
|
|
}
|
|
|
|
Vector3 totalForceVector = totalForce.outputValue;
|
|
//Debug.DrawRay(this.transform.position, totalForceVector, Color.magenta);
|
|
|
|
this.velocity = (1 - sc.inertia) * (totalForceVector * Time.deltaTime) + sc.inertia * velocity + (sc.speed * transform.forward);
|
|
//this.velocity = Vector3.ClampMagnitude(this.velocity, sc.speed);
|
|
|
|
this.transform.position += this.velocity * Time.deltaTime;
|
|
|
|
if (this.velocity != Vector3.zero) {
|
|
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(Neuroid perceptionNeuroid, int id) {
|
|
int availableIx = -1;
|
|
for (int i = 0; i < neighbourSensor.Length; i++) {
|
|
if (neighbourSensor[i] == null || neighbourSensor[i].IsStale())
|
|
availableIx = i;
|
|
else if (neighbourSensor[i].thingId == id)
|
|
return neighbourSensor[i].receptor;
|
|
}
|
|
if (availableIx != -1) {
|
|
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);
|
|
}
|
|
}
|