92 lines
3.7 KiB
C#
92 lines
3.7 KiB
C#
using UnityEngine;
|
|
|
|
namespace NanoBrain.Unity.Braitenberg {
|
|
|
|
/// <summary>
|
|
/// A light sensor
|
|
/// </summary>
|
|
[HelpURL("https://passer.life/documentation/nanobrain/Documentation/html/class_nano_brain_1_1_unity_1_1_braitenberg_1_1_light_sensor.html")]
|
|
public class LightSensor : Sensor {
|
|
/// <summary>
|
|
/// If true, perform occlusion checks with raycasts
|
|
/// </summary>
|
|
[Tooltip("If true, perform occlusion checks with raycasts.")]
|
|
public bool useOcclusion = true;
|
|
/// <summary>
|
|
/// Angle in degrees for directional sensitivity; >= 180: omnidirectional.
|
|
/// </summary>
|
|
[Tooltip("Angle (degrees) for directional sensitivity; >=180: omnidirectional.")]
|
|
public float sensitivityAngle = 180f;
|
|
|
|
protected override float SampleSensor() {
|
|
float sum = 0f;
|
|
// Get all active lights in scene (Point lights only)
|
|
#if UNITY_6000_0_OR_NEWER
|
|
Light[] lights = FindObjectsByType<Light>();
|
|
#else
|
|
Light[] lights = FindObjectsByType<Light>(FindObjectsSortMode.None);
|
|
#endif
|
|
Vector3 pos = transform.position;
|
|
Vector3 forward = transform.forward;
|
|
|
|
float halfAngleCos = Mathf.Cos(sensitivityAngle * 0.5f * Mathf.Deg2Rad);
|
|
|
|
int lightCount = 0;
|
|
foreach (Light light in lights) {
|
|
if (!light.enabled || light.type != LightType.Point)
|
|
continue;
|
|
|
|
// quick distance check
|
|
float dist = Vector3.Distance(light.transform.position, pos);
|
|
if (dist > this.sensorRange + light.range)
|
|
continue; // outside both sensor and light ranges
|
|
|
|
// angular sensitivity
|
|
Vector3 toLight = (light.transform.position - pos).normalized;
|
|
if (sensitivityAngle < 180f) {
|
|
if (Vector3.Dot(forward, toLight) < halfAngleCos)
|
|
continue;
|
|
}
|
|
|
|
// compute light falloff: Unity point lights use inverse squared (physically plausible)
|
|
// you can approximate using L.intensity and L.range
|
|
float attenuation = 0f;
|
|
if (dist < light.range) {
|
|
// inverse-square like falloff (avoid divide-by-zero)
|
|
attenuation = light.intensity / Mathf.Max(0.01f, dist * dist);
|
|
// optionally scale by (1 - dist/range) for a linear blend:
|
|
attenuation *= 1f - dist / light.range;
|
|
}
|
|
else
|
|
continue;
|
|
|
|
// occlusion check
|
|
if (useOcclusion) {
|
|
Vector3 dir = (light.transform.position - pos).normalized;
|
|
float rayDist = Mathf.Min(dist, light.range);
|
|
if (Physics.Raycast(pos, dir, out RaycastHit hit, rayDist, senseLayer))
|
|
attenuation *= 0f; // completely blocked; or reduce by factor
|
|
}
|
|
|
|
//Debug.DrawRay(this.transform.position, toLight * sensorRange);
|
|
// color consideration: convert light color to luminance if needed
|
|
float luminance = RGBToLuminance(light.color) * attenuation;
|
|
//Debug.Log(luminance);
|
|
|
|
sum += luminance;
|
|
lightCount++;
|
|
}
|
|
|
|
if (lightCount == 0)
|
|
return 0;
|
|
else
|
|
return sum / lightCount;
|
|
}
|
|
|
|
static float RGBToLuminance(Color c) {
|
|
// simple Rec.709 luminance
|
|
return 0.2126f * c.r + 0.7152f * c.g + 0.0722f * c.b;
|
|
}
|
|
}
|
|
|
|
} |