105 lines
4.0 KiB
C#
105 lines
4.0 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
|
|
namespace NanoBrain.Unity.Braitenberg {
|
|
|
|
/// <summary>
|
|
/// A non-directional sensor
|
|
/// </summary>
|
|
/// The sensor has a field of view, but the signal returned does not include a direction
|
|
public class Sensor : MonoBehaviour {
|
|
/// <summary>
|
|
/// Maximum distance the sensor detects anything
|
|
/// </summary>
|
|
[Tooltip("Max distance sensor detects anything")]
|
|
public float sensorRange = 10f;
|
|
/// <summary>
|
|
/// Time between samples in seconds
|
|
/// </summary>
|
|
[Tooltip("Time between samples (sec.)")]
|
|
public float sampleInterval = 0.1f;
|
|
|
|
/// <summary>
|
|
/// Unity Layer for the light objects
|
|
/// </summary>
|
|
/// This is used to improve performance
|
|
[Tooltip("Unity Layer for the light objects")]
|
|
public LayerMask senseLayer;
|
|
|
|
/// <summary>
|
|
/// Output value of the sensor
|
|
/// </summary>
|
|
[Tooltip("Output value of the sensor")]
|
|
public float output { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// The NanoBrain::Neuron which is stimulated by this sensor
|
|
/// </summary>
|
|
public Neuron sensoryNeuron;
|
|
|
|
/// <summary>
|
|
/// Unity calls Awake when loading an instance of a script component.
|
|
/// </summary>
|
|
protected virtual void Awake() {
|
|
Vehicle vehicle = GetComponentInParent<Vehicle>();
|
|
if (vehicle == null)
|
|
return;
|
|
|
|
Cluster brain = vehicle.brain;
|
|
if (brain == null)
|
|
return;
|
|
|
|
sensoryNeuron = brain.GetNeuron(this.name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when a component of an active GameObject is first enabled.
|
|
/// </summary>
|
|
void OnEnable() => StartCoroutine(SampleRoutine());
|
|
/// <summary>
|
|
/// Called when a component itself is disabled or its parent GameObject is deactivated.
|
|
/// </summary>
|
|
void OnDisable() => StopAllCoroutines();
|
|
|
|
/// <summary>
|
|
/// The sensor samping routine.
|
|
/// </summary>
|
|
/// <returns>IEnumerator such that it can run as a Unity Coroutine</returns>
|
|
/// The sample rate is defined by sampleInterval
|
|
/// This also stimulates the sensoryNeuron if is has been found.
|
|
private IEnumerator SampleRoutine() {
|
|
WaitForSeconds wait = new(sampleInterval);
|
|
while (true) {
|
|
output = SampleSensor();
|
|
sensoryNeuron?.ProcessStimulus(Vector3.one * (output + 0.00001f));
|
|
yield return wait;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs a sampling function to retrieve a new sensor output value
|
|
/// </summary>
|
|
/// <returns>Sensor output value</returns>
|
|
// Cast a short set of rays in a cone and accumulate "brightness" from hits.
|
|
protected virtual float SampleSensor() {
|
|
int rays = 7;
|
|
float halfAngle = 30f;
|
|
float total = 0f;
|
|
|
|
for (int i = 0; i < rays; i++) {
|
|
float t = rays == 1 ? 0.5f : (float)i / (rays - 1);
|
|
float angle = Mathf.Lerp(-halfAngle, halfAngle, t);
|
|
Vector3 dir = Quaternion.AngleAxis(angle, this.transform.up) * this.transform.forward;
|
|
|
|
//Debug.DrawRay(this.transform.position, dir * sensorRange);
|
|
if (Physics.Raycast(this.transform.position, dir, out RaycastHit hit, sensorRange, senseLayer)) {
|
|
// Strength inversely proportional to distance, clamped to [0,1]
|
|
float str = 1f - (hit.distance / sensorRange);
|
|
// You can also sample material emission or color here if desired
|
|
total += Mathf.Clamp01(str);
|
|
}
|
|
}
|
|
return Mathf.Clamp01(total / rays);
|
|
}
|
|
}
|
|
} |