新增SensorToolkit

This commit is contained in:
2026-05-23 08:48:48 +08:00
parent 9369f512d1
commit 81c326af53
557 changed files with 186698 additions and 137 deletions

View File

@@ -0,0 +1,126 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
[Serializable]
public struct Accumulator<REF, T> : IEquatable<Accumulator<REF, T>>
where REF : UnityEngine.Object
where T : IAccumulated<REF, T> {
[SerializeField] REF outputTarget;
public REF OutputTarget => outputTarget;
[SerializeField] List<REF> inputTargets;
public List<REF> InputTargets => inputTargets;
[SerializeField] List<T> inputs;
public List<T> Inputs => inputs;
[SerializeField] T output;
public T RawOutput => output;
[SerializeField] bool isDirty;
public T PreviousOutput { get; private set; }
int Timestamp;
public void Initialize() {
Timestamp = -1;
inputTargets = new List<REF>();
inputs = new List<T>();
}
public T Output {
get {
if (isDirty) {
Combine();
isDirty = false;
}
return output;
}
}
public void Spawn(REF target, int timestamp) {
Timestamp = timestamp;
outputTarget = target;
isDirty = true;
}
public void Dispose() {
inputs.Clear();
outputTarget = null;
PreviousOutput = default;
output = default(T);
isDirty = false;
}
public bool UpdateInput(REF target, T input, int timestamp) {
if (TryGetInput(target, out var found, out var index)) {
if (found.Equals(input)) {
return false;
}
SetTimestamp(timestamp);
inputs[index] = input;
isDirty = true;
return true;
} else {
SetTimestamp(timestamp);
inputs.Add(input);
inputTargets.Add(target);
isDirty = true;
return true;
}
}
public bool RemoveInput(REF target, int timestamp) {
if (TryGetInput(target, out var found, out var index)) {
SetTimestamp(timestamp);
inputs.RemoveAt(index);
inputTargets.RemoveAt(index);
isDirty = true;
return true;
}
return false;
}
public bool Equals(Accumulator<REF, T> other) {
return outputTarget == other.outputTarget;
}
public override int GetHashCode() {
return outputTarget.GetHashCode();
}
void SetTimestamp(int timestamp) {
if (Timestamp != timestamp) {
Timestamp = timestamp;
PreviousOutput = Output;
}
}
bool TryGetInput(REF target, out T input, out int index) {
for (int i = 0; i < inputTargets.Count; i++) {
var t = inputTargets[i];
if (ReferenceEquals(t, target)) {
input = inputs[i];
index = i;
return true;
}
}
input = default(T);
index = -1;
return false;
}
void Combine() {
bool isFirst = true;
foreach (var input in inputs) {
if (isFirst) {
output = input;
isFirst = false;
} else {
output = output.Combine(input);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fe0121fbe93c42545969b8c96860bb57
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,415 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
[Serializable]
public abstract class AccumulatorPipeline<REF, T> : ISerializationCallbackReceiver
where REF : UnityEngine.Object
where T : IAccumulated<REF, T> {
TargetsEnumerable targetsEnumerable;
public TargetsEnumerable OutputTargets {
get {
if (targetsEnumerable == null) {
targetsEnumerable = new TargetsEnumerable(this);
}
return targetsEnumerable;
}
}
OutputsEnumerable outputsEnumerable;
public OutputsEnumerable Outputs {
get {
if (outputsEnumerable == null) {
outputsEnumerable = new OutputsEnumerable(this);
}
return outputsEnumerable;
}
}
public delegate bool SignalProcessor(in T input, out T processed);
public SignalProcessor SignalProcessorCallback;
public event Action<T> OnAdd;
public event Action<T, T> OnChange;
public event Action<T> OnRemove;
public event Action OnSome;
public event Action OnNone;
Dictionary<REF, int> inputToMap = new Dictionary<REF, int>();
Dictionary<REF, int> outputToMap = new Dictionary<REF, int>();
[NonSerialized] Accumulator<REF, T>[] accumulators = new Accumulator<REF, T>[32];
[NonSerialized] int accumulatorCount = 0;
[NonSerialized] HashSet<REF> toRemove = new HashSet<REF>();
[NonSerialized] HashSet<Accumulator<REF, T>> changed = new HashSet<Accumulator<REF, T>>();
[NonSerialized] List<T> removed = new List<T>();
struct Event {
public enum Type { Add, Change, Remove }
public Type type;
public T target;
}
[NonSerialized] List<Event> eventQueue = new List<Event>();
int prevSignalCount = -1;
int timestamp = 0;
bool isPlayingEvents = false;
public T GetOutput(REF go) {
return accumulators[outputToMap[go]].Output;
}
public bool TryGetOutput(REF go, out T output) {
if (outputToMap.TryGetValue(go, out var i)) {
output = accumulators[i].Output;
return true;
}
output = default;
return false;
}
public List<REF> GetInputObjects(REF go, List<REF> storeIn) {
if (outputToMap.TryGetValue(go, out var i)) {
foreach (var input in accumulators[i].InputTargets) {
storeIn.Add(input);
}
}
return storeIn;
}
public bool ContainsOutput(REF go) {
return outputToMap.ContainsKey(go);
}
public void UpdateAllInputs(List<T> nextInputs) {
if (!AssertRecursionLimitNotReached()) {
return;
}
timestamp += 1;
toRemove.Clear();
foreach (var input in inputToMap) {
toRemove.Add(input.Key);
}
foreach (var signal in nextInputs) {
toRemove.Remove(signal.Object);
UpdateInputInternal(signal);
}
foreach (var remaining in toRemove) {
RemoveInputInternal(remaining);
}
SerializeEvents();
if (!isPlayingEvents) {
PlayEvents();
}
}
public void UpdateInput(T signal) {
if (!AssertRecursionLimitNotReached()) {
return;
}
timestamp += 1;
UpdateInputInternal(signal);
SerializeEvents();
if (!isPlayingEvents) {
PlayEvents();
}
}
public void RemoveInput(REF forObject) {
if (!AssertRecursionLimitNotReached()) {
return;
}
timestamp += 1;
RemoveInputInternal(forObject);
SerializeEvents();
if (!isPlayingEvents) {
PlayEvents();
}
}
public void OnBeforeSerialize() { }
public void OnAfterDeserialize() {
/*inputToMap.Clear();
outputToMap.Clear();
for (int i = 0; i < accumulatorCount; i++) {
var acc = accumulators[i];
outputToMap.Add(acc.OutputTarget, i);
foreach (var input in acc.InputTargets) {
inputToMap.Add(input, i);
}
}*/
}
int recursionCount = 0;
bool AssertRecursionLimitNotReached() {
if (!isPlayingEvents) {
recursionCount = 0;
return true;
}
recursionCount += 1;
if (recursionCount > 10) {
Debug.LogError("It appears that a sensor is being pulsed recursively from a detection event handler. This will be ignored to avoid a stack overflow. To avoid this error make sure that any detection event handlers are not causing a sensor to change its list of detections, and therefore firing a new sequence of events.");
return false;
}
return true;
}
void UpdateInputInternal(T signal) {
if (ReferenceEquals(signal.Object, null)) {
return;
}
var processed = signal;
if (SignalProcessorCallback?.Invoke(in signal, out processed) ?? true) {
UpdateProcessedInput(signal.Object, processed);
} else {
RemoveInputInternal(signal.Object);
}
}
void RemoveInputInternal(REF forObject) {
if (inputToMap.TryGetValue(forObject, out var accIndex)) {
RemoveInputFromMap(forObject, accIndex);
inputToMap.Remove(forObject);
}
}
void UpdateProcessedInput(REF inputTarget, T processed) {
if (inputToMap.TryGetValue(inputTarget, out var accIndex)) {
if (ReferenceEquals(accumulators[accIndex].OutputTarget, processed.Object)) {
if (accumulators[accIndex].UpdateInput(inputTarget, processed, timestamp)) {
OnChangedEvent(accumulators[accIndex]);
}
} else {
RemoveInputFromMap(inputTarget, accIndex);
NewProcessedInput(inputTarget, processed);
}
} else {
NewProcessedInput(inputTarget, processed);
}
}
void NewProcessedInput(REF inputTarget, T processed) {
if (!outputToMap.TryGetValue(processed.Object, out var accIndex)) {
var acc = accumulatorCache.Get();
acc.Spawn(processed.Object, timestamp);
// Add the accumulator to the array, resize if needed
accIndex = accumulatorCount;
if (accIndex == accumulators.Length) {
Array.Resize(ref accumulators, accumulators.Length * 2);
}
accumulators[accIndex] = acc;
accumulatorCount += 1;
outputToMap[acc.OutputTarget] = accIndex;
}
inputToMap[inputTarget] = accIndex;
if (accumulators[accIndex].UpdateInput(inputTarget, processed, timestamp)) {
OnChangedEvent(accumulators[accIndex]);
}
}
void RemoveInputFromMap(REF inputObject, int accIndex) {
if (accumulators[accIndex].RemoveInput(inputObject, timestamp)) {
if (accumulators[accIndex].Inputs.Count > 0) {
OnChangedEvent(accumulators[accIndex]);
} else {
OnRemovedEvent(accumulators[accIndex]);
outputToMap.Remove(accumulators[accIndex].OutputTarget);
accumulatorCache.Dispose(accumulators[accIndex]);
// Swap with last accumulator and remove
var lastIndex = accumulatorCount - 1;
if (accIndex != lastIndex) {
var lastAcc = accumulators[lastIndex];
accumulators[accIndex] = lastAcc;
outputToMap[lastAcc.OutputTarget] = accIndex;
foreach (var input in lastAcc.InputTargets) {
inputToMap[input] = accIndex;
}
}
accumulatorCount -= 1;
}
}
}
void OnChangedEvent(Accumulator<REF, T> signal) {
changed.Add(signal);
}
void OnRemovedEvent(Accumulator<REF, T> signal) {
changed.Remove(signal);
removed.Add(signal.PreviousOutput);
}
void SerializeEvents() {
foreach (var change in changed) {
var previousOutput = change.PreviousOutput;
if (ReferenceEquals(previousOutput.Object, null)) {
eventQueue.Add(new Event { type = Event.Type.Add, target = change.Output });
} else {
eventQueue.Add(new Event { type = Event.Type.Change, target = previousOutput });
eventQueue.Add(new Event { type = Event.Type.Change, target = change.Output });
}
}
foreach (var remove in removed) {
eventQueue.Add(new Event { type = Event.Type.Remove, target = remove });
}
changed.Clear();
removed.Clear();
}
void PlayEvents() {
isPlayingEvents = true;
try {
var eventIndex = 0;
while (eventIndex < eventQueue.Count) {
var evt = eventQueue[eventIndex];
switch (evt.type) {
case Event.Type.Add:
OnAdd?.Invoke(evt.target);
break;
case Event.Type.Change:
// We store the prevValue as current index. The next value in the next index.
eventIndex++;
var changeTo = eventQueue[eventIndex];
OnChange?.Invoke(evt.target, changeTo.target);
break;
case Event.Type.Remove:
OnRemove?.Invoke(evt.target);
break;
}
eventIndex += 1;
}
eventQueue.Clear();
var signalCount = Outputs.Count;
// prevSignalCount initialized to -1, so event always triggers on first update
if (prevSignalCount <= 0 && signalCount > 0) {
OnSome?.Invoke();
} else if (prevSignalCount != 0 && signalCount == 0) {
OnNone?.Invoke();
}
prevSignalCount = signalCount;
} finally {
isPlayingEvents = false;
}
}
AccumulatorCache accumulatorCache = new AccumulatorCache();
class AccumulatorCache : ObjectCache<Accumulator<REF, T>> {
public override void Dispose(Accumulator<REF, T> obj) {
obj.Dispose();
base.Dispose(obj);
}
protected override Accumulator<REF, T> create() {
var inst = base.create();
inst.Initialize();
return inst;
}
}
public class OutputsEnumerable : IEnumerable<T>, IEnumerable {
AccumulatorPipeline<REF, T> source;
public OutputsEnumerable(AccumulatorPipeline<REF, T> source) { this.source = source; }
public Enumerator GetEnumerator() { return new Enumerator(source); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
IEnumerator<T> IEnumerable<T>.GetEnumerator() { return GetEnumerator(); }
public int Count {
get {
int n = 0;
for (int i = 0; i < source.accumulatorCount; i++) {
if (source.accumulators[i].OutputTarget != null) {
n += 1;
}
}
return n;
}
}
public struct Enumerator : IEnumerator<T>, IEnumerator {
AccumulatorPipeline<REF, T> source;
int index;
public Enumerator(AccumulatorPipeline<REF, T> source) {
this.source = source;
index = -1;
}
public T Current { get { return source.accumulators[index].Output; } }
object IEnumerator.Current => throw new NotImplementedException();
public void Dispose() { }
public bool MoveNext() {
index += 1;
if (index >= source.accumulatorCount) {
return false;
}
if (Current.Object == null) {
return MoveNext();
}
return true;
}
public void Reset() { index = -1; }
}
}
public class TargetsEnumerable : IEnumerable<REF>, IEnumerable {
AccumulatorPipeline<REF, T> source;
public TargetsEnumerable(AccumulatorPipeline<REF, T> source) { this.source = source; }
public Enumerator GetEnumerator() { return new Enumerator(source); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
IEnumerator<REF> IEnumerable<REF>.GetEnumerator() { return GetEnumerator(); }
public int Count {
get {
int n = 0;
for (int i = 0; i < source.accumulatorCount; i++) {
if (source.accumulators[i].OutputTarget != null) {
n += 1;
}
}
return n;
}
}
public struct Enumerator : IEnumerator<REF>, IEnumerator {
AccumulatorPipeline<REF, T> source;
int index;
public Enumerator(AccumulatorPipeline<REF, T> source) {
this.source = source;
index = -1;
}
public REF Current { get { return source.accumulators[index].OutputTarget; } }
object IEnumerator.Current => throw new NotImplementedException();
public void Dispose() { }
public bool MoveNext() {
index += 1;
if (index >= source.accumulatorCount) {
return false;
}
if (Current == null) {
return MoveNext();
}
return true;
}
public void Reset() { index = -1; }
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 372a7af7ced857b46af861b11ad5b2a2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,97 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
public struct AngleEnumerator {
public enum AngleMethodType { Center, Origin, BoundingBox }
public enum SortByType { HorizontalAngle, CentralAngle }
public AngleMethodType AngleMethod;
public SortByType SortBy;
public List<AngleResult> results { get; private set; }
public static AngleEnumerator Create() => new AngleEnumerator {
results = new List<AngleResult>()
};
public void Clear() {
results.Clear();
}
public void Calculate(ReferenceFrame frame, FOVRange fov, List<Signal> signals) {
Clear();
fov.Distance *= fov.Distance;
foreach (var signal in signals) {
var angles =
AngleMethod == AngleMethodType.Origin ? frame.AngleTo(signal.Object.transform.position) :
AngleMethod == AngleMethodType.Center ? frame.AngleTo(signal.Bounds.center) :
AngleMethod == AngleMethodType.BoundingBox ? frame.AngleTo(signal.Bounds) :
default;
var quadrance = signal.Bounds.SqrDistance(frame.Position);
if (!fov.Contains(angles, quadrance)) {
continue;
}
var result = new AngleResult {
Object = signal.Object,
Angles = angles,
CentralAngle = angles.GetCentralAngle(),
Distance = quadrance
};
results.Add(result);
}
if (SortBy == SortByType.HorizontalAngle) {
results.Sort(AngleResult.CompareHorizAngle);
} else if (SortBy == SortByType.CentralAngle) {
results.Sort(AngleResult.CompareCentralAngle);
}
}
public void DrawGizmos() {
SensorGizmos.PushColor(Color.black);
int i = 0;
foreach (var result in results) {
SensorGizmos.Label(result.Object.transform.position, $"Index: {i}\n({result.Angles.HorizAngle.ToString("N1")},{result.Angles.VertAngle.ToString("N1")})");
i++;
}
SensorGizmos.PopColor();
}
public struct AngleResult {
public GameObject Object;
public ViewAngles Angles;
public float CentralAngle;
public float Distance;
public static int CompareCentralAngle(AngleResult r1, AngleResult r2) {
var angleDiff = r1.CentralAngle - r2.CentralAngle;
if (angleDiff != 0f) {
return angleDiff > 0f ? 1 : -1;
}
var distanceDiff = r1.Distance - r2.Distance;
if (distanceDiff != 0f) {
return distanceDiff > 0f ? 1 : -1;
}
return 0;
}
public static int CompareHorizAngle(AngleResult r1, AngleResult r2) {
//var a1 = r1.Coords.HorizAngle >= 0 ? r1.Coords.HorizAngle : 360f + r1.Coords.HorizAngle;
//var a2 = r2.Coords.HorizAngle >= 0 ? r2.Coords.HorizAngle : 360f + r2.Coords.HorizAngle;
var a1 = r1.Angles.HorizAngle;
var a2 = r2.Angles.HorizAngle;
var angleDiff = a1 - a2;
if (angleDiff != 0f) {
return angleDiff > 0f ? 1 : -1;
}
var distanceDiff = r1.Distance - r2.Distance;
if (distanceDiff != 0f) {
return distanceDiff > 0f ? 1 : -1;
}
return 0;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e77fc9afd958c154c9e4cfe6dc9067ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,202 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit
{
/*
* Common functionality for 2D sensors that detect colliders within an area such as the Range2D and Trigger2D Sensors.
*/
public abstract class BaseAreaSensor : Sensor {
#region Configurations
[SerializeField]
SignalFilter signalFilter = new SignalFilter();
[Tooltip("In Collider mode the sensor detects GameObjects attached to colliders. In RigidBody mode it detects the RigidBody GameObject attached to colliders.")]
public DetectionModes DetectionMode;
#endregion
#region Public
// Edit the IgnoreList at runtime. Anything in the list will not be detected
public List<GameObject> IgnoreList => signalFilter.IgnoreList;
// Enable/Disable the tag filtering at runtime
public bool EnableTagFilter {
get => signalFilter.EnableTagFilter;
set => signalFilter.EnableTagFilter = value;
}
// Change the allowed tags at runtime
public string[] AllowedTags {
get => signalFilter.AllowedTags;
set => signalFilter.AllowedTags = value;
}
public override void Clear() {
base.Clear();
ClearColliders();
}
#endregion
#region Protected
protected override List<Collider2D> GetInputColliders(GameObject inputObject, List<Collider2D> storeIn) {
List<Collider2D> clist;
if (gameObjectColliders.TryGetValue(inputObject, out clist)) {
foreach (var c in clist) {
storeIn.Add(c);
}
}
return storeIn;
}
protected override void InitialiseSignalProcessors() {
base.InitialiseSignalProcessors();
MapToSignalProxy.Configure(true);
SignalFilter = signalFilter;
}
protected void UpdateAllSignals() {
workList.Clear();
foreach (var cols in gameObjectColliders) {
Signal signal;
if (CalculateSignal(cols.Value, out signal)) {
workList.Add(signal);
}
}
MapToRigidBody.Configure(DetectionMode, true);
UpdateAllSignals(workList);
}
protected void AddCollider(Collider2D c, bool updateSignal) {
var cols = AddColliderToMap(c, c.gameObject, gameObjectColliders);
if (!updateSignal) {
return;
}
MapToRigidBody.Configure(DetectionMode, true);
Signal signal;
if (CalculateSignal(cols, out signal)) {
UpdateSignalImmediate(signal);
}
}
protected void RemoveCollider(Collider2D c, bool updateSignal) {
if (c == null) {
ClearDestroyedGameObjects();
return;
}
var cols = RemoveColliderFromMap(c, c.gameObject, gameObjectColliders);
if (!updateSignal) {
return;
}
MapToRigidBody.Configure(DetectionMode, true);
if (cols == null) {
LostSignalImmediate(c.gameObject);
} else {
Signal signal;
if (CalculateSignal(cols, out signal)) {
UpdateSignalImmediate(signal);
}
}
}
protected void ClearColliders() {
foreach (var set in gameObjectColliders) {
colliderListCache.Dispose(set.Value);
}
gameObjectColliders.Clear();
}
List<Collider2D> colliderList = new List<Collider2D>();
protected List<Collider2D> GetColliders() {
colliderList.Clear();
foreach (var set in gameObjectColliders) {
foreach (var collider in set.Value) {
colliderList.Add(collider);
}
}
return colliderList;
}
#endregion
#region Internals
// Maps a GameObject to a list of it's colliders that have been detected.
Dictionary<GameObject, List<Collider2D>> gameObjectColliders = new Dictionary<GameObject, List<Collider2D>>();
// List of temporary values for modifying collections
List<GameObject> gameObjectList = new List<GameObject>();
List<Signal> workList = new List<Signal>();
static ListCache<Collider2D> colliderListCache = new ListCache<Collider2D>();
void ClearDestroyedGameObjects() {
gameObjectList.Clear();
foreach (var set in gameObjectColliders) {
if (set.Key == null) {
gameObjectList.Add(set.Key);
}
}
foreach (var go in gameObjectList) {
colliderListCache.Dispose(gameObjectColliders[go]);
gameObjectColliders.Remove(go);
}
}
List<Collider2D> AddColliderToMap(Collider2D c, GameObject go, Dictionary<GameObject, List<Collider2D>> dict) {
List<Collider2D> colliderList;
if (!dict.TryGetValue(go, out colliderList)) {
colliderList = colliderListCache.Get();
dict[go] = colliderList;
}
if (!colliderList.Contains(c)) {
colliderList.Add(c);
}
return colliderList;
}
List<Collider2D> RemoveColliderFromMap(Collider2D c, GameObject go, Dictionary<GameObject, List<Collider2D>> dict) {
List<Collider2D> colliderList = null;
if (dict.TryGetValue(go, out colliderList)) {
colliderList.Remove(c);
if (colliderList.Count == 0) {
dict.Remove(go);
colliderListCache.Dispose(colliderList);
colliderList = null;
}
}
return colliderList;
}
bool CalculateSignal(List<Collider2D> colliders, out Signal signal) {
signal = default;
Bounds bounds = new Bounds();
bool anyFound = false;
foreach (var c in colliders) {
if (!signalFilter.TestCollider(c)) {
continue;
}
if (!anyFound) {
bounds = c.bounds;
anyFound = true;
} else {
bounds.Encapsulate(c.bounds);
}
}
var obj = colliders[0].gameObject;
signal = new Signal {
Object = obj,
Shape = new Bounds(bounds.center - obj.transform.position, bounds.size),
Strength = 1f
};
return anyFound;
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2d2980a7f0c34d2199b27bb0ec29c6ff
timeCreated: 1491308888
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,82 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
public abstract class BasePulsableSensor : MonoBehaviour {
// User-defined description for this sensor.
// Enable in SensorToolkit/Settings to view in inspectors
[SerializeField] string comment;
PulseHandle runningPulse;
PulseHandle pendingPulse;
// Should cause the sensor to perform it's 'sensing' routine, so that its list of detected objects
// is up to date at the time of calling. Each sensor can be configured to pulse automatically at
// fixed intervals or each timestep, however, if you need more control over when this occurs then
// you can call this method manually.
public void Pulse() {
if (!isActiveAndEnabled) {
return;
}
if (!runningPulse.TryCancel()) {
runningPulse.Complete();
}
if (!pendingPulse.TryCancel()) {
pendingPulse.Complete();
}
GetPulseJob().Run();
}
// If this sensor has input sensors, then the inputs are pulsed first and then this one is pulsed.
public abstract void PulseAll();
public PulseHandle SchedulePulse() {
if (!isActiveAndEnabled) {
return default;
}
if (runningPulse.IsCompleted && !pendingPulse.IsCompleted) {
runningPulse = pendingPulse;
pendingPulse = default;
}
if (!runningPulse.IsCompleted) {
if (!pendingPulse.IsCompleted) {
return pendingPulse;
}
pendingPulse = GetPulseJob().Schedule(runningPulse);
pendingPulse.Tick();
return pendingPulse;
}
runningPulse = GetPulseJob().Schedule();
runningPulse.Tick();
return runningPulse;
}
public abstract event System.Action OnPulsed;
public abstract void Clear();
public bool ShowDetectionGizmos { get; set; }
protected abstract PulseJob GetPulseJob();
protected void ClearPendingPulse() {
if (!runningPulse.TryCancel()) {
runningPulse.Complete();
}
if (!pendingPulse.TryCancel()) {
pendingPulse.Complete();
}
}
protected virtual void OnDisable() {
ClearPendingPulse();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7129a068598c426bb92759e418b30fdb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,202 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
/*
* Common functionality for sensors that detect colliders within a volume such as the Range and Trigger Sensors.
*/
public abstract class BaseVolumeSensor : Sensor {
#region Configurations
[SerializeField]
SignalFilter signalFilter = new SignalFilter();
[Tooltip("In Collider mode the sensor detects GameObjects attached to colliders. In RigidBody mode it detects the RigidBody GameObject attached to colliders.")]
public DetectionModes DetectionMode;
#endregion
#region Public
// Edit the IgnoreList at runtime. Anything in the list will not be detected
public List<GameObject> IgnoreList => signalFilter.IgnoreList;
// Enable/Disable the tag filtering at runtime
public bool EnableTagFilter {
get => signalFilter.EnableTagFilter;
set => signalFilter.EnableTagFilter = value;
}
// Change the allowed tags at runtime
public string[] AllowedTags {
get => signalFilter.AllowedTags;
set => signalFilter.AllowedTags = value;
}
public override void Clear() {
base.Clear();
ClearColliders();
}
#endregion
#region Protected
protected override List<Collider> GetInputColliders(GameObject inputObject, List<Collider> storeIn) {
List<Collider> clist;
if (gameObjectColliders.TryGetValue(inputObject, out clist)) {
foreach (var c in clist) {
storeIn.Add(c);
}
}
return storeIn;
}
protected override void InitialiseSignalProcessors() {
base.InitialiseSignalProcessors();
MapToSignalProxy.Configure(true);
SignalFilter = signalFilter;
}
protected void UpdateAllSignals() {
workList.Clear();
foreach (var cols in gameObjectColliders) {
Signal signal;
if (CalculateSignal(cols.Value, out signal)) {
workList.Add(signal);
}
}
MapToRigidBody.Configure(DetectionMode, false);
UpdateAllSignals(workList);
}
protected void AddCollider(Collider c, bool updateSignal) {
var cols = AddColliderToMap(c, c.gameObject, gameObjectColliders);
if (!updateSignal) {
return;
}
MapToRigidBody.Configure(DetectionMode, false);
Signal signal;
if (CalculateSignal(cols, out signal)) {
UpdateSignalImmediate(signal);
}
}
protected void RemoveCollider(Collider c, bool updateSignal) {
if (c == null) {
ClearDestroyedGameObjects();
return;
}
var cols = RemoveColliderFromMap(c, c.gameObject, gameObjectColliders);
if (!updateSignal) {
return;
}
MapToRigidBody.Configure(DetectionMode, false);
if (cols == null) {
LostSignalImmediate(c.gameObject);
} else {
Signal signal;
if (CalculateSignal(cols, out signal)) {
UpdateSignalImmediate(signal);
}
}
}
protected void ClearColliders() {
foreach (var set in gameObjectColliders) {
colliderListCache.Dispose(set.Value);
}
gameObjectColliders.Clear();
}
List<Collider> colliderList = new List<Collider>();
protected List<Collider> GetColliders() {
colliderList.Clear();
foreach (var set in gameObjectColliders) {
foreach (var collider in set.Value) {
colliderList.Add(collider);
}
}
return colliderList;
}
#endregion
#region Internals
// Maps a GameObject to a list of it's colliders that have been detected.
List<Collider> colliders = new List<Collider>();
Dictionary<GameObject, List<Collider>> gameObjectColliders = new Dictionary<GameObject, List<Collider>>();
// List of temporary values for modifying collections
List<GameObject> gameObjectList = new List<GameObject>();
List<Signal> workList = new List<Signal>();
static ListCache<Collider> colliderListCache = new ListCache<Collider>();
void ClearDestroyedGameObjects() {
gameObjectList.Clear();
foreach (var set in gameObjectColliders) {
if (set.Key == null) {
gameObjectList.Add(set.Key);
}
}
foreach (var go in gameObjectList) {
colliderListCache.Dispose(gameObjectColliders[go]);
gameObjectColliders.Remove(go);
}
}
List<Collider> AddColliderToMap(Collider c, GameObject go, Dictionary<GameObject, List<Collider>> dict) {
List<Collider> colliderList;
if (!dict.TryGetValue(go, out colliderList)) {
colliderList = colliderListCache.Get();
dict[go] = colliderList;
}
if (!colliderList.Contains(c)) {
colliderList.Add(c);
}
return colliderList;
}
List<Collider> RemoveColliderFromMap(Collider c, GameObject go, Dictionary<GameObject, List<Collider>> dict) {
List<Collider> colliderList = null;
if (dict.TryGetValue(go, out colliderList)) {
colliderList.Remove(c);
if (colliderList.Count == 0) {
dict.Remove(go);
colliderListCache.Dispose(colliderList);
colliderList = null;
}
}
return colliderList;
}
bool CalculateSignal(List<Collider> colliders, out Signal signal) {
signal = default;
Bounds bounds = new Bounds();
bool anyFound = false;
foreach (var c in colliders) {
if (!signalFilter.TestCollider(c)) {
continue;
}
if (!anyFound) {
bounds = c.bounds;
anyFound = true;
} else {
bounds.Encapsulate(c.bounds);
}
}
var obj = colliders[0].gameObject;
var position = obj.transform.position;
signal = new Signal {
Object = obj,
Shape = new Bounds(bounds.center - position, bounds.size),
Strength = 1f
};
return anyFound;
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d62bea7337d540409a9678c0a6e8f081
timeCreated: 1489882031
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using UnityEngine;
using System.Collections.Generic;
namespace Micosmo.SensorToolkit {
public interface ITestNonAlloc<S, T> where S : Sensor {
int Test(S sensor, T[] results);
}
public class PhysicsNonAlloc<T> {
public static int InitialSize = 20;
public static bool DynamicallyIncreaseBufferSize = true;
public T[] Buffer { get; private set; }
public int Count { get; private set; }
public bool IsAtCapacity { get { return Count == Buffer.Length; } }
public PhysicsNonAlloc() {
Expand(Mathf.Max(InitialSize, 1));
}
public int PerformTest<S>(S sensor, ITestNonAlloc<S, T> tester) where S : Sensor {
Count = tester.Test(sensor, Buffer);
if (Count == Buffer.Length && DynamicallyIncreaseBufferSize) {
Expand(Count * 2);
return PerformTest(sensor, tester);
}
return Count;
}
void Expand(int toSize) {
Buffer = new T[toSize];
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1514ea5919444691b9b9cde7cb0cf94a
timeCreated: 1601161652
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,156 @@
using UnityEngine;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Micosmo.SensorToolkit {
/// <summary>
/// Draws the field/property ONLY if the compared property compared by the comparison type with the value of comparedValue returns true.
/// Based on: https://forum.unity.com/threads/draw-a-field-only-if-a-condition-is-met.448855/
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
internal class DrawIfAttribute : PropertyAttribute {
#region Fields
public string comparedPropertyName { get; private set; }
public object comparedValue { get; private set; }
public DisablingType disablingType { get; private set; }
/// <summary>
/// Types of comperisons.
/// </summary>
public enum DisablingType {
ReadOnly = 2,
DontDraw = 3
}
#endregion
/// <summary>
/// Only draws the field only if a condition is met. Supports enum and bools.
/// </summary>
/// <param name="comparedPropertyName">The name of the property that is being compared (case sensitive).</param>
/// <param name="comparedValue">The value the property is being compared to.</param>
/// <param name="disablingType">The type of disabling that should happen if the condition is NOT met. Defaulted to DisablingType.DontDraw.</param>
public DrawIfAttribute(string comparedPropertyName, object comparedValue, DisablingType disablingType = DisablingType.DontDraw) {
this.comparedPropertyName = comparedPropertyName;
this.comparedValue = comparedValue;
this.disablingType = disablingType;
}
}
}
#if UNITY_EDITOR
namespace Micosmo.SensorToolkit.Editors {
/// <summary>
/// Based on: https://forum.unity.com/threads/draw-a-field-only-if-a-condition-is-met.448855/
/// </summary>
[CustomPropertyDrawer(typeof(DrawIfAttribute))]
public class DrawIfPropertyDrawer : PropertyDrawer {
#region Fields
// Reference to the attribute on the property.
DrawIfAttribute drawIf;
// Field that is being compared.
SerializedProperty comparedField;
#endregion
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
if (!ShowMe(property) && drawIf.disablingType == DrawIfAttribute.DisablingType.DontDraw) {
return -EditorGUIUtility.standardVerticalSpacing;
} else {
if (property.propertyType == SerializedPropertyType.Generic) {
int numChildren = 0;
float totalHeight = 0.0f;
var children = property.GetEnumerator();
while (children.MoveNext()) {
SerializedProperty child = children.Current as SerializedProperty;
GUIContent childLabel = new GUIContent(child.displayName);
totalHeight += EditorGUI.GetPropertyHeight(child, childLabel) + EditorGUIUtility.standardVerticalSpacing;
numChildren++;
}
// Remove extra space at end, (we only want spaces between items)
totalHeight -= EditorGUIUtility.standardVerticalSpacing;
return totalHeight;
}
return EditorGUI.GetPropertyHeight(property, label);
}
}
/// <summary>
/// Errors default to showing the property.
/// </summary>
private bool ShowMe(SerializedProperty property) {
drawIf = attribute as DrawIfAttribute;
// Replace propertyname to the value from the parameter
string path = property.propertyPath.Contains(".") ? System.IO.Path.ChangeExtension(property.propertyPath, drawIf.comparedPropertyName) : drawIf.comparedPropertyName;
comparedField = property.serializedObject.FindProperty(path);
if (comparedField == null) {
Debug.LogError("Cannot find property with name: " + path);
return true;
}
// get the value & compare based on types
switch (comparedField.type) { // Possible extend cases to support your own type
case "bool":
return comparedField.boolValue.Equals(drawIf.comparedValue);
case "Enum":
return comparedField.enumValueIndex.Equals((int)drawIf.comparedValue);
default:
Debug.LogError("Error: " + comparedField.type + " is not supported of " + path);
return true;
}
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
// If the condition is met, simply draw the field.
if (ShowMe(property)) {
// A Generic type means a custom class...
if (property.propertyType == SerializedPropertyType.Generic) {
var children = property.GetEnumerator();
Rect offsetPosition = position;
while (children.MoveNext()) {
SerializedProperty child = children.Current as SerializedProperty;
GUIContent childLabel = new GUIContent(child.displayName);
float childHeight = EditorGUI.GetPropertyHeight(child, childLabel);
offsetPosition.height = childHeight;
EditorGUI.PropertyField(offsetPosition, child, childLabel);
offsetPosition.y += childHeight + EditorGUIUtility.standardVerticalSpacing;
}
} else {
EditorGUI.PropertyField(position, property, label);
}
} //...check if the disabling type is read only. If it is, draw it disabled
else if (drawIf.disablingType == DrawIfAttribute.DisablingType.ReadOnly) {
GUI.enabled = false;
EditorGUI.PropertyField(position, property, label);
GUI.enabled = true;
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 258e14d7ad06f12429ff0ebf28166fcd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 259cbc14df0e41919b6fa5f3c0020f64
folderAsset: yes
timeCreated: 1488607388
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,127 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(ArcSensor2D))]
[CanEditMultipleObjects]
public class ArcSensor2DEditor : BaseSensorEditor<ArcSensor2D> {
SerializedProperty parameterisation;
SerializedProperty bezier;
SerializedProperty ballistic;
SerializedProperty ignoreList;
SerializedProperty tagFilterEnabled;
SerializedProperty tagFilter;
SerializedProperty detectsOnLayers;
SerializedProperty detectionMode;
SerializedProperty ignoreTriggerColliders;
SerializedProperty signalProcessors;
SerializedProperty obstructedByLayers;
SerializedProperty worldSpace;
SerializedProperty pulseMode;
SerializedProperty pulseUpdateFunction;
SerializedProperty pulseInterval;
SerializedProperty onDetected;
SerializedProperty onLostDetection;
SerializedProperty onSomeDetection;
SerializedProperty onNoDetection;
SerializedProperty onObstructed;
SerializedProperty onClear;
bool showEvents = false;
protected override bool canTest => true;
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) return;
parameterisation = serializedObject.FindProperty("Parameterisation");
bezier = serializedObject.FindProperty("Bezier");
ballistic = serializedObject.FindProperty("Ballistic");
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
detectsOnLayers = serializedObject.FindProperty("DetectsOnLayers");
detectionMode = serializedObject.FindProperty("DetectionMode");
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
signalProcessors = serializedObject.FindProperty("signalProcessors");
obstructedByLayers = serializedObject.FindProperty("ObstructedByLayers");
worldSpace = serializedObject.FindProperty("WorldSpace");
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
onDetected = serializedObject.FindProperty("OnDetected");
onLostDetection = serializedObject.FindProperty("OnLostDetection");
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
onNoDetection = serializedObject.FindProperty("OnNoDetection");
onObstructed = serializedObject.FindProperty("onObstruction");
onClear = serializedObject.FindProperty("onClear");
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(parameterisation);
if (sensor.Parameterisation == ArcSensor2D.ParameterisationType.Bezier) {
EditorUtils.InlinePropertyField(bezier);
} else if (sensor.Parameterisation == ArcSensor2D.ParameterisationType.Ballistic) {
EditorUtils.InlinePropertyField(ballistic);
}
EditorGUILayout.PropertyField(worldSpace);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(ignoreList, true);
EditorGUILayout.PropertyField(tagFilterEnabled);
if (tagFilterEnabled.boolValue) {
EditorGUILayout.PropertyField(tagFilter, true);
}
EditorGUILayout.PropertyField(detectsOnLayers);
EditorGUILayout.PropertyField(detectionMode);
EditorGUILayout.PropertyField(ignoreTriggerColliders);
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
EditorGUILayout.Space();
EditorGUILayout.PropertyField(obstructedByLayers);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
EditorGUILayout.PropertyField(pulseUpdateFunction);
}
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
}
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onDetected);
EditorGUILayout.PropertyField(onLostDetection);
EditorGUILayout.PropertyField(onSomeDetection);
EditorGUILayout.PropertyField(onNoDetection);
EditorGUILayout.PropertyField(onObstructed);
EditorGUILayout.PropertyField(onClear);
}
EditorGUILayout.Space();
BufferSizeInfo(sensor.CurrentBufferSize);
}
protected override void InspectorDetectedObjects() {
base.InspectorDetectedObjects();
if (!sensor.IsObstructed) return;
EditorGUILayout.Space();
EditorGUILayout.LabelField("Arc is Obstructed", new GUIStyle() { fontStyle = FontStyle.Bold, normal = new GUIStyleState() { textColor = STPrefs.RedEditorTextColour } });
DetectedObjectFieldLayout(sensor.GetObstructionRayHit().GameObject);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3836000b75c34e6a94ea1d518fbef050
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,128 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(ArcSensor))]
[CanEditMultipleObjects]
public class ArcSensorEditor : BaseSensorEditor<ArcSensor> {
SerializedProperty parameterisation;
SerializedProperty bezier;
SerializedProperty ballistic;
SerializedProperty ignoreList;
SerializedProperty tagFilterEnabled;
SerializedProperty tagFilter;
SerializedProperty detectsOnLayers;
SerializedProperty detectionMode;
SerializedProperty ignoreTriggerColliders;
SerializedProperty signalProcessors;
SerializedProperty obstructedByLayers;
SerializedProperty worldSpace;
SerializedProperty pulseMode;
SerializedProperty pulseUpdateFunction;
SerializedProperty pulseInterval;
SerializedProperty onDetected;
SerializedProperty onLostDetection;
SerializedProperty onSomeDetection;
SerializedProperty onNoDetection;
SerializedProperty onObstructed;
SerializedProperty onClear;
bool showEvents = false;
protected override bool canTest => true;
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) return;
parameterisation = serializedObject.FindProperty("Parameterisation");
bezier = serializedObject.FindProperty("Bezier");
ballistic = serializedObject.FindProperty("Ballistic");
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
detectsOnLayers = serializedObject.FindProperty("DetectsOnLayers");
detectionMode = serializedObject.FindProperty("DetectionMode");
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
signalProcessors = serializedObject.FindProperty("signalProcessors");
obstructedByLayers = serializedObject.FindProperty("ObstructedByLayers");
worldSpace = serializedObject.FindProperty("WorldSpace");
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
onDetected = serializedObject.FindProperty("OnDetected");
onLostDetection = serializedObject.FindProperty("OnLostDetection");
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
onNoDetection = serializedObject.FindProperty("OnNoDetection");
onObstructed = serializedObject.FindProperty("onObstruction");
onClear = serializedObject.FindProperty("onClear");
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(parameterisation);
if (sensor.Parameterisation == ArcSensor.ParameterisationType.Bezier) {
EditorUtils.InlinePropertyField(bezier);
} else if (sensor.Parameterisation == ArcSensor.ParameterisationType.Ballistic) {
EditorUtils.InlinePropertyField(ballistic);
}
EditorGUILayout.PropertyField(worldSpace);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(ignoreList, true);
EditorGUILayout.PropertyField(tagFilterEnabled);
if (tagFilterEnabled.boolValue) {
EditorGUILayout.PropertyField(tagFilter, true);
}
EditorGUILayout.PropertyField(detectsOnLayers);
EditorGUILayout.PropertyField(detectionMode);
EditorGUILayout.PropertyField(ignoreTriggerColliders);
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
EditorGUILayout.Space();
EditorGUILayout.PropertyField(obstructedByLayers);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
EditorGUILayout.PropertyField(pulseUpdateFunction);
}
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
}
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onDetected);
EditorGUILayout.PropertyField(onLostDetection);
EditorGUILayout.PropertyField(onSomeDetection);
EditorGUILayout.PropertyField(onNoDetection);
EditorGUILayout.PropertyField(onObstructed);
EditorGUILayout.PropertyField(onClear);
}
EditorGUILayout.Space();
BufferSizeInfo(sensor.CurrentBufferSize);
}
protected override void InspectorDetectedObjects() {
base.InspectorDetectedObjects();
if (!sensor.IsObstructed) return;
EditorGUILayout.Space();
EditorGUILayout.LabelField("Arc is Obstructed", new GUIStyle() { fontStyle = FontStyle.Bold, normal = new GUIStyleState() { textColor = STPrefs.RedEditorTextColour } });
DetectedObjectFieldLayout(sensor.GetObstructionRayHit().GameObject);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b59d8dc7c6f475dbafc42d1973b84aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,142 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors {
public static class EditorState {
public static event System.Action OnStopTesting;
public static Observable<BasePulsableSensor> ActivePulsable = new Observable<BasePulsableSensor>();
public static void StopAllTesting() {
if (EditorApplication.isPlaying || EditorApplication.isPaused) {
return;
}
OnStopTesting?.Invoke();
ActivePulsable.Value = null;
}
[InitializeOnEnterPlayMode]
static void OnEnterPlayMode(EnterPlayModeOptions options) {
if (options.HasFlag(EnterPlayModeOptions.DisableDomainReload)) {
StopAllTesting();
}
}
}
public abstract class BasePulsableEditor<T> : Editor where T : BasePulsableSensor {
T pulsable;
protected bool IsTesting = false;
protected bool IsActivePulsable => EditorState.ActivePulsable.Value == pulsable;
protected abstract bool canTest { get; }
bool isInGame => EditorApplication.isPlaying || EditorApplication.isPaused;
protected bool showDetections => isInGame || IsTesting;
SerializedProperty comment;
protected virtual void OnEnable() {
if (serializedObject == null) {
return;
}
pulsable = serializedObject.targetObject as T;
pulsable.OnPulsed += OnPulsedHandler;
EditorState.OnStopTesting += OnStopTestingHandler;
EditorState.ActivePulsable.OnChanged += ActivePulsableChangedHandler;
if ((EditorApplication.isPlaying || EditorApplication.isPaused) && EditorState.ActivePulsable.Value == null) {
EditorState.ActivePulsable.Value = pulsable;
}
comment = serializedObject.FindProperty("comment");
}
protected virtual void OnDisable() {
EditorState.StopAllTesting();
if (IsActivePulsable) {
EditorState.ActivePulsable.Value = null;
}
pulsable.OnPulsed -= OnPulsedHandler;
EditorState.OnStopTesting -= OnStopTestingHandler;
EditorState.ActivePulsable.OnChanged -= ActivePulsableChangedHandler;
}
public override void OnInspectorGUI() {
var mb = pulsable as MonoBehaviour;
if (mb != null && mb.transform.hasChanged) {
EditorState.StopAllTesting();
mb.transform.hasChanged = false;
}
serializedObject.Update();
var rect = EditorGUILayout.BeginVertical();
rect.xMin -= 12; rect.xMax += 2;
if (IsActivePulsable) {
DrawActive(rect);
}
if (STPrefs.ShowUserComments) {
EditorGUILayout.PropertyField(comment);
}
OnPulsableGUI();
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
if (showDetections && !IsActivePulsable) {
if (GUILayout.Button("Show Gizmos", GUILayout.Width(100))) {
EditorState.ActivePulsable.Value = pulsable;
}
}
if (canTest && !isInGame) {
if (GUILayout.Button("Test", GUILayout.Width(100))) {
StartTesting();
}
}
EditorGUILayout.EndHorizontal();
}
void DrawActive(Rect rect) {
var colour = STPrefs.ActiveSensorEditorColour;
EditorGUI.DrawRect(rect, colour);
}
protected abstract void OnPulsableGUI();
void OnPulsedHandler() {
Repaint();
if (Application.isPlaying || pulsable == null) {
return;
}
IsTesting = true;
SceneView.RepaintAll();
}
void StartTesting() {
if (Application.isPlaying || pulsable == null) {
return;
}
if (IsTesting) {
EditorState.StopAllTesting();
}
pulsable.PulseAll();
EditorState.ActivePulsable.Value = pulsable;
}
void OnStopTestingHandler() {
if (!IsTesting || Application.isPlaying || pulsable == null) {
return;
}
IsTesting = false;
pulsable.Clear();
SceneView.RepaintAll();
}
void ActivePulsableChangedHandler() {
pulsable.ShowDetectionGizmos = IsActivePulsable;
SceneView.RepaintAll();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3f7a263227ad44aba56cc981feac916b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,106 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors {
public class SensorEditorActions {
static double prevPingTime;
static GameObject prevPingObject;
public static void PingOrSelectObject(GameObject o) {
var time = EditorApplication.timeSinceStartup;
var delta = time - prevPingTime;
if (o != null && ReferenceEquals(o, prevPingObject) && delta <= .3f) {
Selection.activeGameObject = o;
} else if (o != null) {
EditorGUIUtility.PingObject(o);
}
prevPingTime = time;
prevPingObject = o;
}
}
public abstract class BaseSensorEditor<T> : BasePulsableEditor<T> where T : Sensor {
protected T sensor;
protected override void OnEnable() {
base.OnEnable();
sensor = serializedObject.targetObject as T;
}
protected override void OnPulsableGUI() {
EditorGUILayout.Space();
EditorGUI.BeginChangeCheck();
InspectorParameters();
if (EditorGUI.EndChangeCheck()) {
EditorState.StopAllTesting();
}
serializedObject.ApplyModifiedProperties();
if (showDetections) {
EditorUtils.HorizontalLine(new Color(0.5f, 0.5f, 0.5f, 1));
EditorGUILayout.Space();
InspectorDetectedObjects();
EditorGUILayout.Space();
}
}
protected abstract void InspectorParameters();
protected virtual void InspectorDetectedObjects() {
if (sensor.Signals.Count == 0) {
EditorGUILayout.LabelField("Nothing Detected...", new GUIStyle() { fontStyle = FontStyle.Bold });
return;
}
EditorGUILayout.LabelField("Detected Signals", new GUIStyle() { fontStyle = FontStyle.Bold });
foreach (var signal in sensor.Signals) {
SignalFieldLayout(signal);
}
}
protected void SignalFieldLayout(Signal signal) {
var rect = EditorGUILayout.GetControlRect();
SignalField(rect, signal);
}
protected void SignalField(Rect rect, Signal signal) {
var width = rect.width;
var objRect = new Rect(rect.x, rect.y, width / 2, rect.height);
var strengthRect = new Rect(objRect.max.x, rect.y, width / 4, rect.height);
var shapeRect = new Rect(strengthRect.max.x, rect.y, width / 4, rect.height);
DetectedObjectField(objRect, signal.Object);
EditorGUI.LabelField(strengthRect, $"Str: {signal.Strength.ToString("N1")}", new GUIStyle() { alignment = TextAnchor.MiddleCenter });
EditorGUI.LabelField(shapeRect, $"Size: {signal.Shape.size.magnitude.ToString("N1")}");
}
protected void DetectedObjectFieldLayout(GameObject go) {
var rect = EditorGUILayout.GetControlRect();
DetectedObjectField(rect, go);
}
protected void DetectedObjectField(Rect rect, GameObject go) {
var guiContent = EditorGUIUtility.ObjectContent(go, typeof(GameObject));
var style = new GUIStyle("TextField");
style.fixedHeight = 16;
style.imagePosition = go ? ImagePosition.ImageLeft : ImagePosition.TextOnly;
if (GUI.Button(rect, guiContent, style) && go) {
SensorEditorActions.PingOrSelectObject(go);
}
}
protected void BufferSizeInfo(int bufferSize) {
if (bufferSize > PhysicsNonAlloc<object>.InitialSize && Application.isPlaying) {
EditorGUILayout.HelpBox("Buffer size expanded to: " + bufferSize, MessageType.Info);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 70eca8a011db432fb1fdebee224b01d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,56 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(BooleanSensor))]
[CanEditMultipleObjects]
public class BooleanSensorEditor : BaseSensorEditor<BooleanSensor> {
SerializedProperty inputSensors;
SerializedProperty operation;
SerializedProperty signalProcessors;
SerializedProperty onDetected;
SerializedProperty onLostDetection;
SerializedProperty onSomeDetection;
SerializedProperty onNoDetection;
bool showEvents = false;
protected override bool canTest { get { return true; } }
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) {
return;
}
inputSensors = serializedObject.FindProperty("InputSensors");
operation = serializedObject.FindProperty("operation");
signalProcessors = serializedObject.FindProperty("signalProcessors");
onDetected = serializedObject.FindProperty("OnDetected");
onLostDetection = serializedObject.FindProperty("OnLostDetection");
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
onNoDetection = serializedObject.FindProperty("OnNoDetection");
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(inputSensors, true);
EditorGUILayout.PropertyField(operation);
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onDetected);
EditorGUILayout.PropertyField(onLostDetection);
EditorGUILayout.PropertyField(onSomeDetection);
EditorGUILayout.PropertyField(onNoDetection);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b2019cf5f0c448dba819243098ec8e49
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using UnityEngine;
using UnityEditor;
using System.Collections;
namespace Micosmo.SensorToolkit.Editors {
public class EditorUtils {
public static void InlinePropertyField(SerializedProperty root) {
if (!root.hasChildren) {
return;
}
SerializedProperty child = root.Copy();
SerializedProperty nextSiblingProperty = root.Copy();
nextSiblingProperty.NextVisible(false);
child.NextVisible(true);
while(!SerializedProperty.EqualContents(child, nextSiblingProperty)) {
EditorGUILayout.PropertyField(child, true);
child.NextVisible(false);
}
}
public static void HorizontalLine(Color color) {
GUIStyle horizontalLine;
horizontalLine = new GUIStyle();
horizontalLine.normal.background = EditorGUIUtility.whiteTexture;
horizontalLine.margin = new RectOffset(0, 0, 4, 4);
horizontalLine.fixedHeight = 1;
var c = GUI.color;
GUI.color = color;
GUILayout.Box(GUIContent.none, horizontalLine);
GUI.color = c;
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1f1d93a18b8749548f323705b3edaafb
timeCreated: 1601171253
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
using UnityEngine;
using UnityEditor;
using System.Collections;
namespace Micosmo.SensorToolkit
{
[CustomEditor(typeof(FOVCollider2D))]
[CanEditMultipleObjects]
public class FOVCollider2DEditor : Editor
{
SerializedProperty length;
SerializedProperty nearDistance;
SerializedProperty fovAngle;
SerializedProperty resolution;
void OnEnable()
{
if (serializedObject == null) return;
length = serializedObject.FindProperty("Length");
nearDistance = serializedObject.FindProperty("NearDistance");
fovAngle = serializedObject.FindProperty("FOVAngle");
resolution = serializedObject.FindProperty("Resolution");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(length);
EditorGUILayout.PropertyField(nearDistance);
EditorGUILayout.PropertyField(fovAngle);
EditorGUILayout.PropertyField(resolution);
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d78425a8bc834891a4e9419f126e0a83
timeCreated: 1491385286
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using UnityEngine;
using UnityEditor;
using System.Collections;
namespace Micosmo.SensorToolkit
{
[CustomEditor(typeof(FOVCollider))]
[CanEditMultipleObjects]
public class FOVColliderEditor : Editor
{
SerializedProperty length;
SerializedProperty nearDistance;
SerializedProperty fovAngle;
SerializedProperty elevationAngle;
SerializedProperty resolution;
void OnEnable()
{
if (serializedObject == null) return;
length = serializedObject.FindProperty("Length");
nearDistance = serializedObject.FindProperty("NearDistance");
fovAngle = serializedObject.FindProperty("FOVAngle");
elevationAngle = serializedObject.FindProperty("ElevationAngle");
resolution = serializedObject.FindProperty("Resolution");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(length);
EditorGUILayout.PropertyField(nearDistance);
EditorGUILayout.PropertyField(fovAngle);
EditorGUILayout.PropertyField(elevationAngle);
EditorGUILayout.PropertyField(resolution);
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9ec8d8335d1c46e9b4b17f9e90ec102a
timeCreated: 1491114639
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,259 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(LOSSensor2D))]
[CanEditMultipleObjects]
public class LOSSensor2DEditor : BaseSensorEditor<LOSSensor2D> {
static string horizAngleMessage =
"Angle can't exceed 90 when Point Sampling Method is set to Quality. Recommend changing " +
"Point Sampling Method to Fast. In most cases Fast is superior to Quality anyway.";
SerializedProperty inputSensor;
SerializedProperty blocksLineOfSight;
SerializedProperty ignoreTriggerColliders;
SerializedProperty pulseMode;
SerializedProperty pulseUpdateFunction;
SerializedProperty pulseInterval;
SerializedProperty pointSamplingMethod;
SerializedProperty testLOSTargetsOnly;
SerializedProperty numberOfRays;
SerializedProperty minimumVisibility;
SerializedProperty movingAverage;
SerializedProperty windowSize;
SerializedProperty limitDistance;
SerializedProperty maxDistance;
SerializedProperty visibilityByDistance;
SerializedProperty limitViewAngle;
SerializedProperty maxViewAngle;
SerializedProperty visibilityByViewAngle;
SerializedProperty fovConstraintMethod;
SerializedProperty onDetected;
SerializedProperty onLostDetection;
SerializedProperty onSomeDetection;
SerializedProperty onNoDetection;
bool showEvents = false;
protected override bool canTest { get { return true; } }
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) {
return;
}
inputSensor = serializedObject.FindProperty("inputSensor");
blocksLineOfSight = serializedObject.FindProperty("BlocksLineOfSight");
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
pointSamplingMethod = serializedObject.FindProperty("PointSamplingMethod");
testLOSTargetsOnly = serializedObject.FindProperty("TestLOSTargetsOnly");
numberOfRays = serializedObject.FindProperty("NumberOfRays");
minimumVisibility = serializedObject.FindProperty("MinimumVisibility");
movingAverage = serializedObject.FindProperty("MovingAverageEnabled");
windowSize = serializedObject.FindProperty("MovingAverageWindowSize");
limitDistance = serializedObject.FindProperty("LimitDistance");
maxDistance = serializedObject.FindProperty("MaxDistance");
visibilityByDistance = serializedObject.FindProperty("VisibilityByDistance");
limitViewAngle = serializedObject.FindProperty("LimitViewAngle");
maxViewAngle = serializedObject.FindProperty("MaxViewAngle");
visibilityByViewAngle = serializedObject.FindProperty("VisibilityByViewAngle");
fovConstraintMethod = serializedObject.FindProperty("FOVConstraintMethod");
onDetected = serializedObject.FindProperty("OnDetected");
onLostDetection = serializedObject.FindProperty("OnLostDetection");
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
onNoDetection = serializedObject.FindProperty("OnNoDetection");
sensor.ShowRayCastDebug = new HashSet<GameObject>();
}
protected override void OnDisable() {
base.OnDisable();
sensor.ShowRayCastDebug = null;
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(inputSensor);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(blocksLineOfSight);
EditorGUILayout.PropertyField(ignoreTriggerColliders);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(testLOSTargetsOnly);
if (!testLOSTargetsOnly.boolValue) {
EditorGUILayout.PropertyField(numberOfRays);
EditorGUILayout.PropertyField(pointSamplingMethod);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(movingAverage, new GUIContent("Moving Average"));
if (sensor.MovingAverageEnabled) {
EditorGUILayout.PropertyField(windowSize, new GUIContent("Window Size"));
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(limitDistance);
if (sensor.LimitDistance) {
EditorGUILayout.PropertyField(maxDistance);
ScalingFunctionProperty(visibilityByDistance);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(limitViewAngle);
if (sensor.LimitViewAngle) {
EditorGUILayout.PropertyField(maxViewAngle);
if (sensor.MaxViewAngle > 90f && sensor.PointSamplingMethod == PointSamplingMethod.Quality) {
EditorGUILayout.HelpBox(horizAngleMessage, MessageType.Error);
}
ScalingFunctionProperty(visibilityByViewAngle);
}
EditorGUILayout.Space();
if (sensor.LimitDistance || sensor.LimitViewAngle) {
EditorGUILayout.PropertyField(fovConstraintMethod);
EditorGUILayout.Space();
}
EditorGUILayout.PropertyField(minimumVisibility);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
EditorGUILayout.PropertyField(pulseUpdateFunction);
}
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
}
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onDetected);
EditorGUILayout.PropertyField(onLostDetection);
EditorGUILayout.PropertyField(onSomeDetection);
EditorGUILayout.PropertyField(onNoDetection);
}
}
protected override void InspectorDetectedObjects() {
EditorGUILayout.Space();
VisibilityTable();
SceneView.RepaintAll();
}
void ScalingFunctionProperty(SerializedProperty property) {
var mode = property.FindPropertyRelative("Mode");
var curve = property.FindPropertyRelative("Curve");
EditorGUILayout.PropertyField(mode, new GUIContent(property.displayName));
var modeVal = (ScalingMode)mode.intValue;
if (modeVal == ScalingMode.Curve) {
EditorGUILayout.PropertyField(curve, new GUIContent(" "));
}
}
void VisibilityTable() {
var headerRow = EditorGUILayout.GetControlRect();
VisibilityHeaders(headerRow);
var losResults = sensor.GetAllResults();
losResults.Sort(delegate (ILOSResult r1, ILOSResult r2) {
if (r1.Visibility < r2.Visibility) {
return 1;
} else if (r1.Visibility == r2.Visibility) {
return 0;
}
return -1;
});
foreach (var result in losResults) {
var rect = EditorGUILayout.GetControlRect();
VisibilityRow(rect, result);
}
}
void VisibilityColumns(Rect rect, out Rect showRaysCol, out Rect visibilityCol, out Rect signalCol) {
var width = rect.width;
var showRaysWidth = Mathf.Min(50f, width / 3);
showRaysCol = new Rect(rect.x, rect.y, showRaysWidth, rect.height);
width -= showRaysWidth;
var visibilityWidth = Mathf.Min(80f, width / 2);
visibilityCol = new Rect(showRaysCol.xMax, rect.y, visibilityWidth, rect.height);
width -= visibilityWidth;
signalCol = new Rect(visibilityCol.xMax, rect.y, width, rect.height);
}
void VisibilityHeaders(Rect rect) {
Rect rRaysCol, rVisCol, rSigCol;
VisibilityColumns(rect, out rRaysCol, out rVisCol, out rSigCol);
var headerColumnStyle = new GUIStyle(EditorStyles.label) {
fontStyle = FontStyle.Bold,
padding = new RectOffset(0, 16, 0, 0)
};
if (IsActivePulsable) {
GUI.Label(rRaysCol, "Show", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperCenter });
}
GUI.Label(rVisCol, "Visibility", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperRight });
GUI.Label(rSigCol, "Output Signal", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperLeft });
}
void VisibilityRow(Rect rect, ILOSResult losResult) {
Rect rRaysCol, rVisCol, rSigCol;
VisibilityColumns(rect, out rRaysCol, out rVisCol, out rSigCol);
rRaysCol.xMin += rRaysCol.width / 2f - 16;
rVisCol.xMax -= 16;
var signal = losResult.OutputSignal;
if (IsActivePulsable) {
var debug = sensor.ShowRayCastDebug.Contains(signal.Object);
if (debug = EditorGUI.Toggle(rRaysCol, debug)) {
sensor.ShowRayCastDebug.Add(signal.Object);
} else {
sensor.ShowRayCastDebug.Remove(signal.Object);
}
}
var visStyle = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleRight };
if (!losResult.IsVisible) {
visStyle.normal.textColor = STPrefs.RedEditorTextColour;
}
GUI.Label(rVisCol, string.Format("{0:P1}", losResult.Visibility), visStyle);
SignalField(rSigCol, signal);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 34e4a36656384a5d8e94b847a7505ad3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,265 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(LOSSensor))]
[CanEditMultipleObjects]
public class LOSSensorEditor : BaseSensorEditor<LOSSensor> {
static string horizAngleMessage =
"Angle can't exceed 90 when Point Sampling Method is set to Quality. Recommend changing " +
"Point Sampling Method to Fast. In most cases Fast is superior to Quality anyway.";
SerializedProperty inputSensor;
SerializedProperty blocksLineOfSight;
SerializedProperty ignoreTriggerColliders;
SerializedProperty pulseMode;
SerializedProperty pulseUpdateFunction;
SerializedProperty pulseInterval;
SerializedProperty pointSamplingMethod;
SerializedProperty testLOSTargetsOnly;
SerializedProperty numberOfRays;
SerializedProperty minimumVisibility;
SerializedProperty movingAverage;
SerializedProperty windowSize;
SerializedProperty limitDistance;
SerializedProperty maxDistance;
SerializedProperty visibilityByDistance;
SerializedProperty limitViewAngle;
SerializedProperty maxHorizAngle;
SerializedProperty visibilityByHorizAngle;
SerializedProperty maxVertAngle;
SerializedProperty visibilityByVertAngle;
SerializedProperty fovConstraintMethod;
SerializedProperty onDetected;
SerializedProperty onLostDetection;
SerializedProperty onSomeDetection;
SerializedProperty onNoDetection;
bool showEvents = false;
protected override bool canTest { get { return true; } }
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) {
return;
}
inputSensor = serializedObject.FindProperty("inputSensor");
blocksLineOfSight = serializedObject.FindProperty("BlocksLineOfSight");
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
pointSamplingMethod = serializedObject.FindProperty("PointSamplingMethod");
testLOSTargetsOnly = serializedObject.FindProperty("TestLOSTargetsOnly");
numberOfRays = serializedObject.FindProperty("NumberOfRays");
minimumVisibility = serializedObject.FindProperty("MinimumVisibility");
movingAverage = serializedObject.FindProperty("MovingAverageEnabled");
windowSize = serializedObject.FindProperty("MovingAverageWindowSize");
limitDistance = serializedObject.FindProperty("LimitDistance");
maxDistance = serializedObject.FindProperty("MaxDistance");
visibilityByDistance = serializedObject.FindProperty("VisibilityByDistance");
limitViewAngle = serializedObject.FindProperty("LimitViewAngle");
maxHorizAngle = serializedObject.FindProperty("MaxHorizAngle");
visibilityByHorizAngle = serializedObject.FindProperty("VisibilityByHorizAngle");
maxVertAngle = serializedObject.FindProperty("MaxVertAngle");
visibilityByVertAngle = serializedObject.FindProperty("VisibilityByVertAngle");
fovConstraintMethod = serializedObject.FindProperty("FOVConstraintMethod");
onDetected = serializedObject.FindProperty("OnDetected");
onLostDetection = serializedObject.FindProperty("OnLostDetection");
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
onNoDetection = serializedObject.FindProperty("OnNoDetection");
sensor.ShowRayCastDebug = new HashSet<GameObject>();
}
protected override void OnDisable() {
base.OnDisable();
sensor.ShowRayCastDebug = null;
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(inputSensor);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(blocksLineOfSight);
EditorGUILayout.PropertyField(ignoreTriggerColliders);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(testLOSTargetsOnly);
if (!testLOSTargetsOnly.boolValue) {
EditorGUILayout.PropertyField(numberOfRays);
EditorGUILayout.PropertyField(pointSamplingMethod);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(movingAverage, new GUIContent("Moving Average"));
if (sensor.MovingAverageEnabled) {
EditorGUILayout.PropertyField(windowSize, new GUIContent("Window Size"));
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(limitDistance);
if (sensor.LimitDistance) {
EditorGUILayout.PropertyField(maxDistance);
ScalingFunctionProperty(visibilityByDistance);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(limitViewAngle);
if (sensor.LimitViewAngle) {
EditorGUILayout.PropertyField(maxHorizAngle);
if (sensor.MaxHorizAngle > 90f && sensor.PointSamplingMethod == PointSamplingMethod.Quality) {
EditorGUILayout.HelpBox(horizAngleMessage, MessageType.Error);
}
ScalingFunctionProperty(visibilityByHorizAngle);
EditorGUILayout.PropertyField(maxVertAngle);
ScalingFunctionProperty(visibilityByVertAngle);
}
EditorGUILayout.Space();
if (sensor.LimitDistance || sensor.LimitViewAngle) {
EditorGUILayout.PropertyField(fovConstraintMethod);
EditorGUILayout.Space();
}
EditorGUILayout.PropertyField(minimumVisibility);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
EditorGUILayout.PropertyField(pulseUpdateFunction);
}
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
}
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onDetected);
EditorGUILayout.PropertyField(onLostDetection);
EditorGUILayout.PropertyField(onSomeDetection);
EditorGUILayout.PropertyField(onNoDetection);
}
}
protected override void InspectorDetectedObjects() {
EditorGUILayout.Space();
VisibilityTable();
SceneView.RepaintAll();
}
void ScalingFunctionProperty(SerializedProperty property) {
var mode = property.FindPropertyRelative("Mode");
var curve = property.FindPropertyRelative("Curve");
EditorGUILayout.PropertyField(mode, new GUIContent(property.displayName));
var modeVal = (ScalingMode)mode.intValue;
if (modeVal == ScalingMode.Curve) {
EditorGUILayout.PropertyField(curve, new GUIContent(" "));
}
}
void VisibilityTable() {
var headerRow = EditorGUILayout.GetControlRect();
VisibilityHeaders(headerRow);
var losResults = sensor.GetAllResults();
losResults.Sort(delegate (ILOSResult r1, ILOSResult r2) {
if (r1.Visibility < r2.Visibility) {
return 1;
} else if (r1.Visibility == r2.Visibility) {
return 0;
}
return -1;
});
foreach (var result in losResults) {
var rect = EditorGUILayout.GetControlRect();
VisibilityRow(rect, result);
}
}
void VisibilityColumns(Rect rect, out Rect showRaysCol, out Rect visibilityCol, out Rect signalCol) {
var width = rect.width;
var showRaysWidth = Mathf.Min(50f, width / 3);
showRaysCol = new Rect(rect.x, rect.y, showRaysWidth, rect.height);
width -= showRaysWidth;
var visibilityWidth = Mathf.Min(80f, width / 2);
visibilityCol = new Rect(showRaysCol.xMax, rect.y, visibilityWidth, rect.height);
width -= visibilityWidth;
signalCol = new Rect(visibilityCol.xMax, rect.y, width, rect.height);
}
void VisibilityHeaders(Rect rect) {
Rect rRaysCol, rVisCol, rSigCol;
VisibilityColumns(rect, out rRaysCol, out rVisCol, out rSigCol);
var headerColumnStyle = new GUIStyle(EditorStyles.label) {
fontStyle = FontStyle.Bold,
padding = new RectOffset(0,16,0,0)
};
if (IsActivePulsable) {
GUI.Label(rRaysCol, "Show", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperCenter });
}
GUI.Label(rVisCol, "Visibility", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperRight });
GUI.Label(rSigCol, "Output Signal", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperLeft });
}
void VisibilityRow(Rect rect, ILOSResult losResult) {
Rect rRaysCol, rVisCol, rSigCol;
VisibilityColumns(rect, out rRaysCol, out rVisCol, out rSigCol);
rRaysCol.xMin += rRaysCol.width / 2f - 16;
rVisCol.xMax -= 16;
var signal = losResult.OutputSignal;
if (IsActivePulsable) {
var debug = sensor.ShowRayCastDebug.Contains(signal.Object);
if (debug = EditorGUI.Toggle(rRaysCol, debug)) {
sensor.ShowRayCastDebug.Add(signal.Object);
} else {
sensor.ShowRayCastDebug.Remove(signal.Object);
}
}
var visStyle = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleRight };
if (!losResult.IsVisible) {
visStyle.normal.textColor = STPrefs.RedEditorTextColour;
}
GUI.Label(rVisCol, string.Format("{0:P1}", losResult.Visibility), visStyle);
SignalField(rSigCol, signal);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2a9e2ee831754f5d9ed0f5f5521354ce
timeCreated: 1600682780
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
{
"name": "Micosmo.SensorToolkit.Editor",
"references": [
"GUID:b9d61b92870877a459c95c25c7d15074"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1f03c1aaa5578cf4c8db4bdb47ee1a7b
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Micosmo.SensorToolkit;
namespace Micosmo.SensorToolkit.Editors {
[CustomPropertyDrawer(typeof(NavMeshMaskAttribute))]
public class NavMeshMaskDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty serializedProperty, GUIContent label) {
using (new GUILayout.HorizontalScope()) {
position = EditorGUI.PrefixLabel(position, label);
EditorGUI.BeginChangeCheck();
string[] areaNames = GameObjectUtility.GetNavMeshAreaNames();
List<string> completeAreaNames = new List<string>();
foreach (string name in areaNames) {
var id = GameObjectUtility.GetNavMeshAreaFromName(name);
while (id >= completeAreaNames.Count) {
completeAreaNames.Add("");
}
completeAreaNames[id] = name;
}
int mask = serializedProperty.intValue;
mask = EditorGUI.MaskField(position, mask, completeAreaNames.ToArray());
if (EditorGUI.EndChangeCheck()) {
serializedProperty.intValue = mask;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e6ba988a66d424ea6597d3e437d3400
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,96 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(NavMeshSensor))]
[CanEditMultipleObjects]
public class NavMeshSensorEditor : BasePulsableEditor<NavMeshSensor> {
SerializedProperty test;
SerializedProperty ray;
SerializedProperty sphere;
SerializedProperty areaMask;
SerializedProperty pulseMode;
SerializedProperty pulseInterval;
SerializedProperty onObstructed;
SerializedProperty onClear;
bool showEvents = false;
protected NavMeshSensor sensor;
protected override bool canTest => true;
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) return;
sensor = serializedObject.targetObject as NavMeshSensor;
test = serializedObject.FindProperty("Test");
ray = serializedObject.FindProperty("Ray");
sphere = serializedObject.FindProperty("Sphere");
areaMask = serializedObject.FindProperty("AreaMask");
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
onObstructed = serializedObject.FindProperty("onObstruction");
onClear = serializedObject.FindProperty("onClear");
}
protected override void OnPulsableGUI() {
EditorGUILayout.Space();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(test);
if (sensor.Test == NavMeshSensor.TestType.Ray) {
EditorUtils.InlinePropertyField(ray);
} else if (sensor.Test == NavMeshSensor.TestType.Sample) {
EditorUtils.InlinePropertyField(sphere);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(areaMask);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
}
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onObstructed);
EditorGUILayout.PropertyField(onClear);
}
if (EditorGUI.EndChangeCheck()) {
EditorState.StopAllTesting();
}
serializedObject.ApplyModifiedProperties();
if (showDetections) {
EditorUtils.HorizontalLine(new Color(0.5f, 0.5f, 0.5f, 1));
EditorGUILayout.Space();
InspectorDetectedObjects();
EditorGUILayout.Space();
}
}
void InspectorDetectedObjects() {
if (!sensor.IsObstructed) return;
EditorGUILayout.Space();
EditorGUILayout.LabelField("Ray is Obstructed", new GUIStyle() { fontStyle = FontStyle.Bold, normal = new GUIStyleState() { textColor = STPrefs.RedEditorTextColour } });
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 152d28158e8a4f6c8472d50e383242fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,107 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(RangeSensor2D))]
[CanEditMultipleObjects]
public class RangeSensor2DEditor : BaseSensorEditor<RangeSensor2D> {
SerializedProperty shape;
SerializedProperty circle;
SerializedProperty box;
SerializedProperty capsule;
SerializedProperty ignoreList;
SerializedProperty tagFilterEnabled;
SerializedProperty tagFilter;
SerializedProperty detectsOnLayers;
SerializedProperty pulseMode;
SerializedProperty pulseUpdateFunction;
SerializedProperty pulseInterval;
SerializedProperty detectionMode;
SerializedProperty ignoreTriggerColliders;
SerializedProperty signalProcessors;
SerializedProperty onDetected;
SerializedProperty onLostDetection;
SerializedProperty onSomeDetection;
SerializedProperty onNoDetection;
bool showEvents = false;
protected override bool canTest { get { return true; } }
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) {
return;
}
shape = serializedObject.FindProperty("Shape");
circle = serializedObject.FindProperty("Circle");
box = serializedObject.FindProperty("Box");
capsule = serializedObject.FindProperty("Capsule");
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
detectsOnLayers = serializedObject.FindProperty("DetectsOnLayers");
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
detectionMode = serializedObject.FindProperty("DetectionMode");
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
signalProcessors = serializedObject.FindProperty("signalProcessors");
onDetected = serializedObject.FindProperty("OnDetected");
onLostDetection = serializedObject.FindProperty("OnLostDetection");
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
onNoDetection = serializedObject.FindProperty("OnNoDetection");
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(shape);
if (sensor.Shape == RangeSensor2D.Shapes.Circle) {
EditorUtils.InlinePropertyField(circle);
} else if (sensor.Shape == RangeSensor2D.Shapes.Box) {
EditorUtils.InlinePropertyField(box);
} else if (sensor.Shape == RangeSensor2D.Shapes.Capsule) {
EditorUtils.InlinePropertyField(capsule);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(ignoreList, true);
EditorGUILayout.PropertyField(tagFilterEnabled);
if (tagFilterEnabled.boolValue) {
EditorGUILayout.PropertyField(tagFilter, true);
}
EditorGUILayout.PropertyField(detectsOnLayers);
EditorGUILayout.PropertyField(detectionMode);
EditorGUILayout.PropertyField(ignoreTriggerColliders);
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
EditorGUILayout.Space();
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
EditorGUILayout.PropertyField(pulseUpdateFunction);
}
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
}
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onDetected);
EditorGUILayout.PropertyField(onLostDetection);
EditorGUILayout.PropertyField(onSomeDetection);
EditorGUILayout.PropertyField(onNoDetection);
}
EditorGUILayout.Space();
BufferSizeInfo(sensor.CurrentBufferSize);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 550d906f63c0467f9e9968050a56c67d
timeCreated: 1491309471
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,107 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(RangeSensor))]
[CanEditMultipleObjects]
public class RangeSensorEditor : BaseSensorEditor<RangeSensor> {
SerializedProperty shape;
SerializedProperty sphere;
SerializedProperty box;
SerializedProperty capsule;
SerializedProperty ignoreList;
SerializedProperty tagFilterEnabled;
SerializedProperty tagFilter;
SerializedProperty detectsOnLayers;
SerializedProperty pulseMode;
SerializedProperty pulseUpdateFunction;
SerializedProperty pulseInterval;
SerializedProperty detectionMode;
SerializedProperty ignoreTriggerColliders;
SerializedProperty signalProcessors;
SerializedProperty onDetected;
SerializedProperty onLostDetection;
SerializedProperty onSomeDetection;
SerializedProperty onNoDetection;
bool showEvents = false;
protected override bool canTest { get { return true; } }
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) {
return;
}
shape = serializedObject.FindProperty("Shape");
sphere = serializedObject.FindProperty("Sphere");
box = serializedObject.FindProperty("Box");
capsule = serializedObject.FindProperty("Capsule");
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
detectsOnLayers = serializedObject.FindProperty("DetectsOnLayers");
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
detectionMode = serializedObject.FindProperty("DetectionMode");
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
signalProcessors = serializedObject.FindProperty("signalProcessors");
onDetected = serializedObject.FindProperty("OnDetected");
onLostDetection = serializedObject.FindProperty("OnLostDetection");
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
onNoDetection = serializedObject.FindProperty("OnNoDetection");
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(shape);
if (sensor.Shape == RangeSensor.Shapes.Sphere) {
EditorUtils.InlinePropertyField(sphere);
} else if (sensor.Shape == RangeSensor.Shapes.Box) {
EditorUtils.InlinePropertyField(box);
} else if (sensor.Shape == RangeSensor.Shapes.Capsule) {
EditorUtils.InlinePropertyField(capsule);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(ignoreList, true);
EditorGUILayout.PropertyField(tagFilterEnabled);
if (tagFilterEnabled.boolValue) {
EditorGUILayout.PropertyField(tagFilter, true);
}
EditorGUILayout.PropertyField(detectsOnLayers);
EditorGUILayout.PropertyField(detectionMode);
EditorGUILayout.PropertyField(ignoreTriggerColliders);
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
EditorGUILayout.Space();
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
EditorGUILayout.PropertyField(pulseUpdateFunction);
}
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
}
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onDetected);
EditorGUILayout.PropertyField(onLostDetection);
EditorGUILayout.PropertyField(onSomeDetection);
EditorGUILayout.PropertyField(onNoDetection);
}
EditorGUILayout.Space();
BufferSizeInfo(sensor.CurrentBufferSize);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3554b5d938da480dafdfd6872234ffe6
timeCreated: 1489796111
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,149 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(RaySensor2D))]
[CanEditMultipleObjects]
public class RaySensor2DEditor : BaseSensorEditor<RaySensor2D> {
SerializedProperty length;
SerializedProperty shape;
SerializedProperty circle;
SerializedProperty box;
SerializedProperty capsule;
SerializedProperty ignoreList;
SerializedProperty tagFilterEnabled;
SerializedProperty tagFilter;
SerializedProperty detectsOnLayers;
SerializedProperty detectionMode;
SerializedProperty ignoreTriggerColliders;
SerializedProperty signalProcessors;
SerializedProperty minimumSlopeAngle;
SerializedProperty slopeUpDirection;
SerializedProperty obstructedByLayers;
SerializedProperty direction;
SerializedProperty worldSpace;
SerializedProperty pulseMode;
SerializedProperty pulseUpdateFunction;
SerializedProperty pulseInterval;
SerializedProperty onDetected;
SerializedProperty onLostDetection;
SerializedProperty onSomeDetection;
SerializedProperty onNoDetection;
SerializedProperty onObstructed;
SerializedProperty onClear;
bool showEvents = false;
protected override bool canTest { get { return true; } }
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) return;
length = serializedObject.FindProperty("Length");
shape = serializedObject.FindProperty("Shape");
circle = serializedObject.FindProperty("Circle");
box = serializedObject.FindProperty("Box");
capsule = serializedObject.FindProperty("Capsule");
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
detectsOnLayers = serializedObject.FindProperty("DetectsOnLayers");
detectionMode = serializedObject.FindProperty("DetectionMode");
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
signalProcessors = serializedObject.FindProperty("signalProcessors");
minimumSlopeAngle = serializedObject.FindProperty("MinimumSlopeAngle");
slopeUpDirection = serializedObject.FindProperty("SlopeUpDirection");
obstructedByLayers = serializedObject.FindProperty("ObstructedByLayers");
direction = serializedObject.FindProperty("Direction");
worldSpace = serializedObject.FindProperty("WorldSpace");
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
onDetected = serializedObject.FindProperty("OnDetected");
onLostDetection = serializedObject.FindProperty("OnLostDetection");
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
onNoDetection = serializedObject.FindProperty("OnNoDetection");
onObstructed = serializedObject.FindProperty("onObstruction");
onClear = serializedObject.FindProperty("onClear");
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(shape);
if (sensor.Shape == RaySensor2D.CastShapeType.Circle) {
EditorUtils.InlinePropertyField(circle);
} else if (sensor.Shape == RaySensor2D.CastShapeType.Box) {
EditorUtils.InlinePropertyField(box);
} else if (sensor.Shape == RaySensor2D.CastShapeType.Capsule) {
EditorUtils.InlinePropertyField(capsule);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(length);
EditorGUILayout.PropertyField(direction);
EditorGUILayout.PropertyField(worldSpace);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(ignoreList, true);
EditorGUILayout.PropertyField(tagFilterEnabled);
if (tagFilterEnabled.boolValue) {
EditorGUILayout.PropertyField(tagFilter, true);
}
EditorGUILayout.PropertyField(detectsOnLayers);
EditorGUILayout.PropertyField(detectionMode);
EditorGUILayout.PropertyField(ignoreTriggerColliders);
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
EditorGUILayout.Space();
EditorGUILayout.PropertyField(minimumSlopeAngle);
if (sensor.MinimumSlopeAngle > 0f) {
EditorGUILayout.PropertyField(slopeUpDirection);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(obstructedByLayers);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
EditorGUILayout.PropertyField(pulseUpdateFunction);
}
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
}
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onDetected);
EditorGUILayout.PropertyField(onLostDetection);
EditorGUILayout.PropertyField(onSomeDetection);
EditorGUILayout.PropertyField(onNoDetection);
EditorGUILayout.PropertyField(onObstructed);
EditorGUILayout.PropertyField(onClear);
}
EditorGUILayout.Space();
BufferSizeInfo(sensor.CurrentBufferSize);
}
protected override void InspectorDetectedObjects() {
base.InspectorDetectedObjects();
if (!sensor.IsObstructed) return;
EditorGUILayout.Space();
EditorGUILayout.LabelField("Ray is Obstructed", new GUIStyle() { fontStyle = FontStyle.Bold, normal = new GUIStyleState() { textColor = STPrefs.RedEditorTextColour } });
DetectedObjectFieldLayout(sensor.GetObstructionRayHit().GameObject);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: dae438f4c18e49eaa664e023208bac2a
timeCreated: 1491307096
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,150 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors
{
[CustomEditor(typeof(RaySensor))]
[CanEditMultipleObjects]
public class RaySensorEditor : BaseSensorEditor<RaySensor> {
SerializedProperty length;
SerializedProperty shape;
SerializedProperty sphere;
SerializedProperty box;
SerializedProperty capsule;
SerializedProperty ignoreList;
SerializedProperty tagFilterEnabled;
SerializedProperty tagFilter;
SerializedProperty detectsOnLayers;
SerializedProperty detectionMode;
SerializedProperty ignoreTriggerColliders;
SerializedProperty signalProcessors;
SerializedProperty minimumSlopeAngle;
SerializedProperty slopeUpDirection;
SerializedProperty obstructedByLayers;
SerializedProperty direction;
SerializedProperty worldSpace;
SerializedProperty pulseMode;
SerializedProperty pulseUpdateFunction;
SerializedProperty pulseInterval;
SerializedProperty onDetected;
SerializedProperty onLostDetection;
SerializedProperty onSomeDetection;
SerializedProperty onNoDetection;
SerializedProperty onObstructed;
SerializedProperty onClear;
bool showEvents = false;
protected override bool canTest { get { return true; } }
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) return;
length = serializedObject.FindProperty("Length");
shape = serializedObject.FindProperty("Shape");
sphere = serializedObject.FindProperty("Sphere");
box = serializedObject.FindProperty("Box");
capsule = serializedObject.FindProperty("Capsule");
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
detectsOnLayers = serializedObject.FindProperty("DetectsOnLayers");
detectionMode = serializedObject.FindProperty("DetectionMode");
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
signalProcessors = serializedObject.FindProperty("signalProcessors");
minimumSlopeAngle = serializedObject.FindProperty("MinimumSlopeAngle");
slopeUpDirection = serializedObject.FindProperty("SlopeUpDirection");
obstructedByLayers = serializedObject.FindProperty("ObstructedByLayers");
direction = serializedObject.FindProperty("Direction");
worldSpace = serializedObject.FindProperty("WorldSpace");
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
onDetected = serializedObject.FindProperty("OnDetected");
onLostDetection = serializedObject.FindProperty("OnLostDetection");
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
onNoDetection = serializedObject.FindProperty("OnNoDetection");
onObstructed = serializedObject.FindProperty("onObstruction");
onClear = serializedObject.FindProperty("onClear");
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(shape);
if (sensor.Shape == RaySensor.CastShapeType.Sphere) {
EditorUtils.InlinePropertyField(sphere);
} else if (sensor.Shape == RaySensor.CastShapeType.Box) {
EditorUtils.InlinePropertyField(box);
} else if (sensor.Shape == RaySensor.CastShapeType.Capsule) {
EditorUtils.InlinePropertyField(capsule);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(length);
EditorGUILayout.PropertyField(direction);
EditorGUILayout.PropertyField(worldSpace);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(ignoreList, true);
EditorGUILayout.PropertyField(tagFilterEnabled);
if (tagFilterEnabled.boolValue) {
EditorGUILayout.PropertyField(tagFilter, true);
}
EditorGUILayout.PropertyField(detectsOnLayers);
EditorGUILayout.PropertyField(detectionMode);
EditorGUILayout.PropertyField(ignoreTriggerColliders);
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
EditorGUILayout.Space();
EditorGUILayout.PropertyField(minimumSlopeAngle);
if (sensor.MinimumSlopeAngle > 0f) {
EditorGUILayout.PropertyField(slopeUpDirection);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(obstructedByLayers);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
EditorGUILayout.PropertyField(pulseUpdateFunction);
}
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
}
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onDetected);
EditorGUILayout.PropertyField(onLostDetection);
EditorGUILayout.PropertyField(onSomeDetection);
EditorGUILayout.PropertyField(onNoDetection);
EditorGUILayout.PropertyField(onObstructed);
EditorGUILayout.PropertyField(onClear);
}
EditorGUILayout.Space();
BufferSizeInfo(sensor.CurrentBufferSize);
}
protected override void InspectorDetectedObjects() {
base.InspectorDetectedObjects();
if (!sensor.IsObstructed) return;
EditorGUILayout.Space();
EditorGUILayout.LabelField("Ray is Obstructed", new GUIStyle() { fontStyle = FontStyle.Bold, normal = new GUIStyleState() { textColor = STPrefs.RedEditorTextColour } });
DetectedObjectFieldLayout(sensor.GetObstructionRayHit().GameObject);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9ccb9305913f430a9726bd254afd4c15
timeCreated: 1490954162
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,83 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors {
[CustomPropertyDrawer(typeof(SteerSeek))]
public class SteerSeekDrawer : PropertyDrawer {
bool foldout = false;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
var foldoutRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
foldout = EditorGUI.Foldout(foldoutRect, foldout, label, true);
if (!foldout) {
return;
}
// Indent child fields
EditorGUI.indentLevel++;
position.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
// Find properties
var seekMode = property.FindPropertyRelative("SeekMode");
var seekPosition = property.FindPropertyRelative("SeekPosition");
var seekDirection = property.FindPropertyRelative("SeekDirection");
var arriveDistanceThreshold = property.FindPropertyRelative("ArriveDistanceThreshold");
var stoppingDistance = property.FindPropertyRelative("StoppingDistance");
var arriveDistanceThresholdPosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.PropertyField(arriveDistanceThresholdPosition, arriveDistanceThreshold);
position.y += arriveDistanceThresholdPosition.height + EditorGUIUtility.standardVerticalSpacing;
var stoppingDistancePosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.PropertyField(stoppingDistancePosition, stoppingDistance);
position.y += stoppingDistancePosition.height + EditorGUIUtility.standardVerticalSpacing;
var seekModePosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.PropertyField(seekModePosition, seekMode);
position.y += seekModePosition.height + EditorGUIUtility.standardVerticalSpacing;
// Conditionally draw SeekPosition field based on SeekMode value
if (seekMode.enumValueIndex == (int)SeekMode.Position) {
var seekPositionPosition = new Rect(position.x, position.y, position.width, EditorGUI.GetPropertyHeight(seekPosition, label, true));
EditorGUI.PropertyField(seekPositionPosition, seekPosition, true);
position.y += seekPositionPosition.height + EditorGUIUtility.standardVerticalSpacing;
} else if (seekMode.enumValueIndex == (int)SeekMode.Direction) {
var seekDirectionPosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.PropertyField(seekDirectionPosition, seekDirection);
position.y += seekDirectionPosition.height + EditorGUIUtility.standardVerticalSpacing;
}
EditorGUI.indentLevel--;
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
if (!foldout) {
return EditorGUIUtility.singleLineHeight;
}
var height = EditorGUIUtility.singleLineHeight;
var seekMode = property.FindPropertyRelative("SeekMode");
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; // seekMode
if (seekMode.enumValueIndex == (int)SeekMode.Position) {
var seekPosition = property.FindPropertyRelative("SeekPosition");
height += EditorGUI.GetPropertyHeight(seekPosition, true) + EditorGUIUtility.standardVerticalSpacing;
} else if (seekMode.enumValueIndex == (int)SeekMode.Direction) {
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; // seekDirection
}
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; // ArriveDistanceThreshold
height += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; // StoppingDistance
return height;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc62f67ad14e1664780363e945ae56e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,147 @@
using UnityEngine;
using UnityEditor;
using System.Collections;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(SteeringSensor2D))]
[CanEditMultipleObjects]
public class SteeringSensor2DEditor : BasePulsableEditor<SteeringSensor2D> {
SerializedProperty resolution;
SerializedProperty seek;
SerializedProperty interest;
SerializedProperty danger;
SerializedProperty vo;
SerializedProperty decision;
SerializedProperty pulseMode;
SerializedProperty pulseUpdateFunction;
SerializedProperty pulseInterval;
SerializedProperty locomotionMode;
SerializedProperty rigidBody;
SerializedProperty locomotion;
protected SteeringSensor2D sensor;
protected override bool canTest => true;
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) return;
sensor = serializedObject.targetObject as SteeringSensor2D;
resolution = serializedObject.FindProperty("resolution");
seek = serializedObject.FindProperty("seek");
interest = serializedObject.FindProperty("interest");
danger = serializedObject.FindProperty("danger");
vo = serializedObject.FindProperty("velocity");
decision = serializedObject.FindProperty("decision");
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
locomotionMode = serializedObject.FindProperty("LocomotionMode");
rigidBody = serializedObject.FindProperty("RigidBody");
locomotion = serializedObject.FindProperty("locomotion");
}
protected override void OnPulsableGUI() {
EditorGUILayout.Space();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(resolution);
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
EditorGUILayout.PropertyField(pulseUpdateFunction);
}
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(seek);
EditorGUILayout.Space();
SteeringSensor2D.ShowInterestGizmos = SteerSystemLayout("Interest", interest, SteeringSensor2D.ShowInterestGizmos);
EditorGUILayout.Space();
SteeringSensor2D.ShowDangerGizmos = SteerSystemLayout("Danger", danger, SteeringSensor2D.ShowDangerGizmos);
EditorGUILayout.Space();
SteeringSensor2D.ShowVelocityGizmos = SteerSystemLayout("Velocity", vo, SteeringSensor2D.ShowVelocityGizmos);
EditorGUILayout.Space();
SteeringSensor2D.ShowDecisionGizmos = SteerSystemLayout("Decision", decision, SteeringSensor2D.ShowDecisionGizmos);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(locomotionMode);
var locmode = (LocomotionMode2D)locomotionMode.enumValueIndex;
if (locmode != LocomotionMode2D.None) {
if (locmode == LocomotionMode2D.RigidBody2D) {
EditorGUILayout.PropertyField(rigidBody);
}
EditorGUILayout.PropertyField(locomotion, true);
}
if (EditorGUI.EndChangeCheck()) {
EditorState.StopAllTesting();
}
displayErrors();
serializedObject.ApplyModifiedProperties();
}
void displayErrors() {
}
bool SteerSystemLayout(string labelText, SerializedProperty property, bool showGizmos) {
const float gizmoWidth = 70;
EditorGUILayout.BeginVertical(BackgroundStyle);
var r = EditorGUILayout.BeginHorizontal();
EditorGUI.PrefixLabel(r, new GUIContent(labelText));
// We don't want to stop testing after clicking the checkbox to show gizmos for a steering system. Suspend the change
// detection first.
if (EditorGUI.EndChangeCheck()) {
EditorState.StopAllTesting();
}
var prevShowGizmos = showGizmos;
showGizmos = GUI.Toggle(new Rect(r.width - gizmoWidth, r.y, gizmoWidth, EditorGUIUtility.singleLineHeight), showGizmos, " Gizmos");
if (IsTesting && prevShowGizmos != showGizmos) {
SceneView.RepaintAll();
}
EditorGUI.BeginChangeCheck();
r.width -= 30;
EditorGUILayout.PropertyField(property, new GUIContent(""));
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
return showGizmos;
}
static GUIStyle backgroundStyle;
static GUIStyle BackgroundStyle {
get {
if (backgroundStyle?.normal?.background == null) {
var texture = new Texture2D(1, 1);
texture.SetPixel(0, 0, new Color(0.2f, 0.2f, 0.2f, 0.15f));
texture.Apply();
backgroundStyle = new GUIStyle();
backgroundStyle.normal.background = texture;
}
return backgroundStyle;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 73d4e924a248430e888b584bd22fce7e
timeCreated: 1506248635
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,158 @@
using UnityEngine;
using UnityEditor;
using System.Collections;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(SteeringSensor))]
[CanEditMultipleObjects]
public class SteeringSensorEditor : BasePulsableEditor<SteeringSensor> {
SerializedProperty isSpherical;
SerializedProperty upDirection;
SerializedProperty resolution;
SerializedProperty seek;
SerializedProperty interest;
SerializedProperty danger;
SerializedProperty vo;
SerializedProperty decision;
SerializedProperty pulseMode;
SerializedProperty pulseUpdateFunction;
SerializedProperty pulseInterval;
SerializedProperty locomotionMode;
SerializedProperty rigidBody;
SerializedProperty characterController;
SerializedProperty locomotion;
protected SteeringSensor sensor;
protected override bool canTest => true;
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) return;
sensor = serializedObject.targetObject as SteeringSensor;
isSpherical = serializedObject.FindProperty("isSpherical");
upDirection = serializedObject.FindProperty("upDirection");
resolution = serializedObject.FindProperty("resolution");
seek = serializedObject.FindProperty("seek");
interest = serializedObject.FindProperty("interest");
danger = serializedObject.FindProperty("danger");
vo = serializedObject.FindProperty("velocity");
decision = serializedObject.FindProperty("decision");
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
locomotionMode = serializedObject.FindProperty("LocomotionMode");
rigidBody = serializedObject.FindProperty("RigidBody");
characterController = serializedObject.FindProperty("CharacterController");
locomotion = serializedObject.FindProperty("locomotion");
}
protected override void OnPulsableGUI() {
EditorGUILayout.Space();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(isSpherical);
if (!sensor.IsSpherical) {
EditorGUILayout.PropertyField(upDirection);
}
EditorGUILayout.PropertyField(resolution);
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
EditorGUILayout.PropertyField(pulseUpdateFunction);
}
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(seek);
EditorGUILayout.Space();
SteeringSensor.ShowInterestGizmos = SteerSystemLayout("Interest", interest, SteeringSensor.ShowInterestGizmos);
EditorGUILayout.Space();
SteeringSensor.ShowDangerGizmos = SteerSystemLayout("Danger", danger, SteeringSensor.ShowDangerGizmos);
EditorGUILayout.Space();
SteeringSensor.ShowVelocityGizmos = SteerSystemLayout("Velocity", vo, SteeringSensor.ShowVelocityGizmos);
EditorGUILayout.Space();
SteeringSensor.ShowDecisionGizmos = SteerSystemLayout("Decision", decision, SteeringSensor.ShowDecisionGizmos);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(locomotionMode);
var locmode = (LocomotionMode)locomotionMode.enumValueIndex;
if (locmode != LocomotionMode.None) {
if (locmode == LocomotionMode.RigidBodyFlying || locmode == LocomotionMode.RigidBodyCharacter) {
EditorGUILayout.PropertyField(rigidBody);
} else {
EditorGUILayout.PropertyField(characterController);
}
EditorGUILayout.PropertyField(locomotion, true);
}
if (EditorGUI.EndChangeCheck()) {
EditorState.StopAllTesting();
}
displayErrors();
serializedObject.ApplyModifiedProperties();
}
void displayErrors() {
}
bool SteerSystemLayout(string labelText, SerializedProperty property, bool showGizmos) {
const float gizmoWidth = 70;
EditorGUILayout.BeginVertical(BackgroundStyle);
var r = EditorGUILayout.BeginHorizontal();
EditorGUI.PrefixLabel(r, new GUIContent(labelText));
// We don't want to stop testing after clicking the checkbox to show gizmos for a steering system. Suspend the change
// detection first.
if (EditorGUI.EndChangeCheck()) {
EditorState.StopAllTesting();
}
var prevShowGizmos = showGizmos;
showGizmos = GUI.Toggle(new Rect(r.width - gizmoWidth, r.y, gizmoWidth, EditorGUIUtility.singleLineHeight), showGizmos, " Gizmos");
if (IsTesting && prevShowGizmos != showGizmos) {
SceneView.RepaintAll();
}
EditorGUI.BeginChangeCheck();
r.width -= 30;
EditorGUILayout.PropertyField(property, new GUIContent(""));
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
return showGizmos;
}
static GUIStyle backgroundStyle;
static GUIStyle BackgroundStyle {
get {
if (backgroundStyle?.normal?.background == null) {
var texture = new Texture2D(1, 1);
texture.SetPixel(0, 0, new Color(0.2f, 0.2f, 0.2f, 0.15f));
texture.Apply();
backgroundStyle = new GUIStyle();
backgroundStyle.normal.background = texture;
}
return backgroundStyle;
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6d30917a0ea44e538d691caba14ba15f
timeCreated: 1506168822
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
using UnityEngine;
using UnityEditor;
using System.Collections;
namespace Micosmo.SensorToolkit
{
[CustomPropertyDrawer(typeof(TagSelectorAttribute))]
public class TagSelectorPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType == SerializedPropertyType.String)
{
EditorGUI.BeginProperty(position, label, property);
property.stringValue = EditorGUI.TagField(position, label, property.stringValue);
EditorGUI.EndProperty();
}
else
{
EditorGUI.PropertyField(position, property, label);
}
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1034f3df53154711b139c1d96798a066
timeCreated: 1498963683
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,133 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(TriggerSensor2D))]
[CanEditMultipleObjects]
public class TriggerSensor2DEditor : BaseSensorEditor<TriggerSensor2D> {
SerializedProperty ignoreList;
SerializedProperty tagFilterEnabled;
SerializedProperty tagFilter;
SerializedProperty detectionMode;
SerializedProperty signalProcessors;
SerializedProperty runInSafeMode;
SerializedProperty onDetected;
SerializedProperty onLostDetection;
SerializedProperty onSomeDetection;
SerializedProperty onNoDetection;
bool showEvents = false;
protected override bool canTest { get { return false; } }
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) {
return;
}
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
detectionMode = serializedObject.FindProperty("DetectionMode");
signalProcessors = serializedObject.FindProperty("signalProcessors");
runInSafeMode = serializedObject.FindProperty("runInSafeMode");
onDetected = serializedObject.FindProperty("OnDetected");
onLostDetection = serializedObject.FindProperty("OnLostDetection");
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
onNoDetection = serializedObject.FindProperty("OnNoDetection");
sensor.OnDetected.AddListener(detectionEventHandler);
sensor.OnLostDetection.AddListener(detectionEventHandler);
}
protected override void OnDisable() {
base.OnDisable();
sensor.OnDetected.RemoveListener(detectionEventHandler);
sensor.OnLostDetection.RemoveListener(detectionEventHandler);
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(ignoreList, true);
EditorGUILayout.PropertyField(tagFilterEnabled);
if (tagFilterEnabled.boolValue) {
EditorGUILayout.PropertyField(tagFilter, true);
}
EditorGUILayout.PropertyField(detectionMode);
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
EditorGUILayout.Space();
EditorGUILayout.PropertyField(runInSafeMode);
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onDetected);
EditorGUILayout.PropertyField(onLostDetection);
EditorGUILayout.PropertyField(onSomeDetection);
EditorGUILayout.PropertyField(onNoDetection);
}
displayErrors();
}
void displayErrors() {
var showTriggerError = !checkForTriggers();
var rb = sensor.GetComponent<Rigidbody2D>();
var showRigidbodyError = rb == null;
var showSleepmodeError = !showRigidbodyError && rb.sleepMode != RigidbodySleepMode2D.NeverSleep;
if (showTriggerError || showRigidbodyError) {
EditorGUILayout.Space();
}
if (showTriggerError) {
EditorGUILayout.HelpBox("Needs active Trigger Collider to detect GameObjects!", MessageType.Warning);
}
if (showRigidbodyError) {
EditorGUILayout.HelpBox("In order to detect GameObjects without RigidBodies the TriggerSensor must itself have a RigidBody! Recommend adding a kinematic RigidBody.", MessageType.Warning);
}
if (showSleepmodeError) {
EditorGUILayout.HelpBox("The rigidbody which owns the trigger collider should have its 'Sleeping Mode' parameter set to 'Never Sleep'", MessageType.Warning);
}
}
bool checkForTriggers() {
var hasRB = sensor.GetComponent<Rigidbody2D>() != null;
if (hasRB) {
foreach (Collider2D c in sensor.GetComponentsInChildren<Collider2D>()) {
if (c.enabled && c.isTrigger) return true;
}
} else {
foreach (Collider2D c in sensor.GetComponents<Collider2D>()) {
if (c.enabled && c.isTrigger) return true;
}
}
return false;
}
void detectionEventHandler(GameObject g, Sensor s) {
Repaint();
}
}
[CustomEditor(typeof(TriggerSensor2D.Safety))]
[CanEditMultipleObjects]
public class TriggerSensor2DSafetyEditor : Editor {
static string msg =
"This component was added because you have a Trigger Sensor using the 'Run In Safe Mode' " +
"option. It handles some quirks in Unity regarding missed trigger events because a " +
"collider is disabled. Its not efficient to use safe mode if you plan " +
"to use many Trigger Sensors. Please read the manual to learn how to avoid these quirks.";
public override void OnInspectorGUI() {
EditorGUILayout.HelpBox(msg, MessageType.Warning);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: aa0e59da74104ac795301aa622815811
timeCreated: 1491309965
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,130 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(TriggerSensor))]
[CanEditMultipleObjects]
public class TriggerSensorEditor : BaseSensorEditor<TriggerSensor>
{
SerializedProperty ignoreList;
SerializedProperty tagFilterEnabled;
SerializedProperty tagFilter;
SerializedProperty detectionMode;
SerializedProperty signalProcessors;
SerializedProperty runInSafeMode;
SerializedProperty onDetected;
SerializedProperty onLostDetection;
SerializedProperty onSomeDetection;
SerializedProperty onNoDetection;
bool showEvents = false;
protected override bool canTest { get { return false; } }
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) {
return;
}
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
detectionMode = serializedObject.FindProperty("DetectionMode");
signalProcessors = serializedObject.FindProperty("signalProcessors");
runInSafeMode = serializedObject.FindProperty("runInSafeMode");
onDetected = serializedObject.FindProperty("OnDetected");
onLostDetection = serializedObject.FindProperty("OnLostDetection");
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
onNoDetection = serializedObject.FindProperty("OnNoDetection");
sensor.OnDetected.AddListener(detectionEventHandler);
sensor.OnLostDetection.AddListener(detectionEventHandler);
}
protected override void OnDisable() {
base.OnDisable();
sensor.OnDetected.RemoveListener(detectionEventHandler);
sensor.OnLostDetection.RemoveListener(detectionEventHandler);
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(ignoreList, true);
EditorGUILayout.PropertyField(tagFilterEnabled);
if (tagFilterEnabled.boolValue) {
EditorGUILayout.PropertyField(tagFilter, true);
}
EditorGUILayout.PropertyField(detectionMode);
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
EditorGUILayout.Space();
EditorGUILayout.PropertyField(runInSafeMode);
EditorGUILayout.Space();
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
EditorGUILayout.PropertyField(onDetected);
EditorGUILayout.PropertyField(onLostDetection);
EditorGUILayout.PropertyField(onSomeDetection);
EditorGUILayout.PropertyField(onNoDetection);
}
displayErrors();
}
void displayErrors() {
var showTriggerError = !checkForTriggers();
var showRigidbodyError = (sensor.DetectionMode == DetectionModes.Colliders || sensor.DetectionMode == DetectionModes.Either) && sensor.GetComponent<Rigidbody>() == null;
if (showTriggerError || showRigidbodyError) {
EditorGUILayout.Space();
}
if (showTriggerError) {
EditorGUILayout.HelpBox("Needs active Trigger Collider to detect GameObjects!", MessageType.Warning);
}
if (showRigidbodyError) {
EditorGUILayout.HelpBox("In order to detect GameObjects without RigidBodies the TriggerSensor must itself have a RigidBody! Recommend adding a kinematic RigidBody.", MessageType.Warning);
}
}
bool checkForTriggers() {
var hasRB = sensor.GetComponent<Rigidbody>() != null;
if (hasRB) {
foreach (Collider c in sensor.GetComponentsInChildren<Collider>()) {
if (c.enabled && c.isTrigger) return true;
}
}
else {
foreach (Collider c in sensor.GetComponents<Collider>()) {
if (c.enabled && c.isTrigger) return true;
}
}
return false;
}
void detectionEventHandler(GameObject g, Sensor s) {
Repaint();
}
}
[CustomEditor(typeof(TriggerSensor.Safety))]
[CanEditMultipleObjects]
public class TriggerSensorSafetyEditor : Editor {
static string msg =
"This component was added because you have a Trigger Sensor using the 'Run In Safe Mode' " +
"option. It handles some quirks in Unity regarding missed trigger events because a " +
"collider is disabled. Its not efficient to use safe mode if you plan " +
"to use many Trigger Sensors. Please read the manual to learn how to avoid these quirks.";
public override void OnInspectorGUI() {
EditorGUILayout.HelpBox(msg, MessageType.Warning);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 872102644ea14cd1a954ae9c03e52353
timeCreated: 1489481290
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace Micosmo.SensorToolkit.Editors {
[CustomEditor(typeof(UserSignals))]
[CanEditMultipleObjects]
public class UserSignalsEditor : BaseSensorEditor<UserSignals> {
SerializedProperty inputSignals;
protected override bool canTest => true;
protected override void OnEnable() {
base.OnEnable();
if (serializedObject == null) {
return;
}
inputSignals = serializedObject.FindProperty("inputSignals");
}
protected override void InspectorParameters() {
EditorGUILayout.PropertyField(inputSignals, true);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 506414bd9150484293af313c454b24e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dd915a3f5ecc460a9fa2a23f58f13ca2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,301 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Micosmo.SensorToolkit {
public static partial class SensorGizmos {
public static void Label(Vector3 position, string text) {
#if UNITY_EDITOR
Handles.Label(position, text, EditorStyles.textField);
#endif
}
public static void Polyline(float width, params Vector3[] points) {
#if UNITY_EDITOR
Handles.DrawAAPolyLine(width, points);
#endif
}
public static void LineNoZTest(Vector3 from, Vector3 to) {
#if UNITY_EDITOR
var oldZTest = Handles.zTest;
Handles.zTest = UnityEngine.Rendering.CompareFunction.Disabled;
Handles.DrawLine(from, to);
Handles.zTest = oldZTest;
#endif
}
public static void ThickLineNoZTest(Vector3 from, Vector3 to, float thickness) {
#if UNITY_EDITOR
var oldZTest = Handles.zTest;
Handles.zTest = UnityEngine.Rendering.CompareFunction.Disabled;
Handles.DrawAAPolyLine(thickness, from, to);
Handles.zTest = oldZTest;
#endif
}
public static void SphereGizmo(Vector3 position, float radius) {
#if UNITY_EDITOR
PushMatrix(Matrix4x4.identity);
BackfaceSphereHandle(position, radius);
PopMatrix();
#endif
}
public static void CircleGizmo(Vector3 position, Vector3 normal, float radius) {
#if UNITY_EDITOR
PushMatrix(Matrix4x4.identity);
Handles.DrawWireDisc(position, normal, radius);
PopMatrix();
#endif
}
public static void CircleSector(Vector3 position, Vector3 direction, Vector3 normal, float angle, float radius) {
#if UNITY_EDITOR
PushMatrix(Matrix4x4.identity);
var r1 = Quaternion.AngleAxis(-angle, normal) * direction * radius;
var r2 = Quaternion.AngleAxis(angle, normal) * direction * radius;
Handles.DrawWireArc(position, normal, r1, angle * 2, radius);
//BackfaceArc(position, direction, normal, angle, radius);
Handles.DrawLine(position, position + r1);
Handles.DrawLine(position, position + r2);
PopMatrix();
#endif
}
public static void BackfaceArc(Vector3 position, Vector3 direction, Vector3 normal, float angle, float radius) {
#if UNITY_EDITOR
PushMatrix(Matrix4x4.identity);
if (Camera.current.orthographic) {
Vector3 normalized = Vector3.Cross(normal, Camera.current.transform.forward).normalized;
DrawTwoShadedWireDiscSector(position, normal, normalized, 180f,
Vector3.SignedAngle(normalized, Quaternion.AngleAxis(-angle, normal) * direction, normal),
Vector3.SignedAngle(normalized, Quaternion.AngleAxis(angle, normal) * direction, normal),
radius);
} else {
var cam2pos = Matrix.MultiplyPoint(position) - Camera.current.transform.position;
float sqrMagnitude = cam2pos.sqrMagnitude;
float rad2 = radius * radius;
float f1 = rad2 * rad2 / sqrMagnitude;
float num3 = f1 / rad2;
if (num3 < 1.0f) {
float a = Vector3.Angle(cam2pos, normal);
float num4 = Mathf.Tan((90f - Mathf.Min(a, 180f - a)) * (Mathf.PI / 180f));
float f2 = Mathf.Sqrt(f1 + num4 * num4 * f1) / radius;
if (f2 < 1.0f) {
float grazeAngle = Mathf.Asin(f2) * 57.29578f;
Vector3 normalized = Vector3.Cross(normal, cam2pos).normalized;
Vector3 from = Quaternion.AngleAxis(grazeAngle, normal) * normalized;
DrawTwoShadedWireDiscSector(position, normal, from, (90.0f - grazeAngle) * 2.0f,
Vector3.SignedAngle(from, Quaternion.AngleAxis(-angle, normal) * direction, normal),
Vector3.SignedAngle(from, Quaternion.AngleAxis(angle, normal) * direction, normal),
radius);
} else {
PushColor(SetA(Color, .2f));
var r1 = Quaternion.AngleAxis(-angle, normal) * direction * radius;
Handles.DrawWireArc(position, normal, r1, angle * 2, radius);
PopColor();
}
} else {
PushColor(SetA(Color, .2f));
var r1 = Quaternion.AngleAxis(-angle, normal) * direction * radius;
Handles.DrawWireArc(position, normal, r1, angle * 2, radius);
PopColor();
}
}
PopMatrix();
#endif
}
public static void CapsuleGizmo(Vector3 position, float radius, float height) {
#if UNITY_EDITOR
PushMatrix(Matrix * Matrix4x4.Translate(position));
height = Mathf.Abs(height);
var extent = Vector3.up * height / 2f;
HalfSphere(extent, radius);
PushMatrix(Matrix * Matrix4x4.Rotate(Quaternion.FromToRotation(Vector3.up, Vector3.down)));
HalfSphere(extent, radius);
PopMatrix();
Gizmos.DrawRay(extent + Vector3.right * radius, Vector3.down * height);
Gizmos.DrawRay(extent - Vector3.right * radius, Vector3.down * height);
Gizmos.DrawRay(extent + Vector3.forward * radius, Vector3.down * height);
Gizmos.DrawRay(extent - Vector3.forward * radius, Vector3.down * height);
PopMatrix();
#endif
}
public static void Capsule2DGizmo(Vector3 position, float radius, float height) {
#if UNITY_EDITOR
PushMatrix(Matrix * Matrix4x4.Translate(position));
height = Mathf.Max(radius * 2, height);
var pt1 = Vector3.up * (height - 2 * radius) / 2f;
var pt2 = -pt1;
Handles.color = Gizmos.color;
Handles.DrawWireArc(pt1, -Vector3.forward, Vector3.left, 180f, radius);
Handles.DrawWireArc(pt2, -Vector3.forward, Vector3.right, 180f, radius);
Gizmos.DrawRay(pt1 + Vector3.right * radius, Vector3.down * (height - 2 * radius));
Gizmos.DrawRay(pt1 - Vector3.right * radius, Vector3.down * (height - 2 * radius));
PopMatrix();
#endif
}
static void CameraFacingWireDisc(Vector3 position, float radius) {
#if UNITY_EDITOR
if (Camera.current.orthographic) {
Handles.DrawWireDisc(position, position - Matrix.inverse.MultiplyPoint(Camera.current.transform.position), radius);
} else {
var cam2pos = position - Matrix.inverse.MultiplyPoint(Camera.current.transform.position);
float sqrMagnitude = cam2pos.sqrMagnitude;
float rad2 = radius * radius;
float f1 = rad2 * rad2 / sqrMagnitude;
float num3 = f1 / rad2;
if (num3 < 1.0f) {
float num4 = Mathf.Sqrt(rad2 - f1);
Handles.DrawWireDisc(position - rad2 * cam2pos / sqrMagnitude, cam2pos, num4);
}
}
#endif
}
static Vector3[] vector3Array = new Vector3[6] {
Vector3.right, Vector3.up, Vector3.forward,
-Vector3.right, -Vector3.up, -Vector3.forward
};
static void BackfaceSphereHandle(Vector3 position, float radius) {
#if UNITY_EDITOR
CameraFacingWireDisc(position, radius);
var localCamPos = Matrix.inverse.MultiplyPoint(Camera.current.transform.position);
var localCamForward = Matrix.inverse.MultiplyVector(Camera.current.transform.forward);
if (Camera.current.orthographic) {
for (int index = 0; index < 3; ++index) {
Vector3 normalized = Vector3.Cross(vector3Array[index], localCamForward).normalized;
DrawTwoShadedWireDisc(position, vector3Array[index], normalized, 180f, radius);
}
} else {
var cam2pos = position - localCamPos;
float sqrMagnitude = cam2pos.sqrMagnitude;
float rad2 = radius * radius;
float f1 = rad2 * rad2 / sqrMagnitude;
float num3 = f1 / rad2;
for (int index = 0; index < 3; ++index) {
if (num3 < 1.0f) {
float a = Vector3.Angle(cam2pos, vector3Array[index]);
float num4 = Mathf.Tan((90f - Mathf.Min(a, 180f - a)) * (Mathf.PI / 180f));
float f2 = Mathf.Sqrt(f1 + num4 * num4 * f1) / radius;
if (f2 < 1.0f) {
float angle = Mathf.Asin(f2) * 57.29578f;
Vector3 normalized = Vector3.Cross(vector3Array[index], cam2pos).normalized;
Vector3 from = Quaternion.AngleAxis(angle, vector3Array[index]) * normalized;
DrawTwoShadedWireDisc(position, vector3Array[index], from, (90.0f - angle) * 2.0f, radius);
} else {
DrawTwoShadedWireDisc(position, vector3Array[index], radius);
}
} else {
DrawTwoShadedWireDisc(position, vector3Array[index], radius);
}
}
}
#endif
}
static void HalfSphere(Vector3 position, float radius) {
#if UNITY_EDITOR
Handles.DrawWireDisc(position, Vector3.up, radius);
Handles.DrawWireArc(position, Vector3.right, Vector3.back, 180f, radius);
Handles.DrawWireArc(position, Vector3.forward, Vector3.right, 180f, radius);
#endif
}
static void DrawTwoShadedWireDisc(Vector3 position, Vector3 axis, float radius) {
#if UNITY_EDITOR
PushColor(SetA(Color, .2f));
Handles.DrawWireDisc(position, axis, radius);
PopColor();
#endif
}
static void DrawTwoShadedWireDisc(Vector3 position, Vector3 axis, Vector3 from, float degrees, float radius) {
#if UNITY_EDITOR
Handles.DrawWireArc(position, axis, from, degrees, radius);
PushColor(SetA(Color, .2f));
Handles.DrawWireArc(position, axis, from, degrees - 360f, radius);
PopColor();
#endif
}
static void DrawTwoShadedWireDiscSector(Vector3 position, Vector3 axis, Vector3 from, float degrees, float startDegrees, float endDegrees, float radius) {
#if UNITY_EDITOR
if (endDegrees < startDegrees) {
endDegrees += 360f;
}
var currPos = startDegrees;
var i = 0;
while (currPos < endDegrees && i < 4) {
var isFront = currPos >= 0 && currPos < degrees;
var isBack = !isFront;
if (isBack) {
PushColor(SetA(Color, .2f));
}
var nextPos = currPos;
if (currPos < 0f) {
nextPos = Mathf.Min(0, endDegrees);
} else if (currPos < 360f) {
nextPos = isBack
? Mathf.Min(endDegrees, 360f)
: Mathf.Min(endDegrees, degrees);
} else {
nextPos = endDegrees;
}
var delta = (nextPos - currPos);
Handles.DrawWireArc(position, axis, Quaternion.AngleAxis(currPos, axis) * from, delta, radius);
currPos = nextPos;
i += 1;
if (isBack) {
PopColor();
}
}
/*Debug.Log($"start: {startDegrees} -- end: {endDegrees} -- degrees: {degrees}");
PushColor(Color.green);
Gizmos.DrawCube(position + from*radius, Vector3.one * .2f);
PopColor();
PushColor(Color.red);
Gizmos.DrawCube(position + Quaternion.AngleAxis(degrees, axis) * from*radius, Vector3.one * .2f);
PopColor();
PushColor(Color.blue);
Gizmos.DrawCube(position + Quaternion.AngleAxis(startDegrees, axis) * from * radius, Vector3.one * .2f);
PopColor();*/
#endif
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 556127bc1b274ab4992983fa6b421428
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,96 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Micosmo.SensorToolkit {
public static partial class SensorGizmos {
public static readonly Color DarkPurple = new Color(.2f, .169f, .255f);
public static readonly Color Green = new Color(0.345f, 0.737f, 0.51f);
public static readonly Color Red = new Color(0.922f, 0.318f, 0.376f);
public static readonly Color Yellow = new Color(0.949f, 0.816f, 0.482f);
public static readonly Color Blue = new Color(0.757f, 0.816f, 0.933f);
public static readonly Color Cyan = new Color(0.698f, 0.125f, 0.467f);
static readonly List<Matrix4x4> matrixStack = new List<Matrix4x4>();
public static Matrix4x4 Matrix => matrixStack.Count > 0 ? matrixStack[matrixStack.Count - 1] : Matrix4x4.identity;
public static void PushMatrix(Matrix4x4 m) {
matrixStack.Add(m);
SetMatrix(m);
}
public static void PopMatrix() {
if (matrixStack.Count > 0) {
matrixStack.RemoveAt(matrixStack.Count - 1);
}
SetMatrix(Matrix);
}
static void SetMatrix(Matrix4x4 m) {
if (matrixStack.Count > 0) {
matrixStack[matrixStack.Count - 1] = m;
}
Gizmos.matrix = m;
#if UNITY_EDITOR
Handles.matrix = m;
#endif
}
static readonly List<Color> colorStack = new List<Color>();
public static Color Color => colorStack.Count > 0 ? colorStack[colorStack.Count - 1] : Color.cyan;
public static void PushColor(Color c) {
colorStack.Add(c);
SetColor(c);
}
public static void PopColor() {
if (colorStack.Count > 0) {
colorStack.RemoveAt(colorStack.Count - 1);
}
SetColor(Color);
}
public static void WithColor(Color c, System.Action action) {
PushColor(c);
action();
PopColor();
}
static void SetColor(Color c) {
if (colorStack.Count > 0) {
colorStack[colorStack.Count - 1] = c;
}
Gizmos.color = c;
#if UNITY_EDITOR
Handles.color = c;
#endif
}
static Color SetA(Color c, float a) => new Color(c.r, c.g, c.b, a);
public static Color LerpColour(Color[] pts, float t) {
var i = Mathf.FloorToInt(t * pts.Length);
var frac = (t * pts.Length) - i;
if (i < 0) {
return pts[0];
}
if (i >= pts.Length-1) {
return pts[pts.Length - 1];
}
return Color.Lerp(pts[i], pts[i + 1], frac);
}
public static Color ParseHexColour(string c) {
var col = Color.magenta;
ColorUtility.TryParseHtmlString(c, out col);
return col;
}
public static Color[] ParseHexColours(string[] colours) {
var result = new Color[colours.Length];
for (var i = 0; i < colours.Length; i++) {
result[i] = ParseHexColour(colours[i]);
}
return result;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36d4944765754a92becd2e54904be20f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,209 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Micosmo.SensorToolkit {
public static partial class SensorGizmos {
public static void FOVGizmo(ReferenceFrame frame, float length, float horizAngle, float vertAngle) {
var m = Matrix4x4.TRS(frame.Position, Quaternion.LookRotation(frame.Forward, frame.Up), Vector3.one);
PushMatrix(m);
if (horizAngle != 0) {
PushColor(STPrefs.LOSFovColour);
CircleSector(frame.Position, frame.Forward, frame.Up, horizAngle, length);
PopColor();
}
if (vertAngle != 0) {
PushColor(STPrefs.LOSFovColour);
CircleSector(frame.Position, frame.Forward, frame.Right, vertAngle, length);
PopColor();
}
PopMatrix();
}
public static void DetectedObjectGizmo(Bounds bounds) {
#if UNITY_EDITOR
PushMatrix(Matrix4x4.identity);
PushColor(STPrefs.SignalBoundsColour);
if (bounds.extents != Vector3.zero) {
Gizmos.DrawWireCube(bounds.center, bounds.size);
}
var texture = AssetDatabase.LoadAssetAtPath<Texture>("Assets/Gizmos/SensorToolkit/LOS-VISIBLE.png");
var size = new Vector2(texture.width, texture.height) / HandleUtility.GetHandleSize(bounds.center) / 3f;
size = Vector2.Min(size, new Vector2(texture.width, texture.height));
//size = Vector2.Max(size, Vector2.one * 12f);
if (size.x > 8f && STPrefs.ShowEyeIconInSignal) {
Handles.Label(bounds.center, new GUIContent() { image = texture }, new GUIStyle() { contentOffset = new Vector2(-size.x / 2f, -size.y / 2f), fixedHeight = size.x, fixedWidth = size.y });
}
PopColor();
PopMatrix();
#endif
}
public static void RaycastHitGizmo(Vector3 point, Vector3 normal, bool isObstruction) {
#if UNITY_EDITOR
PushMatrix(Matrix4x4.identity);
PushColor(isObstruction ? STPrefs.CastingBlockedRayColour : STPrefs.CastingRayColour);
Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
var screenSize = HandleUtility.GetHandleSize(point);
var offset = (normal * screenSize * 0.01f);
Handles.DrawSolidDisc(point + offset, normal, screenSize * 0.1f);
SetColor(SetA(Color, 0.2f));
Handles.zTest = UnityEngine.Rendering.CompareFunction.Greater;
Handles.DrawSolidDisc(point + offset, normal, screenSize * 0.1f);
SetColor(STPrefs.RayHitNormalColour);
Gizmos.DrawRay(point, normal * screenSize * 0.5f);
PopColor();
PopMatrix();
#endif
}
public static void SpherecastGizmo(Ray ray, float length, Quaternion rotation, float radius, bool isObstructed) {
#if UNITY_EDITOR
PushMatrix(Matrix4x4.TRS(ray.origin, rotation, Vector3.one));
PushColor(isObstructed ? STPrefs.CastingBlockedRayColour : STPrefs.CastingShapeColour);
BackfaceSphereHandle(Vector3.zero, radius);
SetColor(isObstructed ? STPrefs.CastingBlockedRayColour : STPrefs.CastingRayColour);
BackfaceSphereHandle(ray.direction * length, radius);
var localLeft = Vector3.Cross(Vector3.up, ray.direction).normalized;
var localUp = Vector3.Cross(localLeft, ray.direction).normalized;
Gizmos.DrawRay(localLeft * radius, ray.direction * length);
Gizmos.DrawRay(-localLeft * radius, ray.direction * length);
Gizmos.DrawRay(localUp * radius, ray.direction * length);
Gizmos.DrawRay(-localUp * radius, ray.direction * length);
PopColor();
PopMatrix();
#endif
}
public static void CirclecastGizmo(Ray ray, float length, Quaternion rotation, float radius, bool isObstructed) {
#if UNITY_EDITOR
PushMatrix(Matrix4x4.TRS(ray.origin, rotation, Vector3.one));
PushColor(isObstructed ? STPrefs.CastingBlockedRayColour : STPrefs.CastingShapeColour);
var height = length + radius;
var pt1 = Vector3.zero;
var pt2 = ray.direction*length;
Handles.DrawWireDisc(pt1, -Vector3.forward, radius);
SetColor(isObstructed ? STPrefs.CastingBlockedRayColour : STPrefs.CastingRayColour);
Handles.DrawWireDisc(pt2, -Vector3.forward, radius);
var right = Quaternion.LookRotation(ray.direction, Vector3.forward) * Vector3.right;
Gizmos.DrawRay(pt1 + right * radius, ray.direction * (height - radius));
Gizmos.DrawRay(pt1 - right * radius, ray.direction * (height - radius));
PopColor();
PopMatrix();
#endif
}
public static void BoxcastGizmo(Ray ray, float length, Quaternion rotation, Vector3 halfExtents, bool isObstructed) {
#if UNITY_EDITOR
PushMatrix(Matrix4x4.TRS(ray.origin, rotation, Vector3.one));
PushColor(isObstructed ? STPrefs.CastingBlockedRayColour : STPrefs.CastingShapeColour);
Gizmos.DrawWireCube(Vector3.zero, halfExtents * 2f);
SetColor(isObstructed ? STPrefs.CastingBlockedRayColour : STPrefs.CastingRayColour);
Gizmos.DrawWireCube(ray.direction * length, halfExtents * 2f);
Gizmos.DrawRay(Vector3.up * halfExtents.y + Vector3.right * halfExtents.x + Vector3.forward * halfExtents.z, ray.direction * length);
Gizmos.DrawRay(Vector3.up * halfExtents.y + Vector3.right * halfExtents.x - Vector3.forward * halfExtents.z, ray.direction * length);
Gizmos.DrawRay(Vector3.up * halfExtents.y - Vector3.right * halfExtents.x + Vector3.forward * halfExtents.z, ray.direction * length);
Gizmos.DrawRay(Vector3.up * halfExtents.y - Vector3.right * halfExtents.x - Vector3.forward * halfExtents.z, ray.direction * length);
Gizmos.DrawRay(-Vector3.up * halfExtents.y + Vector3.right * halfExtents.x + Vector3.forward * halfExtents.z, ray.direction * length);
Gizmos.DrawRay(-Vector3.up * halfExtents.y + Vector3.right * halfExtents.x - Vector3.forward * halfExtents.z, ray.direction * length);
Gizmos.DrawRay(-Vector3.up * halfExtents.y - Vector3.right * halfExtents.x + Vector3.forward * halfExtents.z, ray.direction * length);
Gizmos.DrawRay(-Vector3.up * halfExtents.y - Vector3.right * halfExtents.x - Vector3.forward * halfExtents.z, ray.direction * length);
PopColor();
PopMatrix();
#endif
}
public static void CapsulecastGizmo(Ray ray, float length, Quaternion rotation, float radius, float height, bool isObstructed) {
#if UNITY_EDITOR
PushMatrix(Matrix4x4.TRS(ray.origin, rotation, Vector3.one));
PushColor(isObstructed ? STPrefs.CastingBlockedRayColour : STPrefs.CastingShapeColour);
height = Mathf.Abs(height);
CapsuleGizmo(Vector3.zero, radius, height);
SetColor(isObstructed ? STPrefs.CastingBlockedRayColour : STPrefs.CastingRayColour);
CapsuleGizmo(ray.direction * length, radius, height);
var pt1 = Vector3.up * height / 2f;
var pt2 = -pt1;
Gizmos.DrawRay(pt1 + Vector3.right * radius, ray.direction * length);
Gizmos.DrawRay(pt1 - Vector3.right * radius, ray.direction * length);
Gizmos.DrawRay(pt1 + Vector3.up * radius, ray.direction * length);
Gizmos.DrawRay(pt1 + Vector3.forward * radius, ray.direction * length);
Gizmos.DrawRay(pt1 - Vector3.forward * radius, ray.direction * length);
Gizmos.DrawRay(pt2 + Vector3.right * radius, ray.direction * length);
Gizmos.DrawRay(pt2 - Vector3.right * radius, ray.direction * length);
Gizmos.DrawRay(pt2 - Vector3.up * radius, ray.direction * length);
Gizmos.DrawRay(pt2 + Vector3.forward * radius, ray.direction * length);
Gizmos.DrawRay(pt2 - Vector3.forward * radius, ray.direction * length);
PopColor();
PopMatrix();
#endif
}
public static void Capsule2DcastGizmo(Ray ray, float length, Quaternion rotation, float radius, float height, bool isObstructed) {
#if UNITY_EDITOR
PushMatrix(Matrix4x4.TRS(ray.origin, rotation, Vector3.one));
PushColor(isObstructed ? STPrefs.CastingBlockedRayColour : STPrefs.CastingShapeColour);
height = Mathf.Max(radius * 2, height);
Capsule2DGizmo(Vector3.zero, radius, height);
SetColor(isObstructed ? STPrefs.CastingBlockedRayColour : STPrefs.CastingRayColour);
Capsule2DGizmo(ray.direction * length, radius, height);
var pt1 = Vector3.up * (height / 2f - radius);
var pt2 = -pt1;
Gizmos.DrawRay(pt1 + Vector3.right * radius, ray.direction * length);
Gizmos.DrawRay(pt1 - Vector3.right * radius, ray.direction * length);
Gizmos.DrawRay(pt1 + Vector3.up * radius, ray.direction * length);
Gizmos.DrawRay(pt2 + Vector3.right * radius, ray.direction * length);
Gizmos.DrawRay(pt2 - Vector3.right * radius, ray.direction * length);
Gizmos.DrawRay(pt2 - Vector3.up * radius, ray.direction * length);
PopColor();
PopMatrix();
#endif
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: baf797366091494483913e6f9dbe701a
timeCreated: 1601249413
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace Micosmo.SensorToolkit {
public interface IRayCastingSensor {
RayHit GetDetectionRayHit(GameObject detectedGameObject);
bool IsObstructed { get; }
RayHit GetObstructionRayHit();
// Event fired at the time the sensor is obstructed when before it was unobstructed
ObstructionEvent OnObstruction { get; }
// Event fired at the time the sensor is unobstructed when before it was obstructed
ObstructionEvent OnClear { get; }
}
[System.Serializable]
public class ObstructionEvent : UnityEvent<IRayCastingSensor> { }
/**
* A common representation for ray hits that combines both RaycastHit and
* RaycastHit2D. This exists to provide some consistency between the RaySensor
* and RaySensor2D interfaces, and also the Arc Sensors.
*/
public struct RayHit : IEquatable<RayHit> {
public static RayHit None => new RayHit() { Distance = -1 };
public bool IsObstructing;
public Vector3 Point;
public Vector3 Normal;
public float Distance;
public float DistanceFraction;
public Collider Collider;
public Collider2D Collider2D;
public GameObject GameObject =>
Collider != null ? Collider.gameObject :
Collider2D != null ? Collider2D.gameObject :
null;
public bool Equals(RayHit other) {
return IsObstructing == other.IsObstructing &&
Point.Equals(other.Point) &&
Normal.Equals(other.Normal) &&
Distance == other.Distance &&
DistanceFraction == other.DistanceFraction &&
Collider == other.Collider &&
Collider2D == other.Collider2D;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc5e680df93e41b38a1cf4905da84b14
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
public interface ISteeringSensor {
GameObject gameObject { get; }
Transform transform { get; }
SteerSeek Seek { get; }
SteerInterest Interest { get; }
SteerDanger Danger { get; }
SteerVO Velocity { get; }
SteerDecision Decision { get; }
LocomotionSystem Locomotion { get; }
bool IsDestinationReached { get; }
bool IsSeeking { get; }
void SeekTo(Transform destination, float distanceOffset = 0f);
void SeekTo(Vector3 destination, float distanceOffset = 0f);
void ArriveTo(Transform destination, float distanceOffset = 0f);
void ArriveTo(Vector3 destination, float distanceOffset = 0f);
void SeekDirection(Vector3 direction);
void Wander();
void Stop();
Vector3 GetSteeringVector();
float GetSpeedCandidate(Vector3 direction);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dce203f51e872c14bbbfd442e90725fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 10c336ad5c4c43b4f86ea32f414cad03
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,215 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
[System.Serializable]
public struct ReferenceFrame {
public Vector3 Position;
public Vector3 Forward, Right, Up;
public ReferenceFrame(Vector3 position, Vector3 forward, Vector3 right, Vector3 up) {
Position = position; Forward = forward; Right = right; Up = up;
}
public static ReferenceFrame Identity => new ReferenceFrame { Forward = Vector3.forward, Right = Vector3.right, Up = Vector3.up };
public static ReferenceFrame Planar(Vector3 position, Vector3 forward, Vector3 right) =>
new ReferenceFrame(position, forward, right, default);
public static ReferenceFrame From(Transform transform) =>
new ReferenceFrame(transform.position, transform.forward, transform.right, transform.up);
public static ReferenceFrame From(Vector3 position, Quaternion rotation) =>
new ReferenceFrame(position, rotation * Vector3.forward, rotation * Vector3.right, rotation * Vector3.up);
public static ReferenceFrame From(Transform transform, Vector2 horizDir) {
var forward = transform.forward;
if (horizDir == Vector2.zero) {
horizDir = Vector2.right;
}
horizDir = horizDir.normalized;
var right = horizDir.x * transform.right + horizDir.y * transform.up;
var up = Vector3.Cross(forward, right);
return new ReferenceFrame(transform.position, forward, right, up);
}
public ViewAngles AngleTo(Bounds bounds) => AngleUtils.ViewAnglesToBounds(this, bounds);
public SphericalCoords SphericalCoordsTo(Bounds bounds) => AngleUtils.SphericalCoordsToBounds(this, bounds);
public ViewAngles AngleTo(Vector3 target) => AngleUtils.ViewAnglesToPoint(this, target);
public SphericalCoords SphericalCoordsTo(Vector3 target) => AngleUtils.SphericalCoordsToPoint(this, target);
public ReferenceFrame Push(Vector3 nextPosition, Vector3 nextForward) {
// Minimizes twist rotation to push the frame forward and align with a new forward direction
var n0 = Up;
var t0 = Forward;
var t1 = nextForward.normalized;
var v1 = nextPosition - Position;
var c1 = v1.sqrMagnitude;
var n0_l = n0 - (2 / c1) * Vector3.Dot(v1, n0) * v1;
var t0_l = t0 - (2 / c1) * Vector3.Dot(v1, t0) * v1;
var v2 = t1 - t0_l;
var c2 = v2.sqrMagnitude;
var n1 = n0_l - (2 / c2) * Vector3.Dot(v2, n0_l) * v2;
return From(nextPosition, Quaternion.LookRotation(t1, n1));
}
public Vector3 LocalToWorld(Vector3 localPosition) => Position + (Right * localPosition.x) + (Up * localPosition.y) + (Forward * localPosition.z);
public Vector3 WorldToLocal(Vector3 worldPosition) {
var delta = worldPosition - Position;
return new Vector3(Vector3.Dot(delta, Right), Vector3.Dot(delta, Up), Vector3.Dot(delta, Forward));
}
public void DrawGizmos(float size) {
var length = 10f * size;
var thickness = 2f * size;
var that = this;
SensorGizmos.WithColor(Color.blue, () => SensorGizmos.ThickLineNoZTest(that.Position, that.Position + that.Forward * length, thickness));
SensorGizmos.WithColor(Color.red, () => SensorGizmos.ThickLineNoZTest(that.Position, that.Position + that.Right * length, thickness));
SensorGizmos.WithColor(Color.green, () => SensorGizmos.ThickLineNoZTest(that.Position, that.Position + that.Up * length, thickness));
}
}
[System.Serializable]
public struct ViewAngles {
public float HorizAngle;
public float VertAngle;
public ViewAngles Abs => new ViewAngles(Mathf.Abs(HorizAngle), Mathf.Abs(VertAngle));
public ViewAngles(float horizAngle, float vertAngle) {
HorizAngle = horizAngle; VertAngle = vertAngle;
}
public float GetCentralAngle() {
var horiz = HorizAngle * Mathf.Deg2Rad;
var vert = VertAngle * Mathf.Deg2Rad;
return Mathf.Rad2Deg * Mathf.Acos(Mathf.Cos(horiz) * Mathf.Cos(vert));
}
public Vector3 ToCartesian(float distance) {
var horiz = HorizAngle * Mathf.Deg2Rad;
var vert = VertAngle * Mathf.Deg2Rad;
var cosVert = Mathf.Cos(vert);
var sinVert = Mathf.Sin(vert);
return new Vector3(cosVert * Mathf.Sin(horiz), sinVert, cosVert * Mathf.Cos(horiz)) * distance;
}
}
[System.Serializable]
public struct SphericalCoords {
public ViewAngles Angles;
public float Radius;
public SphericalCoords(ViewAngles angles, float radius) {
Angles = angles; Radius = radius;
}
public Vector3 ToCartesian() => Angles.ToCartesian(Radius);
}
public class AngleUtils {
public static float PlanarAngleToPoint(ReferenceFrame frame, Vector3 target) {
var delta = (target - frame.Position);
var proj = Vector3.Dot(delta, frame.Right);
var dist = Vector3.Dot(delta, frame.Forward);
var tri = new CircleInscribedTriangle(new Vector2(proj, dist));
return tri.GetAngle();
}
public static float PlanarAngleToBounds(ReferenceFrame frame, Bounds bounds) {
var center = bounds.center;
var extents = bounds.extents;
CircleInscribedTriangle cwTri = default;
CircleInscribedTriangle acwTri = default;
for (int i = 0; i < 8; i++) {
var xSign = (i & 1) == 0 ? 1 : -1;
var ySign = (i & 2) == 0 ? 1 : -1;
var zSign = (i & 4) == 0 ? 1 : -1;
var point = center + new Vector3(extents.x * xSign, extents.y * ySign, extents.z * zSign);
var delta = (point - frame.Position);
var proj = Vector3.Dot(delta, frame.Right);
var dist = Vector3.Dot(delta, frame.Forward);
var tri = new CircleInscribedTriangle(new Vector2(proj, dist));
cwTri = CircleInscribedTriangle.NearestClockwise(cwTri, tri);
acwTri = CircleInscribedTriangle.NearestAntiClockwise(acwTri, tri);
}
var a1 = cwTri.GetAngle();
var a2 = acwTri.GetAngle();
if (a1 >= 0 && a2 <= 0 && (a1 - a2 < 180f)) {
return 0f;
}
var nearest = (Mathf.Abs(a1) < Mathf.Abs(a2)) ? a1 : a2;
return nearest;
}
public static ViewAngles ViewAnglesToPoint(ReferenceFrame frame, Vector3 target) {
var horizAngle = PlanarAngleToPoint(frame, target);
var toTarget = target - frame.Position;
var projToTarget = toTarget - (Vector3.Dot(toTarget, frame.Up) * frame.Up);
var vertAngle = PlanarAngleToPoint(ReferenceFrame.Planar(frame.Position, projToTarget.normalized, frame.Up), target);
return new ViewAngles(horizAngle, vertAngle);
}
public static SphericalCoords SphericalCoordsToPoint(ReferenceFrame frame, Vector3 target) {
var angles = ViewAnglesToPoint(frame, target);
var radius = Vector3.Distance(frame.Position, target);
return new SphericalCoords(angles, radius);
}
public static ViewAngles ViewAnglesToBounds(ReferenceFrame frame, Bounds bounds) {
var horizAngle = PlanarAngleToBounds(frame, bounds);
var toTarget = (bounds.center - frame.Position);
var projToTarget = toTarget - (Vector3.Dot(toTarget, frame.Up) * frame.Up);
var vertAngle = PlanarAngleToBounds(ReferenceFrame.Planar(frame.Position, projToTarget.normalized, frame.Up), bounds);
return new ViewAngles(horizAngle, vertAngle);
}
public static SphericalCoords SphericalCoordsToBounds(ReferenceFrame frame, Bounds bounds) {
var angles = ViewAnglesToBounds(frame, bounds);
var radius = Mathf.Sqrt(bounds.SqrDistance(frame.Position));
return new SphericalCoords(angles, radius);
}
struct CircleInscribedTriangle {
Vector2 coords;
bool isValid => coords != Vector2.zero;
public CircleInscribedTriangle(Vector2 coords) {
this.coords = coords.normalized;
}
public float GetAngle() => -Mathf.Atan2(-coords.x, coords.y) * Mathf.Rad2Deg;
public static CircleInscribedTriangle NearestClockwise(CircleInscribedTriangle tri1, CircleInscribedTriangle tri2) {
if (!tri1.isValid) {
return tri2;
} else if (!tri2.isValid) {
return tri1;
}
return tri1.IsNearestClockwise(tri2) ? tri1 : tri2;
}
public static CircleInscribedTriangle NearestAntiClockwise(CircleInscribedTriangle tri1, CircleInscribedTriangle tri2) {
if (!tri1.isValid) {
return tri2;
} else if (!tri2.isValid) {
return tri1;
}
return tri1.IsNearestClockwise(tri2) ? tri2 : tri1;
}
int GetClockwiseQuadrant() => coords.x > 0 ? (coords.y > 0 ? 0 : 1) : (coords.y > 0 ? 3 : 2);
int GetAntiClockwiseQuadrant() => 3 - GetClockwiseQuadrant();
bool IsNearestClockwise(CircleInscribedTriangle other) {
var myQuad = GetClockwiseQuadrant();
var otherQuad = other.GetClockwiseQuadrant();
if (myQuad < otherQuad) {
return true;
} else if (myQuad > otherQuad) {
return false;
}
switch (myQuad) {
case 0:
return coords.x < other.coords.x;
case 1:
return coords.y > other.coords.y;
case 2:
return coords.x > other.coords.x;
case 3:
return coords.y < other.coords.y;
default:
return false;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bcd6b545e44c45f48b19c363eb6497d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,231 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
public interface ILOSResult {
Signal OutputSignal { get; }
float Visibility { get; }
bool IsVisible { get; }
List<LOSRayResult> Rays { get; }
}
public struct LOSRayResult {
public Vector3 OriginPoint;
public Vector3 TargetPoint;
public Transform TargetTransform;
public RayHit RayHit;
public float VisibilityMultiplier;
public bool IsObstructed => RayHit.IsObstructing;
public float Visibility => IsObstructed ? 0f : VisibilityMultiplier;
}
public enum ScalingMode { Step, LinearDecay, Curve }
[System.Serializable]
public struct ScalingFunction {
public ScalingMode Mode;
public AnimationCurve Curve;
public float Evaluate(float t) {
if (Mode == ScalingMode.Step) {
return t < 1f ? 1f : 0f;
} if (Mode == ScalingMode.LinearDecay) {
return 1f - Mathf.Clamp01(t);
} else {
return Curve.Evaluate(Mathf.Clamp01(t));
}
}
public static ScalingFunction Default() =>
new ScalingFunction() {
Mode = ScalingMode.Step,
Curve = new AnimationCurve(new Keyframe(0,1), new Keyframe(.5f,1), new Keyframe(1f,0))
};
}
public enum FOVConstraintMethod { BoundingBox, PerRay }
public enum PointSamplingMethod { Fast, Quality }
public abstract class BaseLOSTest : ILOSResult {
public class ConfigParams {
public Signal InputSignal;
public List<Collider> OwnedColliders;
public List<Collider2D> OwnedCollider2Ds;
public ReferenceFrame Frame;
public float MinimumVisibility;
public LayerMask BlocksLineOfSight;
public bool IgnoreTriggerColliders;
public PointSamplingMethod PointSamplingMethod;
public bool TestLOSTargetsOnly;
public int NumberOfRays;
public bool MovingAverageEnabled;
public int MovingAverageWindowSize;
public bool LimitDistance;
public float MaxDistance;
public ScalingFunction VisibilityByDistance;
public bool LimitViewAngle;
public float MaxHorizAngle;
public ScalingFunction VisibilityByHorizAngle;
public float MaxVertAngle;
public ScalingFunction VisibilityByVertAngle;
public FOVConstraintMethod FOVConstraintMethod;
}
public ConfigParams Config => config;
protected ConfigParams config = new ConfigParams();
public Signal OutputSignal { get; private set; }
public float Visibility { get; private set; }
public bool IsVisible { get; private set; } // Possible to have Visibility=0 and still be visible
public List<LOSRayResult> Rays { get; } = new List<LOSRayResult>();
Signal prevInputSignal;
ComponentCache losTargetsCache;
List<Vector3> generatedPoints = new List<Vector3>();
MovingAverageFilter avgFilter = new MovingAverageFilter(1);
public void Reset() {
avgFilter.Clear();
Rays.Clear();
generatedPoints.Clear();
Visibility = 0f;
Clear();
}
public bool PerformTest() {
Rays.Clear();
generatedPoints.Clear();
Clear();
// Need to initialize output signal so .Object is populated
OutputSignal = new Signal() {
Object = config.InputSignal.Object,
Shape = config.InputSignal.Shape,
Strength = 0f
};
IsVisible = false;
Visibility = 0f;
if (!ReferenceEquals(config.InputSignal.Object, prevInputSignal.Object)) {
avgFilter.Clear();
}
prevInputSignal = config.InputSignal;
var visibilityScale = Config.FOVConstraintMethod == FOVConstraintMethod.BoundingBox ? GetVisibilityScale() : 1f;
if (visibilityScale <= 0f) {
return false;
}
var isUsingGeneratedPoints = false;
var losTargets = losTargetsCache.GetComponent<LOSTargets>(config.InputSignal.Object);
if (losTargets == null || losTargets.Targets == null || losTargets.Targets.Count == 0) {
if (config.TestLOSTargetsOnly) {
return IsVisible;
}
if (IsInsideSignal()) {
// If I'm inside the bounds of the target signal then I can see it.
Visibility = 1f;
IsVisible = true;
OutputSignal = new Signal {
Object = config.InputSignal.Object,
Shape = config.InputSignal.Shape,
Strength = config.InputSignal.Strength
};
return IsVisible;
}
GenerateTestPoints(generatedPoints);
isUsingGeneratedPoints = true;
if (generatedPoints.Count == 0) {
return false; // Couldn't generate any testpoints, therefore not visible.
}
}
if (isUsingGeneratedPoints) {
foreach (var pt in generatedPoints) {
var trans = config.InputSignal.Object.transform;
var result = TestPoint(pt);
result.VisibilityMultiplier = visibilityScale;
if (Config.FOVConstraintMethod == FOVConstraintMethod.PerRay) {
result.VisibilityMultiplier *= GetRayVisibilityScale(pt);
}
Rays.Add(result);
}
} else {
foreach (var target in losTargets.Targets) {
var result = TestTransform(target);
result.VisibilityMultiplier = visibilityScale;
if (Config.FOVConstraintMethod == FOVConstraintMethod.PerRay) {
result.VisibilityMultiplier *= GetRayVisibilityScale(target.position);
}
Rays.Add(result);
}
}
var rayVisibilitySum = 0f;
foreach (var ray in Rays) {
rayVisibilitySum += ray.Visibility;
}
Visibility = (rayVisibilitySum / Rays.Count);
if (config.MovingAverageEnabled) {
avgFilter.Size = config.MovingAverageWindowSize;
avgFilter.AddSample(Visibility);
Visibility = avgFilter.Value;
} else {
avgFilter.Clear();
}
IsVisible = Visibility >= config.MinimumVisibility;
OutputSignal = new Signal() {
Object = config.InputSignal.Object,
Shape = config.InputSignal.Shape,
Strength = IsVisible ? config.InputSignal.Strength * Visibility : 0f
};
return IsVisible;
}
public virtual void DrawGizmos() {
foreach (var result in Rays) {
Gizmos.color = SensorGizmos.LerpColour(STPrefs.RayVisibilityGradient, 1f - result.VisibilityMultiplier);
if (result.IsObstructed) {
Gizmos.DrawLine(result.OriginPoint, result.RayHit.Point);
Gizmos.color = STPrefs.CastingBlockedRayColour;
Gizmos.DrawLine(result.RayHit.Point, result.TargetPoint);
Gizmos.DrawCube(result.TargetPoint, Vector3.one * 0.02f);
} else {
Gizmos.DrawLine(result.OriginPoint, result.TargetPoint);
Gizmos.DrawCube(result.TargetPoint, Vector3.one * 0.02f);
}
}
}
LOSRayResult TestTransform(Transform testTransform) {
var result = TestPoint(testTransform.position);
result.TargetTransform = testTransform;
return result;
}
protected abstract LOSRayResult TestPoint(Vector3 testPoint);
protected abstract bool IsInsideSignal();
protected abstract void GenerateTestPoints(List<Vector3> storeIn);
protected abstract float GetVisibilityScale();
protected abstract float GetRayVisibilityScale(Vector3 target);
protected abstract void Clear();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5d1cad1996ab8c24b825fcc8b2af9f7d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,81 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
public struct CuttingPlane {
public Vector3 Point;
public Vector3 Normal;
public void Cut(List<Triangle> triangleList) {
for (int i = triangleList.Count - 1; i >= 0; i--) {
var tri = triangleList[i];
Triangle slice1, slice2;
var nSlices = tri.Slice(Point, Normal, out slice1, out slice2);
if (nSlices == 0) {
triangleList.RemoveAt(i);
} else {
triangleList[i] = slice1;
if (nSlices > 1) {
triangleList.Add(slice2);
}
}
}
}
public void Cut(List<Edge2D> edgeList) {
for (int i = edgeList.Count - 1; i >= 0; i--) {
var edge = edgeList[i];
Edge2D slicedEdge;
var nSlices = edge.Slice(Point, ((Vector2)Normal).normalized, out slicedEdge);
if (nSlices == 0) {
edgeList.RemoveAt(i);
} else {
edgeList[i] = slicedEdge;
}
}
}
}
public struct FOVCuttingPlanes {
CuttingPlane rightPlane, leftPlane, topPlane, bottomPlane;
public static FOVCuttingPlanes From(ReferenceFrame frame, FOVRange fov) {
var horizRightRot = Quaternion.AngleAxis(fov.HorizAngle / 2f, frame.Up);
var horizLeftRot = Quaternion.Inverse(horizRightRot);
var vertUpRot = Quaternion.AngleAxis(fov.VertAngle / 2f, frame.Right);
var vertDownRot = Quaternion.Inverse(vertUpRot);
return new FOVCuttingPlanes {
rightPlane = new CuttingPlane { Point = frame.Position, Normal = horizRightRot * (-frame.Right) },
leftPlane = new CuttingPlane { Point = frame.Position, Normal = horizLeftRot * frame.Right },
topPlane = new CuttingPlane { Point = frame.Position, Normal = vertUpRot * frame.Up },
bottomPlane = new CuttingPlane { Point = frame.Position, Normal = vertDownRot * (-frame.Up) }
};
}
public void Clip(List<Triangle> triangles) {
rightPlane.Cut(triangles);
leftPlane.Cut(triangles);
topPlane.Cut(triangles);
bottomPlane.Cut(triangles);
}
}
public struct FOVCuttingPlanes2D {
CuttingPlane rightPlane, leftPlane;
public static FOVCuttingPlanes2D From(ReferenceFrame frame, FOVRange2D fov) {
var rightRot = Quaternion.AngleAxis(fov.Angle / 2f, Vector3.back);
var leftRot = Quaternion.Inverse(rightRot);
return new FOVCuttingPlanes2D {
rightPlane = new CuttingPlane { Point = frame.Position, Normal = rightRot * (-frame.Right) },
leftPlane = new CuttingPlane { Point = frame.Position, Normal = leftRot * (frame.Right) }
};
}
public void Clip(List<Edge2D> edges) {
rightPlane.Cut(edges);
leftPlane.Cut(edges);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc6af1cba09a80b43a3b1d78c9e42d26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,75 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
[System.Serializable]
public struct Edge2D {
public Vector2 P1, P2;
public Edge2D(Vector2 p1, Vector2 p2) {
P1 = p1; P2 = p2;
}
public float GetLength() => (P2 - P1).magnitude;
public Edge2D ProjectCircle(Vector2 origin) => new Edge2D((P1 - origin).normalized + origin, (P2 - origin).normalized + origin);
public Vector2 GetRandomPoint(Vector3 sobolPosition) => Vector2.Lerp(P1, P2, sobolPosition.x);
public void DrawGizmos(float z) {
Gizmos.DrawLine(new Vector3(P1.x, P1.y, z), new Vector3(P2.x, P2.y, z));
}
public int Slice(Vector2 linePoint, Vector2 lineNormal, out Edge2D slice) {
slice = default;
var plane = new Plane(lineNormal, linePoint);
var p1Dist = plane.GetDistanceToPoint(P1);
var isP1Inside = p1Dist >= 0f;
var p2Dist = plane.GetDistanceToPoint(P2);
var isP2Inside = p2Dist >= 0f;
if (isP1Inside && isP2Inside) {
slice = this;
return 1;
} else if (!isP1Inside && !isP2Inside) {
return 0;
}
Vector2 intersectPoint;
EdgePlaneIntersection(out intersectPoint, this, lineNormal, linePoint);
slice = new Edge2D(isP1Inside ? P1 : P2, intersectPoint);
return 1;
}
static bool EdgePlaneIntersection(out Vector2 intersection, Edge2D edge, Vector2 planeNormal, Vector2 planePoint) {
float length;
float dotNumerator;
float dotDenominator;
Vector2 vector;
intersection = Vector3.zero;
var lineVec = (edge.P2 - edge.P1).normalized;
dotNumerator = Vector3.Dot((planePoint - edge.P1), planeNormal);
dotDenominator = Vector3.Dot(lineVec, planeNormal);
if (dotDenominator != 0.0f) {
length = dotNumerator / dotDenominator;
vector = SetVectorLength(lineVec, length);
intersection = edge.P1 + vector;
return true;
} else {
return false;
}
}
static Vector2 SetVectorLength(Vector2 vector, float size) {
Vector2 vectorNormalized = vector.normalized;
return vectorNormalized *= size;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2498b42cb1733234693569bc906db078
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
[System.Serializable]
public struct FOVRange {
public float HorizAngle;
public float VertAngle;
public float Distance;
public static FOVRange Of(float horizAngle, float vertAngle, float distance = float.PositiveInfinity) => new FOVRange {
HorizAngle = Mathf.Clamp(horizAngle, 0f, 180f),
VertAngle = Mathf.Clamp(vertAngle, 0f, 180f),
Distance = distance
};
public bool ContainsAngles(ViewAngles angles) =>
Mathf.Abs(angles.HorizAngle) <= HorizAngle && Mathf.Abs(angles.VertAngle) <= VertAngle;
public bool ContainsDistance(float distance) => distance <= Distance;
public bool Contains(ViewAngles angles, float distance) =>
ContainsAngles(angles) && ContainsDistance(distance);
public void DrawGizmos(ReferenceFrame frame) {
SensorGizmos.FOVGizmo(frame, float.IsInfinity(Distance) ? 1f : Distance, HorizAngle, VertAngle);
}
}
public struct FOVRange2D {
public float Angle;
public float Distance;
public static FOVRange2D Of(float angle, float distance = float.PositiveInfinity) => new FOVRange2D {
Angle = Mathf.Clamp(angle, 0f, 180f),
Distance = distance
};
public void DrawGizmos(ReferenceFrame frame) {
SensorGizmos.FOVGizmo(frame, float.IsInfinity(Distance) ? 1f : Distance, Angle, 0f);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1b4f5e6cc15b72b418fa101403eb87dc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,181 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
public class LOSTest2D : BaseLOSTest {
List<Edge2D> edges = new List<Edge2D>();
List<Edge2D> projectedEdges = new List<Edge2D>();
ComponentCache losColliderOwnerCache;
SobolSequence3D sobol = new SobolSequence3D();
public override void DrawGizmos() {
base.DrawGizmos();
SensorGizmos.PushColor(Color.blue);
foreach (var edge in edges) {
edge.DrawGizmos(config.Frame.Position.z);
}
/*foreach (var edge in projectedEdges) {
edge.DrawGizmos(Config.Origin.z);
}*/
SensorGizmos.PopColor();
}
protected override void Clear() {
edges.Clear();
projectedEdges.Clear();
}
protected override LOSRayResult TestPoint(Vector3 testPoint) {
var saveQHT = Physics2D.queriesHitTriggers;
Physics2D.queriesHitTriggers = !config.IgnoreTriggerColliders;
var result = DoTest(testPoint);
Physics2D.queriesHitTriggers = saveQHT;
return result;
}
LOSRayResult DoTest(Vector3 testPoint) {
var delta = (Vector2)testPoint - (Vector2)config.Frame.Position;
var ray = new Ray(config.Frame.Position, delta.normalized);
var result = new LOSRayResult() { OriginPoint = ray.origin, TargetPoint = testPoint, VisibilityMultiplier = 1f };
var hitInfo = Physics2D.Raycast(ray.origin, ray.direction, delta.magnitude, config.BlocksLineOfSight);
if (hitInfo.collider != null) {
// Ray hit something, check that it was the target.
var isTarget = (hitInfo.rigidbody != null && hitInfo.rigidbody.gameObject == config.InputSignal.Object)
|| hitInfo.collider.gameObject == config.InputSignal.Object;
isTarget = isTarget || config.OwnedCollider2Ds.Contains(hitInfo.collider);
var losColliderOwner = losColliderOwnerCache.GetComponent<LOSColliderOwner>(config.InputSignal.Object);
if (losColliderOwner != null) {
isTarget = isTarget || losColliderOwner.IsColliderOwner(hitInfo.collider);
}
if (!isTarget) {
result.RayHit = new RayHit() {
IsObstructing = true,
Point = hitInfo.point,
Normal = hitInfo.normal,
Distance = hitInfo.distance,
DistanceFraction = hitInfo.distance / delta.magnitude,
Collider2D = hitInfo.collider
};
}
}
return result;
}
protected override bool IsInsideSignal() {
var origin = Config.Frame.Position;
var bounds = Config.InputSignal.Bounds;
origin.Set(origin.x, origin.y, bounds.center.z);
return bounds.Contains(origin);
}
protected override void GenerateTestPoints(List<Vector3> storeIn) {
if (Config.PointSamplingMethod == PointSamplingMethod.Fast) {
FastGenerateTestPoints(storeIn);
} else if (Config.PointSamplingMethod == PointSamplingMethod.Quality) {
QualityGenerateTestPoints(storeIn);
}
}
void FastGenerateTestPoints(List<Vector3> storeIn) {
var bounds = Config.InputSignal.Bounds;
for (int i = 0; i < Config.NumberOfRays; i++) {
var nextSobol = sobol.Next();
var random3 = new Vector3(Mathf.Lerp(-1, 1, nextSobol.x), Mathf.Lerp(-1, 1, nextSobol.y), Mathf.Lerp(-1, 1, nextSobol.z));
random3 *= .9f;
var randomPoint = bounds.center + Vector3.Scale(bounds.extents, random3);
storeIn.Add(randomPoint);
}
}
void QualityGenerateTestPoints(List<Vector3> storeIn) {
edges.Clear();
projectedEdges.Clear();
var bounds = config.InputSignal.Bounds;
bounds.center = (Vector2)bounds.center;
LOSUtils.MapBoundsToEdges(config.Frame.Position, bounds, edges);
if (config.LimitViewAngle) {
var fov = FOVRange2D.Of(config.MaxHorizAngle * 2f);
FOVCuttingPlanes2D.From(Config.Frame, fov).Clip(edges);
}
if (edges.Count == 0) {
return;
}
foreach (var edge in edges) {
projectedEdges.Add(edge.ProjectCircle(config.Frame.Position));
}
for (int i = 0; i < config.NumberOfRays; i++) {
int nAttempts = 0;
Start:
var nextSobol = sobol.Next();
var randomPoint = LOSUtils.GetRandomPointOnEdges(projectedEdges, nextSobol);
float boundsDist;
var ray = new Ray((Vector2)config.Frame.Position, ((Vector2)(randomPoint - config.Frame.Position)).normalized);
bounds.IntersectRay(ray, out boundsDist);
if (boundsDist == 0f) {
if (nAttempts < 2) {
// Very rarely the random point will be outside the bounds, try again.
nAttempts++;
goto Start;
}
// Tried three times and still no good. Ignore this point. Doubt this will ever happen. But don't want to
// search forever in case there's a configuration that would cause infinite loops.
continue;
}
var intBoundsInPoint = ray.origin + ray.direction * boundsDist + new Vector3(0f, 0f, config.Frame.Position.z);
var intBoundsOutPoint = LOSUtils.RaycastBoundsOutPoint(intBoundsInPoint, (intBoundsInPoint - Config.Frame.Position).normalized, bounds);
var midpoint = (intBoundsOutPoint + intBoundsInPoint) / 2f;
var penetration = midpoint - intBoundsInPoint;
if (config.LimitDistance) {
penetration = Vector3.ClampMagnitude(penetration, config.MaxDistance / 100f);
}
storeIn.Add(intBoundsInPoint + penetration);
}
}
protected override float GetVisibilityScale() {
var visibilityScale = 1f;
if (config.LimitDistance) {
var bounds = config.InputSignal.Bounds;
bounds.center.Set(bounds.center.x, bounds.center.y, config.Frame.Position.z);
float distance = Mathf.Sqrt((bounds.SqrDistance(config.Frame.Position)));
visibilityScale *= config.VisibilityByDistance.Evaluate(distance / config.MaxDistance);
}
if (config.LimitViewAngle) {
var horizAngle = Mathf.Abs(AngleUtils.PlanarAngleToBounds(config.Frame, Config.InputSignal.Bounds));
visibilityScale *= config.VisibilityByHorizAngle.Evaluate(horizAngle / config.MaxHorizAngle);
}
return visibilityScale;
}
protected override float GetRayVisibilityScale(Vector3 target) {
var visibilityScale = 1f;
if (config.LimitDistance) {
float distance = (config.Frame.Position - target).magnitude;
visibilityScale *= config.VisibilityByDistance.Evaluate(distance / config.MaxDistance);
}
if (config.LimitViewAngle) {
var horizAngle = Mathf.Abs(AngleUtils.PlanarAngleToPoint(config.Frame, target));
visibilityScale *= config.VisibilityByHorizAngle.Evaluate(horizAngle / config.MaxHorizAngle);
}
return visibilityScale;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 724697f57b16047448504ede355a12e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,171 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
public class LOSTest3D : BaseLOSTest {
List<Triangle> triangles = new List<Triangle>();
List<Triangle> projectedTriangles = new List<Triangle>();
ComponentCache losColliderOwnerCache;
SobolSequence3D sobol = new SobolSequence3D();
QueryTriggerInteraction queryTriggerInteraction => Config.IgnoreTriggerColliders
? QueryTriggerInteraction.Ignore
: QueryTriggerInteraction.Collide;
public override void DrawGizmos() {
base.DrawGizmos();
SensorGizmos.PushColor(Color.blue);
foreach (var triangle in triangles) {
triangle.DrawGizmos();
}
/*foreach (var triangle in projectedTriangles) {
triangle.DrawGizmos();
}*/
SensorGizmos.PopColor();
}
protected override void Clear() {
triangles.Clear();
projectedTriangles.Clear();
}
protected override LOSRayResult TestPoint(Vector3 testPoint) {
var delta = testPoint - config.Frame.Position;
var ray = new Ray(config.Frame.Position, delta.normalized);
RaycastHit hitInfo;
var result = new LOSRayResult() { OriginPoint = ray.origin, TargetPoint = testPoint, VisibilityMultiplier = 1f };
if (Physics.Raycast(ray, out hitInfo, delta.magnitude, config.BlocksLineOfSight, queryTriggerInteraction)) {
// Ray hit something, check that it was the target.
var isTarget = (hitInfo.rigidbody != null && hitInfo.rigidbody.gameObject == config.InputSignal.Object)
|| hitInfo.collider.gameObject == config.InputSignal.Object;
isTarget = isTarget || (config.OwnedColliders?.Contains(hitInfo.collider) ?? false);
var losColliderOwner = losColliderOwnerCache.GetComponent<LOSColliderOwner>(config.InputSignal.Object);
if (losColliderOwner != null) {
isTarget = isTarget || losColliderOwner.IsColliderOwner(hitInfo.collider);
}
if (!isTarget) {
result.RayHit = new RayHit() {
IsObstructing = true,
Point = hitInfo.point,
Normal = hitInfo.normal,
Distance = hitInfo.distance,
DistanceFraction = hitInfo.distance / delta.magnitude,
Collider = hitInfo.collider
};
}
}
return result;
}
protected override bool IsInsideSignal() => Config.InputSignal.Bounds.Contains(Config.Frame.Position);
protected override void GenerateTestPoints(List<Vector3> storeIn) {
if (Config.PointSamplingMethod == PointSamplingMethod.Fast) {
FastGenerateTestPoints(storeIn);
} else if (Config.PointSamplingMethod == PointSamplingMethod.Quality) {
QualityGenerateTestPoints(storeIn);
}
}
void FastGenerateTestPoints(List<Vector3> storeIn) {
var bounds = Config.InputSignal.Bounds;
for (int i = 0; i < Config.NumberOfRays; i++) {
var nextSobol = sobol.Next();
var random3 = new Vector3(Mathf.Lerp(-1,1, nextSobol.x), Mathf.Lerp(-1, 1, nextSobol.y), Mathf.Lerp(-1, 1, nextSobol.z));
random3 *= .9f;
var randomPoint = bounds.center + Vector3.Scale(bounds.extents, random3);
storeIn.Add(randomPoint);
}
}
void QualityGenerateTestPoints(List<Vector3> storeIn) {
triangles.Clear();
projectedTriangles.Clear();
var bounds = config.InputSignal.Bounds;
LOSUtils.MapBoundsToTriangles(config.Frame.Position, bounds, triangles);
if (config.LimitViewAngle) {
var fov = FOVRange.Of(config.MaxHorizAngle * 2f, config.MaxVertAngle * 2f);
FOVCuttingPlanes.From(config.Frame, fov).Clip(triangles);
}
if (triangles.Count == 0) {
return;
}
foreach (var triangle in triangles) {
projectedTriangles.Add(triangle.ProjectSphere(config.Frame.Position));
}
for (int i = 0; i < config.NumberOfRays; i++) {
int nAttempts = 0;
Start:
var nextSobol = sobol.Next();
var randomPoint = LOSUtils.GetRandomPointInTriangles(projectedTriangles, nextSobol);
float boundsDist;
var ray = new Ray(config.Frame.Position, (randomPoint - config.Frame.Position).normalized);
bounds.IntersectRay(ray, out boundsDist);
if (boundsDist == 0f) {
if (nAttempts < 2) {
// Very rarely the random point will be outside the bounds, try again.
nAttempts++;
goto Start;
}
// Tried three times and still no good. Ignore this point. Doubt this will ever happen. But don't want to
// search forever in case there's a configuration that would cause infinite loops.
continue;
}
var intBoundsInPoint = ray.origin + ray.direction * boundsDist;
var intBoundsOutPoint = LOSUtils.RaycastBoundsOutPoint(intBoundsInPoint, (intBoundsInPoint - config.Frame.Position).normalized, bounds);
var midpoint = (intBoundsOutPoint + intBoundsInPoint) / 2f;
var penetration = midpoint - intBoundsInPoint;
if (config.LimitDistance) {
penetration = Vector3.ClampMagnitude(penetration, config.MaxDistance / 100f);
}
storeIn.Add(intBoundsInPoint + penetration);
}
}
protected override float GetVisibilityScale() {
var visibilityScale = 1f;
if (config.LimitDistance) {
float distance = Mathf.Sqrt((config.InputSignal.Bounds.SqrDistance(config.Frame.Position)));
visibilityScale *= config.VisibilityByDistance.Evaluate(distance / config.MaxDistance);
}
if (config.LimitViewAngle) {
var coords = AngleUtils.ViewAnglesToBounds(config.Frame, config.InputSignal.Bounds).Abs;
visibilityScale *= config.VisibilityByHorizAngle.Evaluate(coords.HorizAngle / config.MaxHorizAngle)
* config.VisibilityByVertAngle.Evaluate(coords.VertAngle / config.MaxVertAngle);
}
return visibilityScale;
}
protected override float GetRayVisibilityScale(Vector3 target) {
var visibilityScale = 1f;
if (config.LimitDistance) {
float distance = (config.Frame.Position - target).magnitude;
visibilityScale *= config.VisibilityByDistance.Evaluate(distance / config.MaxDistance);
}
if (config.LimitViewAngle) {
var coords = AngleUtils.ViewAnglesToPoint(config.Frame, target).Abs;
visibilityScale *= config.VisibilityByHorizAngle.Evaluate(coords.HorizAngle / config.MaxHorizAngle)
* config.VisibilityByVertAngle.Evaluate(coords.VertAngle / config.MaxVertAngle);
}
return visibilityScale;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3e288c1c7e4c2242b1db4fdca3c133c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,159 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
public class LOSUtils {
public static void MapBoundsToEdges(Vector2 viewPos, Bounds bounds, List<Edge2D> storeIn) {
var center = (Vector2)bounds.center;
var extents = (Vector2)bounds.extents;
if (viewPos.x > bounds.max.x) {
storeIn.Add(new Edge2D(center + new Vector2(extents.x, -extents.y), center + new Vector2(extents.x, extents.y)));
} else if (viewPos.x < bounds.min.x) {
storeIn.Add(new Edge2D(center - new Vector2(extents.x, -extents.y), center - new Vector2(extents.x, extents.y)));
}
if (viewPos.y > bounds.max.y) {
storeIn.Add(new Edge2D(center + new Vector2(-extents.x, extents.y), center + new Vector2(extents.x, extents.y)));
} else if (viewPos.y < bounds.min.y) {
storeIn.Add(new Edge2D(center - new Vector2(-extents.x, extents.y), center - new Vector2(extents.x, extents.y)));
}
}
public static void MapBoundsToTriangles(Vector3 viewPos, Bounds bounds, List<Triangle> storeIn) {
var center = bounds.center;
var extents = bounds.extents;
var ltf = new Vector3(-extents.x, extents.y, extents.z) + center;
var rtf = new Vector3(extents.x, extents.y, extents.z) + center;
var rtb = new Vector3(extents.x, extents.y, -extents.z) + center;
var ltb = new Vector3(-extents.x, extents.y, -extents.z) + center;
var lbf = ltf + (Vector3.down * 2f * extents.y);
var rbf = rtf + (Vector3.down * 2f * extents.y);
var rbb = rtb + (Vector3.down * 2f * extents.y);
var lbb = ltb + (Vector3.down * 2f * extents.y);
if (Vector3.Dot(viewPos - (bounds.center + Vector3.right * bounds.extents.x), Vector3.right) > 0) {
storeIn.Add(new Triangle(rtb, rtf, rbf));
storeIn.Add(new Triangle(rtb, rbf, rbb));
} else if (Vector3.Dot(viewPos - (bounds.center - Vector3.right * bounds.extents.x), Vector3.left) > 0) {
storeIn.Add(new Triangle(ltb, ltf, lbf));
storeIn.Add(new Triangle(ltb, lbf, lbb));
}
if (Vector3.Dot(viewPos - (bounds.center + Vector3.up * bounds.extents.y), Vector3.up) > 0) {
storeIn.Add(new Triangle(ltb, ltf, rtf));
storeIn.Add(new Triangle(ltb, rtf, rtb));
} else if (Vector3.Dot(viewPos - (bounds.center - Vector3.up * bounds.extents.y), Vector3.down) > 0) {
storeIn.Add(new Triangle(lbb, lbf, rbf));
storeIn.Add(new Triangle(lbb, rbf, rbb));
}
if (Vector3.Dot(viewPos - (bounds.center + Vector3.forward * bounds.extents.z), Vector3.forward) > 0) {
storeIn.Add(new Triangle(rtf, ltf, lbf));
storeIn.Add(new Triangle(rtf, lbf, rbf));
} else if (Vector3.Dot(viewPos - (bounds.center - Vector3.forward * bounds.extents.z), Vector3.back) > 0) {
storeIn.Add(new Triangle(rtb, ltb, lbb));
storeIn.Add(new Triangle(rtb, lbb, rbb));
}
}
static List<float> triangleAreas = new List<float>();
public static Vector3 GetRandomPointInTriangles(List<Triangle> triangles, Vector3 sobolPosition) {
if (triangles.Count == 0) {
return default;
}
var totalArea = 0f;
triangleAreas.Clear();
foreach (var triangle in triangles) {
totalArea += triangle.GetArea();
triangleAreas.Add(totalArea);
}
var r = sobolPosition.z * totalArea;
for (int i = 0; i < triangleAreas.Count; i++) {
if (triangleAreas[i] >= r) {
return triangles[i].GetRandomPoint(sobolPosition);
}
}
return triangles[triangles.Count - 1].GetRandomPoint(sobolPosition);
}
static List<float> edgeLengths = new List<float>();
public static Vector3 GetRandomPointOnEdges(List<Edge2D> edges, Vector3 sobolPosition) {
if (edges.Count == 0) {
return default;
}
var totalLength = 0f;
edgeLengths.Clear();
foreach (var edge in edges) {
totalLength += edge.GetLength();
edgeLengths.Add(totalLength);
}
var r = sobolPosition.y * totalLength;
for (int i = 0; i < edgeLengths.Count; i++) {
if (edgeLengths[i] >= r) {
return edges[i].GetRandomPoint(sobolPosition);
}
}
return edges[edges.Count - 1].GetRandomPoint(sobolPosition);
}
public static Vector3 RaycastBoundsOutPoint(Vector3 rayPoint, Vector3 rayDir, Bounds bounds) {
Vector3 intPoint = rayPoint;
float bestDistance = Mathf.Infinity;
if (rayPoint.x < bounds.max.x && Vector3.Dot(rayDir, Vector3.right) > 0f) {
var distance = DistanceRayToPlane(rayPoint, rayDir, bounds.max, Vector3.right);
if (distance < bestDistance) {
bestDistance = distance;
intPoint = rayPoint + rayDir * distance;
}
} else if (rayPoint.x > bounds.min.x && Vector3.Dot(rayDir, Vector2.left) > 0f) {
var distance = DistanceRayToPlane(rayPoint, rayDir, bounds.min, Vector3.left);
if (distance < bestDistance) {
bestDistance = distance;
intPoint = rayPoint + rayDir * distance;
}
}
if (rayPoint.y < bounds.max.y && Vector3.Dot(rayDir, Vector3.up) > 0f) {
var distance = DistanceRayToPlane(rayPoint, rayDir, bounds.max, Vector3.up);
if (distance < bestDistance) {
bestDistance = distance;
intPoint = rayPoint + rayDir * distance;
}
} else if (rayPoint.y > bounds.min.y && Vector3.Dot(rayDir, Vector3.down) > 0f) {
var distance = DistanceRayToPlane(rayPoint, rayDir, bounds.min, Vector3.down);
if (distance < bestDistance) {
bestDistance = distance;
intPoint = rayPoint + rayDir * distance;
}
}
if (rayPoint.z < bounds.max.z && Vector3.Dot(rayDir, Vector3.forward) > 0f) {
var distance = DistanceRayToPlane(rayPoint, rayDir, bounds.max, Vector3.forward);
if (distance < bestDistance) {
bestDistance = distance;
intPoint = rayPoint + rayDir * distance;
}
} else if (rayPoint.z > bounds.min.z && Vector3.Dot(rayDir, Vector3.back) > 0f) {
var distance = DistanceRayToPlane(rayPoint, rayDir, bounds.min, Vector3.back);
if (distance < bestDistance) {
bestDistance = distance;
intPoint = rayPoint + rayDir * distance;
}
}
return intPoint;
}
static float DistanceRayToPlane(Vector3 rayPoint, Vector3 rayDir, Vector3 planePoint, Vector3 planeNormal) {
var distToPlane = Vector3.Dot(rayPoint - planePoint, planeNormal);
return distToPlane / (Vector3.Dot(-rayDir, planeNormal));
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 070ea86dbdbfe634d9d1854e071459bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
public class MovingAverageFilter {
int _size;
public int Size {
get => _size;
set {
_size = Mathf.Max(value, 1);
FitSize();
}
}
// Prefer to underestimate then overestimate
public float Value => total / Mathf.Max(_size, samples.Count);
Queue<float> samples = new Queue<float>();
float total;
MovingAverageFilter() { }
public MovingAverageFilter(int initialSize) {
Size = initialSize;
}
public void AddSample(float x) {
samples.Enqueue(x);
total += x;
FitSize();
}
public void Clear() {
samples.Clear();
total = 0;
}
void FitSize() {
while (samples.Count > _size) {
total -= samples.Dequeue();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3f5fddd7d3d228d48837fa1426d37786
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,126 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
[System.Serializable]
public struct Triangle {
public Vector3 P1, P2, P3;
public Vector3 N => Vector3.Cross(P2 - P1, P3 - P2).normalized;
public Triangle(Vector3 p1, Vector3 p2, Vector3 p3) {
P1 = p1; P2 = p2; P3 = p3;
}
public float GetArea() {
float res = Mathf.Pow(((P2.x * P1.y) - (P3.x * P1.y) - (P1.x * P2.y) + (P3.x * P2.y) + (P1.x * P3.y) - (P2.x * P3.y)), 2.0f);
res += Mathf.Pow(((P2.x * P1.z) - (P3.x * P1.z) - (P1.x * P2.z) + (P3.x * P2.z) + (P1.x * P3.z) - (P2.x * P3.z)), 2.0f);
res += Mathf.Pow(((P2.y * P1.z) - (P3.y * P1.z) - (P1.y * P2.z) + (P3.y * P2.z) + (P1.y * P3.z) - (P2.y * P3.z)), 2.0f);
return Mathf.Sqrt(res) * 0.5f;
}
public Triangle ProjectSphere(Vector3 origin) {
return new Triangle((P1 - origin).normalized + origin, (P2 - origin).normalized + origin, (P3 - origin).normalized + origin);
}
public Vector3 GetRandomPoint(Vector3 sobolPosition) {
var a = P2 - P1;
var b = P3 - P1;
var u1 = sobolPosition.x;
var u2 = sobolPosition.y;
if (u1 + u2 > 1) {
u1 = 1 - u1;
u2 = 1 - u2;
}
var w = (u1 * a) + (u2 * b);
return w + P1;
}
public void DrawGizmos() {
Gizmos.DrawLine(P1, P2);
Gizmos.DrawLine(P2, P3);
Gizmos.DrawLine(P3, P1);
}
public int Slice(Vector3 planePoint, Vector3 planeNormal, out Triangle slice1, out Triangle slice2) {
slice1 = default;
slice2 = default;
int nInside = 0, nOutside = 0;
Vector3 inPt1 = default, inPt2 = default;
Vector3 outPt1 = default, outPt2 = default;
var p1Dist = Vector3.Dot(P1 - planePoint, planeNormal);
ClassifyPoint(P1, p1Dist, ref nInside, ref nOutside, ref inPt1, ref inPt2, ref outPt1, ref outPt2);
var p2Dist = Vector3.Dot(P2 - planePoint, planeNormal);
ClassifyPoint(P2, p2Dist, ref nInside, ref nOutside, ref inPt1, ref inPt2, ref outPt1, ref outPt2);
var p3Dist = Vector3.Dot(P3 - planePoint, planeNormal);
ClassifyPoint(P3, p3Dist, ref nInside, ref nOutside, ref inPt1, ref inPt2, ref outPt1, ref outPt2);
if (nOutside == 0) {
// Completely inside plane
slice1 = this;
return 1;
}
if (nInside == 0) {
// Completely outside plane
return 0;
}
Vector3 edgeInt1, edgeInt2;
LinePlaneIntersection(out edgeInt1, inPt1, (outPt1 - inPt1).normalized, planeNormal, planePoint);
if (nInside > 1) {
LinePlaneIntersection(out edgeInt2, inPt2, (outPt1 - inPt2).normalized, planeNormal, planePoint);
slice1 = new Triangle { P1 = inPt1, P2 = edgeInt1, P3 = edgeInt2 };
slice2 = new Triangle { P1 = inPt1, P2 = inPt2, P3 = edgeInt2 };
return 2;
} else {
LinePlaneIntersection(out edgeInt2, inPt1, (outPt2 - inPt1).normalized, planeNormal, planePoint);
slice1 = new Triangle { P1 = inPt1, P2 = edgeInt1, P3 = edgeInt2 };
return 1;
}
}
// I had originally made all these refs private vars, but structs with >40 bytes have poor peformance.
// So to get under 40 bytes I made them all refs.
void ClassifyPoint(Vector3 p, float d, ref int nInside, ref int nOutside, ref Vector3 inPt1, ref Vector3 inPt2, ref Vector3 outPt1, ref Vector3 outPt2) {
if (d >= 0f) {
if (nInside == 0) inPt1 = p;
else if (nInside == 1) inPt2 = p;
nInside += 1;
} else {
if (nOutside == 0) outPt1 = p;
else if (nOutside == 1) outPt2 = p;
nOutside += 1;
}
}
static bool LinePlaneIntersection(out Vector3 intersection, Vector3 linePoint, Vector3 lineVec, Vector3 planeNormal, Vector3 planePoint) {
float length;
float dotNumerator;
float dotDenominator;
Vector3 vector;
intersection = Vector3.zero;
dotNumerator = Vector3.Dot((planePoint - linePoint), planeNormal);
dotDenominator = Vector3.Dot(lineVec, planeNormal);
if (dotDenominator != 0.0f) {
length = dotNumerator / dotDenominator;
vector = SetVectorLength(lineVec, length);
intersection = linePoint + vector;
return true;
} else {
return false;
}
}
static Vector3 SetVectorLength(Vector3 vector, float size) {
Vector3 vectorNormalized = Vector3.Normalize(vector);
return vectorNormalized *= size;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 63fbbd6c6ee9b3347938ecfe9f70adf3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,58 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Micosmo.SensorToolkit {
public static class MotionUtils {
public static float SeekAccel(float maxSpeed, float vSteer, float velocity) {
var targetVelocity = Mathf.Clamp(vSteer, -maxSpeed, maxSpeed);
var accel = (targetVelocity - velocity) / Time.deltaTime;
return accel;
}
public static Vector2 SeekAccel(float maxSpeed, Vector2 vSteer, Vector2 velocity) {
var targetVelocity = Vector2.ClampMagnitude(vSteer, maxSpeed);
var accel = (targetVelocity - velocity) / Time.deltaTime;
return accel;
}
public static Vector3 SeekAccel(float maxSpeed, Vector3 vSteer, Vector3 velocity) {
var targetVelocity = Vector3.ClampMagnitude(vSteer, maxSpeed);
var accel = (targetVelocity - velocity) / Time.deltaTime;
return accel;
}
public static Vector3 SeekAngularAccel(float maxAccel, float maxSpeed, Vector3 angularVelocity, Quaternion currRot, Quaternion targetRot) {
var delta = targetRot * Quaternion.Inverse(currRot);
delta.ToAngleAxis(out var angle, out var axis);
if (float.IsNaN(axis.sqrMagnitude)) {
axis = angularVelocity.normalized;
}
angle = Mathf.DeltaAngle(0f, angle);
var distance = Mathf.Abs(angle);
var stoppingDistance = StoppingDistance(maxSpeed, maxAccel);
var targetAngularSpeed = Mathf.Min(maxSpeed, maxSpeed * (distance / stoppingDistance));
var targetAngularVelocity = Mathf.Sign(angle) * targetAngularSpeed * axis;
var torque = (targetAngularVelocity - angularVelocity) / Time.deltaTime;
return Vector3.ClampMagnitude(torque, maxAccel);
}
public static float SeekAngularAccel2D(float maxAccel, float maxSpeed, float angularVelocity, Vector2 currDir, Vector2 targetDir) {
var dAngle = Vector2.SignedAngle(currDir, targetDir);
var distance = Mathf.Abs(dAngle);
var stoppingDistance = StoppingDistance(maxSpeed, maxAccel);
var arrive = Mathf.Clamp01(distance / stoppingDistance);
return SeekAccel(maxSpeed, (arrive * Mathf.Sign(dAngle)) * maxSpeed, angularVelocity);
}
public static float StoppingDistance(float velocity, float accel) {
if (accel == 0) return 0;
var d = velocity * velocity / (2 * accel);
return d * 1.1f;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76ba871a42f54880b622fab9f356d87a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace Micosmo.SensorToolkit
{
public struct WaitForSecondsCache {
WaitForSeconds w;
float s;
public WaitForSeconds WaitForSeconds(float s) {
if (this.s == s && w != null) {
return w;
}
w = new WaitForSeconds(s);
this.s = s;
return w;
}
}
public struct ComponentCache {
enum InvokeType { GetComponent, GetComponentInParent, GetComponentInChildren }
Component component;
System.Type type;
GameObject obj;
InvokeType prevInvokeType;
public T GetComponent<T>(GameObject ofObj) where T : class {
checkInvokeType(InvokeType.GetComponent);
if (type == typeof(T) && ReferenceEquals(ofObj, obj)) {
return component as T;
} else {
T tc = null;
if (ofObj) {
ofObj.TryGetComponent(out tc);
}
component = tc as Component;
type = typeof(T);
obj = ofObj;
return component as T;
}
}
public T GetComponentInParent<T>(GameObject ofObj) where T : class {
checkInvokeType(InvokeType.GetComponentInParent);
if (type == typeof(T) && ReferenceEquals(ofObj, obj)) {
return component as T;
} else {
component = null;
if (ofObj) {
component = ofObj.GetComponentInParent<T>() as Component;
}
type = typeof(T);
obj = ofObj;
return component as T;
}
}
public T GetComponentInChildren<T>(GameObject ofObj) where T : class {
checkInvokeType(InvokeType.GetComponentInChildren);
if (type == typeof(T) && ReferenceEquals(ofObj, obj)) {
return component as T;
} else {
component = null;
if (ofObj) {
component = ofObj.GetComponentInChildren<T>() as Component;
}
type = typeof(T);
obj = ofObj;
return component as T;
}
}
void checkInvokeType(InvokeType nextInvokeType) {
if (nextInvokeType != prevInvokeType || !Application.isPlaying) {
// We don't cache in edit mode. Because the user may add/remove components at any time.
component = null;
obj = null;
}
prevInvokeType = nextInvokeType;
}
}
public class ObjectCache<T> where T : new()
{
Stack<T> cache;
public ObjectCache() : this(10) { }
public ObjectCache(int startSize)
{
cache = new Stack<T>();
for (int i = 0; i < startSize; i++) { cache.Push(create()); }
}
public T Get()
{
if (cache.Count > 0) return cache.Pop();
else return create();
}
public virtual void Dispose(T obj)
{
cache.Push(obj);
}
protected virtual T create()
{
return System.Activator.CreateInstance<T>();
}
}
public class ListCache<T> : ObjectCache<List<T>>
{
public override void Dispose(List<T> obj)
{
obj.Clear();
base.Dispose(obj);
}
}
}

Some files were not shown because too many files have changed in this diff Show More