using UnityEngine;
namespace ServiceSim
{
///
/// ロボットの前に人・ロボット・椅子があるかを見る。
/// 見るだけで、止まる・動き出すの判断はしない(それは ServingRobotController)。
/// 止まった原因を分けられるように、いちばん近いものの種類も返す。
///
public class FrontSensor : MonoBehaviour
{
public struct Reading
{
/// 前に何かあるか
public bool blocked;
/// いちばん近いものまでの、ロボットの外周からの距離(m)。何もなければ sensorRange
public float nearestDistance;
public bool sawRobot;
public bool sawPerson;
public bool sawChair;
/// いちばん近いものが人か
public bool nearestIsPerson;
/// いちばん近いものが人のときの種類
public PersonKind nearestPersonKind;
/// いちばん近いものが人のときの速さ(m/秒)
public float nearestPersonSpeed;
/// いちばん近いものが人で、立ち止まっているか
public bool nearestPersonStanding;
/// いちばん近いものがロボットか
public bool nearestIsRobot;
/// いちばん近いものが椅子か
public bool nearestIsChair;
/// いちばん近いものの名前(cause_detail に使う)
public string nearestName;
}
[Header("設定")]
public RobotSpec spec;
[Tooltip("自分のロボット。自分を見ないようにするために使う")]
public ServingRobotController owner;
const int MaxHits = 32;
readonly Collider[] _hits = new Collider[MaxHits];
///
/// この回で、前のセンサーの箱に入った当たりのいちばん多かった数(依頼 v11 の 5 章)。
/// MaxHits(32)に届いていなければ切り捨てが起きていないので、
/// 「物理の返る順番」はずれの元から外せる。ExperimentRunner が回し始めに 0 に戻す
///
public static int MaxSeen;
/// 切り捨てが起きた回数(0 のはず)
public static int Overflows;
Reading _last;
public Reading Last { get { return _last; } }
/// いまの前の様子を見る
public Reading Sense()
{
Reading r = new Reading();
if (spec == null)
{
_last = r;
return r;
}
r.nearestDistance = spec.sensorRange;
r.nearestName = "";
float radius = spec.Radius;
Vector3 fwd = transform.forward;
Vector3 eye = transform.position + Vector3.up * (spec.sensorHeight * 0.5f);
Vector3 center = eye + fwd * (radius + spec.sensorRange * 0.5f);
Vector3 half = new Vector3(spec.sensorWidth * 0.5f, spec.sensorHeight * 0.5f, spec.sensorRange * 0.5f);
int n = Physics.OverlapBoxNonAlloc(center, half, _hits, transform.rotation, ~0, QueryTriggerInteraction.Ignore);
// 依頼 v11 の 5 章:当たりが MaxHits を超えると切り捨てになり、
// どれが落ちるかは物理の返す順番で決まる(=順番に頼る所)。
// 実際に超えているかを数えて summary.json に出す。
// 超えていなければ「物理の返る順番」はずれの元から外せる
if (n > MaxSeen) MaxSeen = n;
if (n > MaxHits) { n = MaxHits; Overflows++; }
for (int i = 0; i < n; i++)
{
Collider col = _hits[i];
if (col == null) continue;
PersonBody person = col.GetComponentInParent();
ServingRobotController other = null;
Chair chair = null;
if (person == null)
{
other = col.GetComponentInParent();
if (other != null && owner != null && other == owner) continue; // 自分は見ない
if (other == null) chair = col.GetComponentInParent();
}
if (person == null && other == null && chair == null) continue; // 床・壁・卓は見ない
r.blocked = true;
if (person != null) r.sawPerson = true;
if (other != null) r.sawRobot = true;
if (chair != null) r.sawChair = true;
// ロボットの外周からの距離
Vector3 closest = col.ClosestPoint(eye);
Vector3 flat = closest - transform.position;
flat.y = 0f;
float d = Mathf.Max(0f, Vector3.Dot(flat, fwd) - radius);
if (d >= r.nearestDistance) continue;
r.nearestDistance = d;
r.nearestIsPerson = person != null;
r.nearestIsRobot = other != null;
r.nearestIsChair = chair != null;
if (person != null)
{
r.nearestPersonKind = person.kind;
r.nearestPersonSpeed = person.Speed;
r.nearestPersonStanding = person.IsStanding;
r.nearestName = person.name;
}
else if (other != null) r.nearestName = other.name;
else if (chair != null) r.nearestName = chair.transform.parent != null
? chair.transform.parent.name + "/" + chair.name : chair.name;
}
_last = r;
return r;
}
void OnDrawGizmosSelected()
{
if (spec == null) return;
Vector3 fwd = transform.forward;
Vector3 center = transform.position
+ Vector3.up * (spec.sensorHeight * 0.5f)
+ fwd * (spec.Radius + spec.sensorRange * 0.5f);
Gizmos.color = _last.blocked ? new Color(1f, 0.3f, 0.2f, 0.6f) : new Color(0.3f, 1f, 0.4f, 0.4f);
Matrix4x4 old = Gizmos.matrix;
Gizmos.matrix = Matrix4x4.TRS(center, transform.rotation, Vector3.one);
Gizmos.DrawWireCube(Vector3.zero, new Vector3(spec.sensorWidth, spec.sensorHeight, spec.sensorRange));
Gizmos.matrix = old;
}
}
}