Plane.FormationCreator/Plane.FormationCreator/Formation/FormationController.cs
2017-02-27 02:06:48 +08:00

735 lines
28 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Plane.Communication;
using Plane.Copters;
using Plane.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;
using static Plane.Copters.Constants;
using Plane.Geography;
namespace Plane.FormationCreator.Formation
{
public class FormationController
{
public FormationController(ILogger logger, CopterManager copterManager)
{
_logger = logger;
_copterManager = copterManager;
foreach (var copter in _copterManager.Copters)
{
_guides.Add(copter, null);
}
_copterManager.Copters.CollectionChanged += (sender, e) =>
{
if (e.OldItems != null)
{
foreach (ICopter copter in e.OldItems)
{
if (_guides.ContainsKey(copter))
{
_guides.Remove(copter);
}
}
}
if (e.NewItems != null)
{
foreach (ICopter copter in e.NewItems)
{
if (!_guides.ContainsKey(copter))
{
_guides.Add(copter, null);
}
}
}
};
//var thread = new Thread(() =>
//{
// while (true)
// {
// Control();
// Thread.Sleep(50);
// }
//})
//{
// IsBackground = true
//};
//thread.Start();
var timer = new System.Timers.Timer(INTERVAL);
timer.Elapsed += (sender, e) => Control();
timer.Start();
}
private const int INTERVAL = 50;
private bool _shouldStop;
private Dictionary<ICopter, Guide> _guides = new Dictionary<ICopter, Guide>();
private ILogger _logger;
private CopterManager _copterManager;
#region
private bool AnyCopterWithGuide()
{
try
{
return _guides.Any(item => item.Value != null);
}
catch (InvalidOperationException ex) // 集合已修改。
{
_logger.Log(ex.ToString(), Category.Info, Priority.None);
return true; // 安全起见返回 true让调用者再次检查。
}
}
private bool AnyCopterWithGuide(IEnumerable<ICopter> copters)
{
try
{
return copters.Any(c => _guides[c] != null);
}
catch (InvalidCastException ex) // 集合已修改。
{
_logger.Log(ex.ToString(), Category.Info, Priority.None);
return true; // 安全起见返回 true让调用者再次检查。
}
}
private async Task EnsureAllTasksFinishedAsync()
{
await Task.Run(async () =>
{
while (AnyCopterWithGuide())
{
await Task.Delay(INTERVAL).ConfigureAwait(false);
}
}).ConfigureAwait(false);
}
private async Task EnsureCopterTasksFinishedAsync(IEnumerable<ICopter> copters)
{
await Task.Run(async () =>
{
while (AnyCopterWithGuide(copters))
{
await Task.Delay(INTERVAL).ConfigureAwait(false);
}
}).ConfigureAwait(false);
}
private async Task EnsureTaskFinishedAsync(ICopter copter)
{
while (_guides[copter] != null)
{
await Task.Delay(INTERVAL).ConfigureAwait(false);
}
}
#endregion
private void Control()
{
try
{
var copters = _guides.Keys.ToList();
Parallel.ForEach(copters, (copter, loopState) =>
{
try
{
if (_shouldStop)
{
loopState.Stop();
return;
}
var guide = _guides[copter];
if (guide == null)
{
return;
}
if (!guide.IsLoop && guide.FinishPredicate?.Invoke() == true)
{
var finishAction = guide?.FinishAction;
_guides[copter] = null;
copter.HoverAsync().Wait();
finishAction?.Invoke();
}
else
{
guide.UpdateAction?.Invoke();
}
}
catch (Exception ex)
{
_logger.Log(ex);
}
});
if (_shouldStop)
{
Parallel.ForEach(copters, copter =>
{
try
{
var finishAction = _guides[copter]?.FinishAction;
_guides[copter] = null;
copter.HoverAsync().Wait();
finishAction?.Invoke();
}
catch (Exception ex)
{
_logger.Log(ex);
}
});
}
}
catch (Exception ex)
{
_logger.Log(ex);
}
}
public async Task AllStop()
{
Parallel.ForEach(_copterManager.Copters, copter => copter.HoverAsync());
if (!_shouldStop && AnyCopterWithGuide())
{
_shouldStop = true;
while (AnyCopterWithGuide())
{
await Task.Delay(INTERVAL).ConfigureAwait(false);
}
_shouldStop = false;
}
Parallel.ForEach(_copterManager.Copters, copter => copter.HoverAsync().Wait());
}
public async Task TurnAsync(IEnumerable<ICopter> copters, double targetLat, double targetLng)
{
await AllStop();
foreach (var copter in copters)
{
var guide = !_guides.ContainsKey(copter) || _guides[copter] == null ? (_guides[copter] = new Guide()) : _guides[copter];
var targetHeading = (float)GeographyUtils.RadToDeg(GeographyUtils.CalcDirection2D(copter.Latitude, copter.Longitude, targetLat, targetLng));
guide.FinishPredicate = () => Math.Abs(copter.Heading - targetHeading) < 1;
guide.UpdateAction = () =>
{
copter.MoveToIdealState(new CopterState
{
Heading = targetHeading
});
};
}
await EnsureAllTasksFinishedAsync().ConfigureAwait(false);
}
public async Task FlyToLatLngAsync(ICopter copter, double targetLat, double targetLng)
{
const double DT = INTERVAL / 1000.0;
const double DECELERATE_DISTANCE = 15;
double idealVel = 0;
double idealMovedDistance = 0;
var startLat = copter.Latitude;
var startLng = copter.Longitude;
var startTargetDistance = GeographyUtils.CalcDistance2D(startLat, startLng, targetLat, targetLng);
var idealMoveDirectionRadians = (float)GeographyUtils.CalcDirection2D(startLat, startLng, targetLat, targetLng);
var idealMoveDirectionDegrees = (float)GeographyUtils.RadToDeg(idealMoveDirectionRadians);
var guide = !_guides.ContainsKey(copter) || _guides[copter] == null ? (_guides[copter] = new Guide()) : _guides[copter];
guide.FinishPredicate = () => GeographyUtils.CalcDistance2D(copter.Latitude, copter.Longitude, targetLat, targetLng) <= 0.5;
var log = new List<object>();
guide.UpdateAction = () =>
{
if (idealVel < MAX_VEL)
{
idealVel += MAX_ACCEL * DT;
if (idealVel > MAX_VEL) idealVel = MAX_VEL;
}
var distance = GeographyUtils.CalcDistance2D(copter.Latitude, copter.Longitude, targetLat, targetLng);
if (distance < DECELERATE_DISTANCE) idealVel = MAX_VEL * distance / DECELERATE_DISTANCE;
double idealLat, idealLng;
if (idealMovedDistance < startTargetDistance)
{
idealMovedDistance += idealVel * DT;
if (idealMovedDistance > startTargetDistance)
{
idealLat = targetLat;
idealLng = targetLng;
}
else
{
var idealLatLng = GeographyUtils.CalcLatLngSomeMetersAway2D(startLat, startLng, idealMoveDirectionDegrees, (float)idealMovedDistance);
idealLat = idealLatLng.Item1;
idealLng = idealLatLng.Item2;
}
}
else
{
idealLat = targetLat;
idealLng = targetLng;
}
copter.MoveToIdealState(new CopterState
{
Latitude = idealLat,
Longitude = idealLng,
Heading = idealMoveDirectionDegrees,
VelocityDirection = idealMoveDirectionRadians,
Velocity = idealVel
});
log.Add(new
{
time = DateTime.Now,
targetLat = idealLat,
targetLng = idealLng,
lat = copter.Latitude,
lng = copter.Longitude,
alt = copter.Altitude
});
};
guide.FinishAction = () => _logger.Log(JsonConvert.SerializeObject(log));
await EnsureAllTasksFinishedAsync().ConfigureAwait(false);
}
public async Task FlyToLatLngAsync(IEnumerable<ICopter> copters, double targetLat, double targetLng)
{
await AllStop();
if (!copters.Any()) return;
var centerLat = copters.Average(c => c.Latitude);
var centerLng = copters.Average(c => c.Longitude);
var latDelta = targetLat - centerLat;
var lngDelta = targetLng - centerLng;
foreach (var copter in copters)
{
var copterTargetLat = copter.Latitude + latDelta;
var copterTargetLng = copter.Longitude + lngDelta;
var guide = !_guides.ContainsKey(copter) || _guides[copter] == null ? (_guides[copter] = new Guide()) : _guides[copter];
guide.FinishPredicate = () => GeographyUtils.CalcDistance2D(copter.Latitude, copter.Longitude, copterTargetLat, copterTargetLng) <= 0.5;
guide.UpdateAction = () =>
{
copter.MoveToIdealState(new CopterState
{
Latitude = copterTargetLat,
Longitude = copterTargetLng
});
};
}
await EnsureAllTasksFinishedAsync().ConfigureAwait(false);
}
public async Task FlyToAltitudeAsync(ICopter copter, float targetAlt)
{
var guide = _guides[copter] ?? (_guides[copter] = new Guide());
guide.FinishPredicate = () => Math.Abs(copter.Altitude - targetAlt) <= 0.5;
guide.UpdateAction = () =>
{
copter.MoveToIdealState(new CopterState
{
Altitude = targetAlt
});
};
await EnsureTaskFinishedAsync(copter).ConfigureAwait(false);
}
public async Task FlyToAltitudeAsync(IEnumerable<ICopter> copters, float targetAlt)
{
await AllStop();
foreach (var copter in copters)
{
if (!_guides.ContainsKey(copter))
{
_guides.Add(copter, null);
}
var guide = _guides[copter] ?? (_guides[copter] = new Guide());
guide.FinishPredicate = () => Math.Abs(copter.Altitude - targetAlt) <= 0.5;
guide.UpdateAction = () =>
{
copter.MoveToIdealState(new CopterState
{
Altitude = targetAlt
});
};
}
await EnsureAllTasksFinishedAsync().ConfigureAwait(false);
}
public async Task FlyInCircleAsync(IEnumerable<ICopter> copters, float centerDirection, float radius, bool clockwise = true, bool loop = false, double? velLimit = null)
{
await AllStop();
var velMax = velLimit ?? 2.0;
var velMin = -velMax;
var angVelMax = velMax / radius;
var angVelMin = velMin / radius;
var angAccel = (clockwise ? MAX_ACCEL : -MAX_ACCEL) / radius;
Parallel.ForEach(copters, copter =>
{
var guide = _guides[copter] ?? (_guides[copter] = new Guide());
guide.IsLoop = loop;
var centerLat = copter.Latitude + radius * Math.Cos(GeographyUtils.DegToRad(centerDirection)) * GeographyUtils.METERS_TO_LAT_SPAN;
var centerLng = copter.Longitude + radius * Math.Sin(GeographyUtils.DegToRad(centerDirection)) * GeographyUtils.CalcMetersToLngSpan(copter.Latitude);
var startingPointLat = copter.Latitude;
var startingPointLng = copter.Longitude;
var angVel = 0.0;
const double DT = INTERVAL / 1000.0;
var angleTotal = 0.0;
var startAngle = GeographyUtils.CalcDirection2D(centerLat, centerLng, copter.Latitude, copter.Longitude);
guide.FinishPredicate = () => angleTotal >= Math.PI * 2;
var log = new List<object>();
#region guide.UpdateAction
guide.UpdateAction = () =>
{
if (clockwise)
{
if (angVel < angVelMax)
{
angVel += angAccel * DT;
MathUtils.Constrain(ref angVel, 0, angVelMax);
}
}
else
{
if (angVel > angVelMin)
{
angVel += angAccel * DT;
MathUtils.Constrain(ref angVel, angVelMin, 0);
}
}
var angleChange = angVel * DT;
angleTotal += angleChange;
var targetAngle = startAngle + angleTotal;
var targetLat = centerLat + Math.Cos(targetAngle) * radius * GeographyUtils.METERS_TO_LAT_SPAN;
var targetLng = centerLng + Math.Sin(targetAngle) * radius * GeographyUtils.CalcMetersToLngSpan(copter.Latitude);
copter.MoveToIdealState(new CopterState
{
Latitude = targetLat,
Longitude = targetLng,
Heading = (float)GeographyUtils.RadToDeg(GeographyUtils.CalcDirection2D(targetLat, targetLng, centerLat, centerLng)),
VelocityDirection = GeographyUtils.CalcDirection2D(centerLat, centerLng, targetLat, targetLng)
+ Math.PI / 2,
Velocity = angVel * radius
});
log.Add(new
{
time = DateTime.Now,
targetLat,
targetLng,
lat = copter.Latitude,
lng = copter.Longitude,
alt = copter.Altitude,
angAccel,
angVel,
angleChange,
angleTotal
});
};
#endregion guide.UpdateAction
guide.FinishAction = () =>
{
_logger.Log(JsonConvert.SerializeObject(log));
};
});
await EnsureAllTasksFinishedAsync().ConfigureAwait(false);
}
public async Task FlyAroundCenterOfCoptersAsync(IEnumerable<ICopter> copters, bool clockwise = true, bool loop = false, double? velLimit = null)
{
await AllStop();
var nullableCenter = copters.GetCenter();
if (nullableCenter == null) return;
var center = nullableCenter.Value;
var radiuses = copters.ToDictionary(c => c, c => GeographyUtils.CalcDistance2D(c.Latitude, c.Longitude, center.Lat, center.Lng));
var velMax = velLimit ?? 2.0;
var velMin = -velMax;
var angVelMax = velMax / radiuses.Values.Max();
Parallel.ForEach(copters, copter =>
{
var guide = _guides[copter] ?? (_guides[copter] = new Guide());
guide.IsLoop = loop;
var startingPointLat = copter.Latitude;
var startingPointLng = copter.Longitude;
var radius = radiuses[copter];
var angVelMin = velMin / radius;
var angAccel = (clockwise ? 10 : -10) / radius;
var angVel = 0.0;
const double DT = INTERVAL / 1000.0;
var angleTotal = 0.0;
var startAngle = GeographyUtils.CalcDirection2D(center.Lat, center.Lng, copter.Latitude, copter.Longitude);
guide.FinishPredicate = () => angleTotal >= Math.PI * 2;
var log = new List<object>();
#region guide.UpdateAction
guide.UpdateAction = () =>
{
if (clockwise)
{
if (angVel < angVelMax)
{
angVel += angAccel * DT;
MathUtils.Constrain(ref angVel, 0, angVelMax);
}
}
else
{
if (angVel > angVelMin)
{
angVel += angAccel * DT;
MathUtils.Constrain(ref angVel, angVelMin, 0);
}
}
var angleChange = angVel * DT;
angleTotal += angleChange;
var targetAngle = startAngle + angleTotal;
var targetLat = center.Lat + Math.Cos(targetAngle) * radius * GeographyUtils.METERS_TO_LAT_SPAN;
var targetLng = center.Lng + Math.Sin(targetAngle) * radius * GeographyUtils.CalcMetersToLngSpan(copter.Latitude);
copter.MoveToIdealState(new CopterState
{
Latitude = targetLat,
Longitude = targetLng,
Heading = (float)GeographyUtils.RadToDeg(GeographyUtils.CalcDirection2D(targetLat, targetLng, center.Lat, center.Lng)),
VelocityDirection = GeographyUtils.CalcDirection2D(center.Lat, center.Lng, targetLat, targetLng)
+ Math.PI / 2,
Velocity = angVel * radius
});
log.Add(new
{
time = DateTime.Now,
targetLat,
targetLng,
lat = copter.Latitude,
lng = copter.Longitude,
alt = copter.Altitude,
angAccel,
angVel,
angleChange,
angleTotal
});
};
#endregion guide.UpdateAction
guide.FinishAction = () =>
{
_logger.Log(JsonConvert.SerializeObject(log));
};
});
await EnsureAllTasksFinishedAsync().ConfigureAwait(false);
}
public Task FlyInRectangleAsync(IEnumerable<ICopter> copters, float startDirection, float sideLength1, float sideLength2, bool clockwise = true, bool loop = false)
{
throw new NotImplementedException();
//await AllStop();
//Parallel.ForEach(copters, copter =>
//{
// const int pointCount = 4;
// var points = new LatLng[pointCount];
// // 把起始点也加进去是为了循环飞行。
// points[0] = new LatLng { Lat = copter.Latitude, Lng = copter.Longitude };
// var directionRad = startDirection * GeographyUtils.DegreesToRad;
// float distance;
// LatLng lastPoint;
// for (int i = 1; i < pointCount; ++i)
// {
// distance = i % 2 != 0 ? sideLength1 : sideLength2;
// lastPoint = points[i - 1];
// points[i] = new LatLng
// {
// Lat = lastPoint.Lat + distance * Math.Cos(directionRad) * GeographyUtils.MetersToLocalLat,
// Lng = lastPoint.Lng + distance * Math.Sin(directionRad) * GeographyUtils.GetMetersToLocalLon(lastPoint.Lat)
// };
// if (clockwise)
// {
// directionRad += Math.PI / 2;
// }
// else
// {
// directionRad -= Math.PI / 2;
// }
// }
// var target = _guides[copter] ?? new Guide();
// target.LatLngs = points;
// target.IsLoop = loop;
// target.CurrentIndex = 0;
// _guides[copter] = target;
// _logger.Log(string.Join(Environment.NewLine, points.Select(p => $"{p.Lat} {p.Lng}")), Category.Debug, Priority.None);
//});
//await Task.Run(() =>
//{
// while (AnyCopterFlyingToLatLng())
// {
// Thread.Sleep(100);
// }
//});
}
public async Task FlyToVerticalLineAndMakeCircle(IEnumerable<ICopter> copters, float centerDirection, float radius, bool clockwise = true, bool loop = false, double? verLimit = null)
{
await AllStop().ConfigureAwait(false);
if (!copters.Any()) return;
// 1. 飞到不同的高度。
int i = 0;
var tasks = copters.Select(copter =>
{
var targetAlt = 40 + 5 * i++;
return FlyToAltitudeAsync(copter, targetAlt);
});
await Task.WhenAll(tasks).ConfigureAwait(false);
// 2. 飞到相同经纬度。
var targetLat = copters.Average(c => c.Latitude);
var targetLng = copters.Average(c => c.Longitude);
tasks = copters.Select(copter =>
{
return FlyToLatLngAsync(copter, targetLat, targetLng);
});
await Task.WhenAll(tasks).ConfigureAwait(false);
// 3. 画圈。
await FlyInCircleAsync(copters, centerDirection, radius, clockwise, loop, verLimit);
}
}
class Guide
{
public bool IsLoop { get; set; }
public Func<bool> FinishPredicate { get; set; }
public Action FinishAction { get; set; }
public Action UpdateAction { get; set; }
}
struct CopterState
{
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public float? Altitude { get; set; }
public float? Heading { get; set; }
public double VelocityDirection { get; set; }
public double Velocity { get; set; }
}
static class ICopterExtensions
{
const double DECELERATE_DISTANCE = 5;
internal static void MoveToIdealState(this ICopter copter, CopterState idealState)
{
ushort channel1, channel2;
CalcChannel1And2(copter, idealState, out channel1, out channel2);
ushort channel3 = CalcChannel3(copter, idealState);
//copter.SetChannels(new ChannelBag
//{
// Channel1 = channel1,
// Channel2 = channel2,
// Channel3 = channel3
//});
copter.SetMobileControlAsync(ch1: channel1, ch2: channel2, ch3: channel3, yaw: idealState.Heading);
}
private static void CalcChannel1And2(ICopter copter, CopterState idealState, out ushort channel1, out ushort channel2)
{
if (idealState.Latitude == null || idealState.Longitude == null)
{
channel1 = channel2 = HOVER_CHANNEL;
return;
}
var distance = GeographyUtils.CalcDistance2D(copter.Latitude, copter.Longitude, idealState.Latitude.Value, idealState.Longitude.Value);
var moveDirection = GeographyUtils.CalcDirection2D(copter.Latitude, copter.Longitude, idealState.Latitude.Value, idealState.Longitude.Value)
- copter.Heading.DegToRad();
var coefficient = distance >= DECELERATE_DISTANCE ? 1.0 : distance / DECELERATE_DISTANCE;
var channelDelta1 = MAX_CHANNEL_DELTA * coefficient;
var ch1Delta1 = channelDelta1 * Math.Sin(moveDirection);
var ch2Delta1 = -channelDelta1 * Math.Cos(moveDirection);
var channelDelta2 = MAX_CHANNEL_DELTA * idealState.Velocity / MAX_VEL;
var idealVelDirectionRelativeToHeading = idealState.VelocityDirection - copter.Heading.DegToRad();
var ch1Delta2 = channelDelta2 * Math.Sin(idealVelDirectionRelativeToHeading);
var ch2Delta2 = -channelDelta2 * Math.Cos(idealVelDirectionRelativeToHeading);
var ch1Delta = ch1Delta1 + ch1Delta2;
var ch2Delta = ch2Delta1 + ch2Delta2;
MathUtils.Constrain(ref ch1Delta, -MAX_CHANNEL_DELTA, MAX_CHANNEL_DELTA);
MathUtils.Constrain(ref ch2Delta, -MAX_CHANNEL_DELTA, MAX_CHANNEL_DELTA);
channel1 = (ushort)(ch1Delta > 0 ? MAX_HOVER_CHANNEL + ch1Delta : MIN_HOVER_CHANNEL + ch1Delta);
channel2 = (ushort)(ch2Delta > 0 ? MAX_HOVER_CHANNEL + ch2Delta : MIN_HOVER_CHANNEL + ch2Delta);
}
private static ushort CalcChannel3(ICopter copter, CopterState idealState)
{
if (idealState.Altitude == null)
{
return HOVER_CHANNEL;
}
var distance = idealState.Altitude - copter.Altitude;
var coefficient = distance >= DECELERATE_DISTANCE ? 1.0 : distance / DECELERATE_DISTANCE;
var ch3Delta = MAX_CHANNEL_DELTA * coefficient;
return (ushort)(ch3Delta > 0 ? MAX_HOVER_CHANNEL + ch3Delta : MIN_HOVER_CHANNEL + ch3Delta);
}
}
}