using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Remoting;
using Sandbox.ModAPI.Ingame;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class CameraReadinessTests
{
static T Field<T>(object value,string name)
{return (T)value.GetType().GetField(name,BindingFlags.NonPublic|BindingFlags.Instance).GetValue(value);}
static Stub<IMyCameraBlock> Camera(ReturnNavigationTests.Rig r,string name,long id,Func<MatrixD> pose)
{
var c=new Stub<IMyCameraBlock>().Set("CustomName",name).Set("EntityId",id).Set("CubeGrid",r.Program.Me.CubeGrid)
.Set("IsFunctional",true).Set("Enabled",true).Set("IsWorking",true).Set("EnableRaycast",true)
.Set("AvailableScanRange",100000d).Set("RaycastConeLimit",45f).Set("RaycastDistanceLimit",-1d)
.Method("get_WorldMatrix",a=>pose()).Method("GetPosition",a=>pose().Translation);
c.Method("CanScan",a=>{
var target=(Vector3D)a[0];var local=Vector3D.TransformNormal(target-pose().Translation,MatrixD.Transpose(pose()));
return c.Value.IsWorking && Vector3D.Distance(target,pose().Translation)<=c.Value.AvailableScanRange && -local.Z>0 &&
Math.Abs(Math.Atan2(local.X,-local.Z))<=Math.PI/4 &&
Math.Abs(Math.Atan2(local.Y,Math.Sqrt(local.X*local.X+local.Z*local.Z)))<=Math.PI/4;
});
return c;
}
[Fact]
public void SavedFourteenMetreApproachCompletesWithTheInstalledCameraLayout()
{
var r=new ReturnNavigationTests.Rig();
var position=new Vector3D(-67811.481492656632,-123686.02419437817,-162113.20791115251);
r.Orientation=MatrixD.CreateWorld(position,new Vector3D(-.22140851616859436,.76646673679351807,.60291552543640137),
new Vector3D(.86432009935379028,-.13206471502780914,.48529350757598877));
r.Controller.Method("GetPosition",a=>position);
r.Ship.Radius=4.6314601354267158;r.Ship.BodyMin=new Vector3D(-2.25,-1.75,-2.25);r.Ship.BodyMax=new Vector3D(2.25,1.25,2.25);
r.Ship.BodyHalfSize=Vector3D.Max(Vector3D.Abs(r.Ship.BodyMin),Vector3D.Abs(r.Ship.BodyMax));r.Ship.Cameras.Clear();
var positions=new[]{new Vector3D(0,1,1.5),new Vector3D(0,1,0),new Vector3D(2,0,1),new Vector3D(0,-.5,-.5),
new Vector3D(0,-1.5,.5),new Vector3D(-2,0,1),new Vector3D(1.5,0,2),new Vector3D(-1.5,0,2)};
var directions=new[]{Vector3D.Backward,Vector3D.Forward,Vector3D.Right,Vector3D.Forward,Vector3D.Forward,Vector3D.Left,Vector3D.Backward,Vector3D.Backward};
var ups=new[]{Vector3D.Up,Vector3D.Up,Vector3D.Down,Vector3D.Down,Vector3D.Down,Vector3D.Backward,Vector3D.Right,Vector3D.Right};
for(int n=0;n<positions.Length;n++)
{
var pose=MatrixD.CreateWorld(positions[n],directions[n],ups[n])*r.Orientation;
r.Ship.Cameras.Add(Camera(r,"Installed "+n,n+1,()=>pose).Value);
}
var target=new Vector3D(-67808.36607753133,-123696.7388086597,-162121.59648775522);
r.Flight.SetDockContact(50,new Vector3D(-67807.59084232424,-123699.42231776408,-162123.70730985893),1.5);
for(int n=0;n<18 && r.Flight.ScanClearance==0;n++)
{
r.Program.Now+=1d/60;
r.Flight.Move(target,Vector3D.Zero,r.Orientation.Forward,r.Orientation.Up,1.83,.1,true,1d/60,false,false);
}
Assert.InRange(r.Flight.ScanClearance,16,17);Assert.True(r.HasTranslation,r.Flight.Problem+" / "+r.Flight.CameraDiagnostics);
}
static MyDetectedEntityInfo Wall(Vector3D point)
{
return new MyDetectedEntityInfo(50,"Berth railing",MyDetectedEntityType.LargeGrid,point,MatrixD.Identity,Vector3.Zero,
VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,new BoundingBoxD(point-new Vector3D(.1),point+new Vector3D(.1)),1);
}
static void Approach(ReturnNavigationTests.Rig r)
{
r.Program.Now+=.05;
r.Flight.Move(Vector3D.Forward*14,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2,.1,true,.05,false,false);
}
[Fact]
public void FarRailingHitCertifiesTheClearApproachBeforeItInsteadOfBlockingAllFourteenMetres()
{
var r=new ReturnNavigationTests.Rig();int rays=0;
r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
r.Camera.Method("Raycast",a=>{rays++;var end=(Vector3D)a[0];return -end.Z>10?Wall(end*(10/-end.Z)):default(MyDetectedEntityInfo);});
for(int n=0;n<9;n++)
{
Approach(r);
if(n<8)Assert.False(r.HasTranslation);
Assert.False(r.Flight.Blocked,"An unfinished sweep must not classify the whole route as impassable.");
}
Assert.Equal(18,rays);Assert.InRange(r.Flight.ScanClearance,9,10);Assert.True(r.HasTranslation,r.Flight.Problem);
Assert.Contains("Berth railing",r.Flight.Problem);
}
[Fact]
public void ObstacleReasonRemainsVisibleWhileTheNextSweepIsIncomplete()
{
var r=new ReturnNavigationTests.Rig();int rays=0;
r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
r.Camera.Method("Raycast",a=>{rays++;return rays%18>=10 || rays%18==0?Wall(Vector3D.Forward*.8):default(MyDetectedEntityInfo);});
for(int n=0;n<9;n++)Approach(r);
for(int n=0;n<25;n++)
{
Approach(r);Assert.False(r.HasTranslation);
Assert.Contains("Berth railing",r.Flight.Problem);Assert.DoesNotContain(P.L.MinerEnvelopeScanPending,r.Flight.Problem);
}
}
[Fact]
public void NewObstacleImmediatelyShortensExistingClearanceBeforeSweepCompletion()
{
var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
for(int n=0;n<9;n++)Approach(r);Assert.True(r.Flight.ScanClearance>10);
r.Camera.Method("Raycast",a=>Wall(Vector3D.Forward*.8));Approach(r);
Assert.False(r.HasTranslation);Assert.InRange(r.Flight.ScanClearance,0,.8);Assert.Contains("Berth railing",r.Flight.Problem);
}
[Fact]
public void RailingClearanceIsReplacedOnlyAfterAnEntireFreshUnobstructedSweep()
{
var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
r.Camera.Method("Raycast",a=>Wall(Vector3D.Forward*.8));for(int n=0;n<9;n++)Approach(r);
r.Camera.Method("Raycast",a=>default(MyDetectedEntityInfo));
for(int n=0;n<8;n++){Approach(r);Assert.False(r.HasTranslation);Assert.Contains("Berth railing",r.Flight.Problem);}
Approach(r);Assert.True(r.HasTranslation);Assert.DoesNotContain("Berth railing",r.Flight.Problem);
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
public void MissingOrInvalidSurfaceHitNeverCertifiesFreeSpaceToTheEntityCentre(int kind)
{
var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
for(int n=0;n<9;n++)Approach(r);Assert.True(r.HasTranslation);
Vector3D? point=kind==0?(Vector3D?)null:new Vector3D(kind==1?double.NaN:double.PositiveInfinity);
r.Camera.Method("Raycast",a=>new MyDetectedEntityInfo(50,"Unknown surface",MyDetectedEntityType.LargeGrid,point,
MatrixD.Identity,Vector3.Zero,VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,
new BoundingBoxD(new Vector3D(-1,-1,-11),new Vector3D(1,1,-9)),1));
for(int n=0;n<20;n++)
{
Approach(r);Assert.Equal(0,r.Flight.ScanClearance);Assert.False(r.HasTranslation);
Assert.Contains(P.L.F(P.L.CameraObstacleUnknown,"Unknown surface"),r.Flight.Problem);
}
}
[Fact]
public void ALongNoseUsesVisibleSamplesInTheSameLanesInsteadOfImpossibleCloseCorners()
{
var r=new ReturnNavigationTests.Rig();r.Ship.Radius=44;r.Ship.BodyHalfSize=new Vector3D(5,15,40);
r.Ship.BodyMin=-r.Ship.BodyHalfSize;r.Ship.BodyMax=r.Ship.BodyHalfSize;
r.Ship.Cameras.Clear();Vector3D front=Vector3D.Forward*40;
r.Ship.Cameras.Add(Camera(r,"Front",1,()=>MatrixD.CreateWorld(front,Vector3D.Forward,Vector3D.Up)).Value);
r.Ship.Cameras.Add(Camera(r,"Rear",2,()=>MatrixD.CreateWorld(Vector3D.Backward*40,Vector3D.Backward,Vector3D.Up)).Value);
var endpoints=new List<Vector3D>();var camera=r.Ship.Cameras[0];
((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(camera)).Method("Raycast",a=>{
Assert.True(camera.CanScan((Vector3D)a[0]));endpoints.Add((Vector3D)a[0]);return default(MyDetectedEntityInfo);
});
for(int n=0;n<30 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
Assert.True(r.HasTranslation);Assert.Equal(18,endpoints.Count);Assert.Equal(50.2,r.Flight.ScanClearance,8);
Assert.Contains(endpoints.GetRange(0,9),point=>-point.Z>Field<double>(r.Flight,"scanLength")*.35+.1);
Assert.Contains(endpoints,point=>point.Y>15);Assert.Contains(endpoints,point=>point.Y<-15);
}
[Theory]
[InlineData("disabled")]
[InlineData("damaged")]
[InlineData("power")]
[InlineData("raycast")]
public void CameraHardwareFailuresHaveDistinctNamesAndNeverAuthoriseMotion(string kind)
{
var r=new ReturnNavigationTests.Rig();r.Camera.Set("CustomName","Lens").Method("CanScan",a=>false);
string format;
if(kind=="raycast") {r.Camera.Set("EnableRaycast",false);format=P.L.CameraRaycastDisabled;}
else
{
r.Camera.Set("IsWorking",false);
if(kind=="disabled") {r.Camera.Set("Enabled",false);format=P.L.CameraDisabled;}
else if(kind=="damaged") {r.Camera.Set("IsFunctional",false);format=P.L.CameraDamaged;}
else format=P.L.CameraUnpowered;
}
r.Step(Vector3D.Forward*100);
Assert.Equal(P.L.F(format,"Lens"),r.Flight.Problem);Assert.False(r.HasTranslation);
Assert.False(Field<bool>(r.Flight,"scanWaitingForCharge"));
}
[Fact]
public void AChargingCameraReportsAvailableAndRequiredRangeInsteadOfAnOrientationFailure()
{
var r=new ReturnNavigationTests.Rig();r.Charge=0;r.Camera.Set("CustomName","Lens").Method("TimeUntilScan",a=>1200);
r.Step(Vector3D.Forward*100);
Assert.True(Field<bool>(r.Flight,"scanWaitingForCharge"));Assert.False(r.HasTranslation);
Assert.Equal(P.L.F(P.L.CameraCharging,"Lens",0d,Field<double>(r.Flight,"scanLength")*.35,1.2),r.Flight.Problem);
}
[Fact]
public void ChargeRangeReductionCannotShrinkInsideTheLeadingHull()
{
var r=new ReturnNavigationTests.Rig();r.Charge=0;r.Ship.BodyMin=new Vector3D(-1,-1,-10);r.Ship.BodyMax=new Vector3D(1,1,1);
for(int n=0;n<500;n++)r.Step(Vector3D.Forward*100,false,1);
Assert.True(Field<double>(r.Flight,"scanLimit")>=11);Assert.False(r.HasTranslation);Assert.Equal(0,r.Flight.ScanClearance);
}
[Fact]
public void PrepareFlightEnablesOnlyTheManagedLocalCameras()
{
var r=new ReturnNavigationTests.Rig();r.Camera.Set("Enabled",false).Set("EnableRaycast",false);
var other=Camera(r,"Foreign",9,()=>MatrixD.Identity).Set("CubeGrid",new Stub<VRage.Game.ModAPI.Ingame.IMyCubeGrid>().Value).Set("Enabled",false).Set("EnableRaycast",false);
r.Ship.Cameras.Add(other.Value);r.Ship.PrepareFlight();
Assert.True(r.Camera.Value.Enabled);Assert.True(r.Camera.Value.EnableRaycast);
Assert.False(other.Value.Enabled);Assert.False(other.Value.EnableRaycast);
}
[Fact]
public void AStaticHundredCamerasReportTheActualDirectionalFailureWithoutLosingRange()
{
var r=new ReturnNavigationTests.Rig();r.Ship.Cameras.Clear();
for(int n=0;n<100;n++)
r.Ship.Cameras.Add(Camera(r,"Lens-"+n,n+1,()=>MatrixD.Identity).Value);
for(int n=0;n<90;n++)r.Step(Vector3D.Right*100);
Assert.Contains(P.L.F(P.L.CameraInventory,100,100,0),r.Flight.CameraDiagnostics);
Assert.False(r.HasTranslation);Assert.False(Field<bool>(r.Flight,"scanWaitingForCharge"));
Assert.Equal(2000,Field<double>(r.Flight,"scanLimit"));Assert.Contains("90.0",r.Flight.CameraDiagnostics);
Assert.Equal(P.L.F(P.L.CameraRegionUnavailable,1,100),r.Flight.Problem);
}
static List<IMyTerminalBlock> TerminalBlocks(MinerStateTests.MinerRig r)
{
var blocks=new List<IMyTerminalBlock> {r.Controller.Value,r.Connector.Value,r.Drill.Value,r.Camera.Value};
foreach(var thrust in r.Hardware.Thrusters)
{
var definition=thrust.BlockDefinition;definition.SubtypeName="MockThruster";
((Stub<IMyThrust>)RemotingServices.GetRealProxy(thrust)).Set("BlockDefinition",definition);
}
blocks.AddRange(r.Hardware.Thrusters);blocks.AddRange(r.Hardware.Gyros);blocks.AddRange(r.Hardware.Batteries);
return blocks;
}
static Stub<IMyCameraBlock> InstalledCamera(MinerStateTests.MinerRig r,long id)
{
return new Stub<IMyCameraBlock>().Set("CubeGrid",r.Program.Me.CubeGrid).Set("EntityId",id)
.Set("IsFunctional",true).Set("WorldMatrix",MatrixD.Identity).Set("Enabled",false).Set("EnableRaycast",false);
}
static void SupplyBlocks(MinerStateTests.MinerRig r,List<IMyTerminalBlock> blocks,List<IMyTerminalBlock> groupBlocks=null)
{
var system=new Stub<IMyGridTerminalSystem>().Method("GetBlocksOfType",a=>{
var list=(List<IMyTerminalBlock>)a[0];var filter=(Func<IMyTerminalBlock,bool>)a[1];
foreach(var block in blocks)if(filter==null || filter(block))list.Add(block);return null;
});
if(groupBlocks!=null)
{
var group=new Stub<IMyBlockGroup>().Method("GetBlocks",a=>{((List<IMyTerminalBlock>)a[0]).AddRange(groupBlocks);return null;});
system.Method("GetBlockGroupWithName",a=>{Assert.Equal("Managed",a[0]);return group.Value;});
}
TestRig.SetBase(r.Program,"GridTerminalSystem",system.Value);
}
[Fact]
public void MinerInitializationDiscoversAndEnablesLocalCamerasAndMeasuresTheHull()
{
var r=new MinerStateTests.MinerRig();var grid=(Stub<VRage.Game.ModAPI.Ingame.IMyCubeGrid>)RemotingServices.GetRealProxy(r.Program.Me.CubeGrid);
grid.Set("Min",new Vector3I(-10)).Set("Max",new Vector3I(10)).Method("GridIntegerToWorld",a=>new Vector3D((Vector3I)a[0])*2.5);
var added=InstalledCamera(r,2);var foreign=InstalledCamera(r,3).Set("CubeGrid",new Stub<VRage.Game.ModAPI.Ingame.IMyCubeGrid>().Value);
var blocks=TerminalBlocks(r);blocks.Add(added.Value);blocks.Add(foreign.Value);SupplyBlocks(r,blocks);
var miner=new P.MinerController(r.Program);var hardware=Field<P.ShipHardware>(miner,"ship");
Assert.Equal(2,hardware.Cameras.Count);Assert.True(added.Value.Enabled);Assert.True(added.Value.EnableRaycast);
Assert.False(foreign.Value.Enabled);Assert.True(hardware.BodyMax.X>25);
Assert.True(Field<bool>(miner,"healthy"));
}
[Theory]
[InlineData("init")]
[InlineData("rescan")]
public void ExplicitInitializationUpdatesAddedAndRemovedCameras(string command)
{
var r=new MinerStateTests.MinerRig();r.State(P.FlightState.Paused);
var added=InstalledCamera(r,2);var blocks=TerminalBlocks(r);blocks.Add(added.Value);SupplyBlocks(r,blocks);
Assert.Single(r.Hardware.Cameras);r.Miner.Command(command);
Assert.Equal(2,r.Hardware.Cameras.Count);Assert.True(added.Value.Enabled);Assert.True(added.Value.EnableRaycast);
blocks.RemoveAll(block=>object.ReferenceEquals(block,r.Camera.Value));r.Miner.Command(command);
Assert.Same(added.Value,Assert.Single(r.Hardware.Cameras));
}
[Fact]
public void InitializationRespectsTheConfiguredHardwareGroup()
{
var r=new MinerStateTests.MinerRig();r.Program.Config=new P.Settings("[Hardware]\nGroup=Managed","miner","test");
var selected=TerminalBlocks(r);var outside=InstalledCamera(r,2);var all=new List<IMyTerminalBlock>(selected) {outside.Value};
SupplyBlocks(r,all,selected);var miner=new P.MinerController(r.Program);
Assert.Same(r.Camera.Value,Assert.Single(Field<P.ShipHardware>(miner,"ship").Cameras));Assert.False(outside.Value.Enabled);
}
[Theory]
[InlineData(P.FlightState.Returning)]
[InlineData(P.FlightState.Holding)]
[InlineData(P.FlightState.Paused)]
[InlineData(P.FlightState.Manual)]
public void HealthyMinerNeverAutomaticallyRediscoversCameras(P.FlightState state)
{
var r=new MinerStateTests.MinerRig();r.State(state);int scans=0;
TestRig.SetBase(r.Program,"GridTerminalSystem",new Stub<IMyGridTerminalSystem>().Method("GetBlocksOfType",a=>{scans++;return null;}).Value);
foreach(double now in new[]{0d,5d,10d,30d}) {r.Program.Now=now;r.Miner.Tick(.1);}
Assert.Equal(0,scans);Assert.Same(r.Camera.Value,Assert.Single(r.Hardware.Cameras));
}
static ReturnNavigationTests.Rig ViewGapRig()
{
var r=new ReturnNavigationTests.Rig();r.Ship.Radius=16;r.Ship.BodyHalfSize=new Vector3D(5,5,10);
r.Ship.BodyMin=-r.Ship.BodyHalfSize;r.Ship.BodyMax=r.Ship.BodyHalfSize;r.Ship.Cameras.Clear();
r.Ship.Cameras.Add(Camera(r,"Front",1,()=>MatrixD.CreateWorld(Vector3D.Forward*10,Vector3D.Forward,Vector3D.Up)).Value);
r.Ship.Cameras.Add(Camera(r,"Rear",2,()=>MatrixD.CreateWorld(Vector3D.Backward*10,Vector3D.Backward,Vector3D.Up)).Value);
return r;
}
[Fact]
public void AShortWaypointDoesNotSqueezeTheCameraHorizonAgainstTheNose()
{
var r=ViewGapRig();int rays=0;
foreach(var camera in r.Ship.Cameras)((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(camera)).Method("Raycast",a=>{rays++;return default(MyDetectedEntityInfo);});
for(int n=0;n<30 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
Assert.True(r.HasTranslation);Assert.False(r.Flight.Blocked);Assert.Equal("",r.Flight.Problem);Assert.Equal(18,rays);
Assert.Equal(20.2,r.Flight.ScanClearance,8);
Assert.Contains(P.L.F(P.L.CameraCoverage,18,1),r.Flight.CameraDiagnostics);
}
[Fact]
public void AnIncompleteCombinedScanDoesNotCountInvisibleRegionsAsScanned()
{
var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);
for(int n=0;n<30;n++)r.Step(Vector3D.Right*100);
Assert.False(r.HasTranslation);Assert.Equal(0,r.Gyro.Value.Yaw);Assert.Equal(0,r.Flight.ScanClearance);
Assert.Equal(0,r.Rays);Assert.Contains(P.L.F(P.L.CameraCoverage,0,0),r.Flight.CameraDiagnostics);
}
[Fact]
public void CombinedScanningStillBrakesForObstaclesInsideTheCurrentLeg()
{
var r=ViewGapRig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);int hits=0;
var camera=(Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(r.Ship.Cameras[0]);
camera.Method("Raycast",a=>{
if(((Vector3D)a[0]).Z<-20) {hits++;return ReturnNavigationTests.Rig.Detection(42);}
return default(MyDetectedEntityInfo);
});
for(int n=0;n<20 && !r.Flight.Blocked;n++) {r.Step(Vector3D.Forward*10);Assert.False(r.HasTranslation);}
Assert.True(r.Flight.Blocked);Assert.Equal(1,hits);Assert.Equal(0,r.Flight.ScanClearance);
}
[Fact]
public void CombinedClearanceDoesNotSurviveARealObstacleOnTheNextSweep()
{
var r=ViewGapRig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);
for(int n=0;n<9;n++)r.Step(Vector3D.Forward*10);Assert.True(r.HasTranslation);
((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(r.Ship.Cameras[0])).Method("Raycast",a=>ReturnNavigationTests.Rig.Detection(42));
r.Program.Now+=.4;r.Step(Vector3D.Forward*10);
Assert.True(r.Flight.Blocked);Assert.False(r.HasTranslation);
}
[Fact]
public void CombinedClearanceExpiresWhenCamerasCannotRecharge()
{
var r=ViewGapRig();for(int n=0;n<9;n++)r.Step(Vector3D.Forward*10);Assert.True(r.HasTranslation);
foreach(var camera in r.Ship.Cameras)((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(camera)).Set("AvailableScanRange",0d);
for(int n=0;n<40;n++)r.Step(Vector3D.Forward*10);
Assert.False(r.HasTranslation);Assert.True(Field<bool>(r.Flight,"scanWaitingForCharge"));
}
[Fact]
public void AnObservedObstaclePastAShortWaypointDoesNotBlockTheCurrentLeg()
{
var r=ViewGapRig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);int hits=0;
((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(r.Ship.Cameras[0])).Method("Raycast",a=>{
if(((Vector3D)a[0]).Z>-25)return default(MyDetectedEntityInfo);
hits++;return new MyDetectedEntityInfo(42,"beyond waypoint",MyDetectedEntityType.LargeGrid,Vector3D.Forward*25,
MatrixD.Identity,Vector3.Zero,VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,new BoundingBoxD(new Vector3D(-1),new Vector3D(1)),1);
});
for(int n=0;n<30 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
Assert.True(r.HasTranslation);Assert.Equal(9,hits);Assert.False(r.Flight.Blocked);Assert.Equal(20.2,r.Flight.ScanClearance,8);
}
[Fact]
public void OneSelfObstructedCameraDoesNotInterruptTwoOtherCamerasOrExistingClearance()
{
var r=new ReturnNavigationTests.Rig();bool blocked=false;int selfHits=0,clearHits=0;
r.Hit=()=>{if(blocked) {selfHits++;return ReturnNavigationTests.Rig.Detection(r.Program.Me.CubeGrid.EntityId);}return default(MyDetectedEntityInfo);};
for(int id=2;id<=3;id++)r.Ship.Cameras.Add(r.MakeCamera(id,()=>10000,()=>{clearHits++;return default(MyDetectedEntityInfo);}).Value);
for(int n=0;n<9;n++)r.Step(Vector3D.Forward*100);Assert.True(r.HasTranslation);
blocked=true;
for(int n=0;n<90;n++)
{
r.Step(Vector3D.Forward*100);
Assert.True(r.HasTranslation);Assert.True(r.Flight.ScanClearance>0);Assert.False(r.Flight.Blocked);
}
Assert.True(selfHits>=18);Assert.True(clearHits>=18);
}
[Fact]
public void ACameraBlockedForOnePointCanStillCoverOtherPoints()
{
var r=new ReturnNavigationTests.Rig();int recovered=0,alternate=0;
r.Hit=()=>ReturnNavigationTests.Rig.Detection(r.Program.Me.CubeGrid.EntityId);
r.Ship.Cameras.Add(r.MakeCamera(2,()=>10000,()=>{alternate++;return default(MyDetectedEntityInfo);}).Value);
r.Step(Vector3D.Forward*100);
Assert.Equal(1,alternate);r.Hit=()=>{recovered++;return default(MyDetectedEntityInfo);};
for(int n=0;n<15 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*100);
Assert.Equal(17,recovered);Assert.True(r.Flight.ScanClearance>0);Assert.True(r.HasTranslation);
}
[Fact]
public void ThreeFrontCamerasShareRegionsEvenWhenTheCenterCameraIsObstructed()
{
var r=ViewGapRig();r.Ship.Cameras.Clear();int left=0,right=0,blocked=0;
var center=Camera(r,"Center",1,()=>MatrixD.CreateWorld(new Vector3D(0,0,-10),Vector3D.Forward,Vector3D.Up));
center.Method("Raycast",a=>{blocked++;return ReturnNavigationTests.Rig.Detection(r.Program.Me.CubeGrid.EntityId);});
var leftCamera=Camera(r,"Left",2,()=>MatrixD.CreateWorld(new Vector3D(-4,0,-10),Vector3D.Forward,Vector3D.Up));
leftCamera.Method("Raycast",a=>{left++;return default(MyDetectedEntityInfo);});
var rightCamera=Camera(r,"Right",3,()=>MatrixD.CreateWorld(new Vector3D(4,0,-10),Vector3D.Forward,Vector3D.Up));
rightCamera.Method("Raycast",a=>{right++;return default(MyDetectedEntityInfo);});
r.Ship.Cameras.Add(center.Value);r.Ship.Cameras.Add(leftCamera.Value);r.Ship.Cameras.Add(rightCamera.Value);
for(int n=0;n<40 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
Assert.True(r.HasTranslation);Assert.True(blocked>0);Assert.True(left>0);Assert.True(right>0);Assert.Equal(18,left+right);
Assert.Contains(P.L.F(P.L.CameraCoverage,18,2),r.Flight.CameraDiagnostics);
}
}
}
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Remoting;
using Sandbox.ModAPI.Ingame;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class CameraReadinessTests
{
static T Field<T>(object value,string name)
{return (T)value.GetType().GetField(name,BindingFlags.NonPublic|BindingFlags.Instance).GetValue(value);}
static Stub<IMyCameraBlock> Camera(ReturnNavigationTests.Rig r,string name,long id,Func<MatrixD> pose)
{
var c=new Stub<IMyCameraBlock>().Set("CustomName",name).Set("EntityId",id).Set("CubeGrid",r.Program.Me.CubeGrid)
.Set("IsFunctional",true).Set("Enabled",true).Set("IsWorking",true).Set("EnableRaycast",true)
.Set("AvailableScanRange",100000d).Set("RaycastConeLimit",45f).Set("RaycastDistanceLimit",-1d)
.Method("get_WorldMatrix",a=>pose()).Method("GetPosition",a=>pose().Translation);
c.Method("CanScan",a=>{
var target=(Vector3D)a[0];var local=Vector3D.TransformNormal(target-pose().Translation,MatrixD.Transpose(pose()));
return c.Value.IsWorking && Vector3D.Distance(target,pose().Translation)<=c.Value.AvailableScanRange && -local.Z>0 &&
Math.Abs(Math.Atan2(local.X,-local.Z))<=Math.PI/4 &&
Math.Abs(Math.Atan2(local.Y,Math.Sqrt(local.X*local.X+local.Z*local.Z)))<=Math.PI/4;
});
return c;
}
[Fact]
public void SavedFourteenMetreApproachCompletesWithTheInstalledCameraLayout()
{
var r=new ReturnNavigationTests.Rig();
var position=new Vector3D(-67811.481492656632,-123686.02419437817,-162113.20791115251);
r.Orientation=MatrixD.CreateWorld(position,new Vector3D(-.22140851616859436,.76646673679351807,.60291552543640137),
new Vector3D(.86432009935379028,-.13206471502780914,.48529350757598877));
r.Controller.Method("GetPosition",a=>position);
r.Ship.Radius=4.6314601354267158;r.Ship.BodyMin=new Vector3D(-2.25,-1.75,-2.25);r.Ship.BodyMax=new Vector3D(2.25,1.25,2.25);
r.Ship.BodyHalfSize=Vector3D.Max(Vector3D.Abs(r.Ship.BodyMin),Vector3D.Abs(r.Ship.BodyMax));r.Ship.Cameras.Clear();
var positions=new[]{new Vector3D(0,1,1.5),new Vector3D(0,1,0),new Vector3D(2,0,1),new Vector3D(0,-.5,-.5),
new Vector3D(0,-1.5,.5),new Vector3D(-2,0,1),new Vector3D(1.5,0,2),new Vector3D(-1.5,0,2)};
var directions=new[]{Vector3D.Backward,Vector3D.Forward,Vector3D.Right,Vector3D.Forward,Vector3D.Forward,Vector3D.Left,Vector3D.Backward,Vector3D.Backward};
var ups=new[]{Vector3D.Up,Vector3D.Up,Vector3D.Down,Vector3D.Down,Vector3D.Down,Vector3D.Backward,Vector3D.Right,Vector3D.Right};
for(int n=0;n<positions.Length;n++)
{
var pose=MatrixD.CreateWorld(positions[n],directions[n],ups[n])*r.Orientation;
r.Ship.Cameras.Add(Camera(r,"Installed "+n,n+1,()=>pose).Value);
}
var target=new Vector3D(-67808.36607753133,-123696.7388086597,-162121.59648775522);
r.Flight.SetDockContact(50,new Vector3D(-67807.59084232424,-123699.42231776408,-162123.70730985893),1.5);
for(int n=0;n<18 && r.Flight.ScanClearance==0;n++)
{
r.Program.Now+=1d/60;
r.Flight.Move(target,Vector3D.Zero,r.Orientation.Forward,r.Orientation.Up,1.83,.1,true,1d/60,false,false);
}
Assert.InRange(r.Flight.ScanClearance,16,17);Assert.True(r.HasTranslation,r.Flight.Problem+" / "+r.Flight.CameraDiagnostics);
}
static MyDetectedEntityInfo Wall(Vector3D point)
{
return new MyDetectedEntityInfo(50,"Berth railing",MyDetectedEntityType.LargeGrid,point,MatrixD.Identity,Vector3.Zero,
VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,new BoundingBoxD(point-new Vector3D(.1),point+new Vector3D(.1)),1);
}
static void Approach(ReturnNavigationTests.Rig r)
{
r.Program.Now+=.05;
r.Flight.Move(Vector3D.Forward*14,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2,.1,true,.05,false,false);
}
[Fact]
public void FarRailingHitCertifiesTheClearApproachBeforeItInsteadOfBlockingAllFourteenMetres()
{
var r=new ReturnNavigationTests.Rig();int rays=0;
r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
r.Camera.Method("Raycast",a=>{rays++;var end=(Vector3D)a[0];return -end.Z>10?Wall(end*(10/-end.Z)):default(MyDetectedEntityInfo);});
for(int n=0;n<9;n++)
{
Approach(r);
if(n<8)Assert.False(r.HasTranslation);
Assert.False(r.Flight.Blocked,"An unfinished sweep must not classify the whole route as impassable.");
}
Assert.Equal(18,rays);Assert.InRange(r.Flight.ScanClearance,9,10);Assert.True(r.HasTranslation,r.Flight.Problem);
Assert.Contains("Berth railing",r.Flight.Problem);
}
[Fact]
public void ObstacleReasonRemainsVisibleWhileTheNextSweepIsIncomplete()
{
var r=new ReturnNavigationTests.Rig();int rays=0;
r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
r.Camera.Method("Raycast",a=>{rays++;return rays%18>=10 || rays%18==0?Wall(Vector3D.Forward*.8):default(MyDetectedEntityInfo);});
for(int n=0;n<9;n++)Approach(r);
for(int n=0;n<25;n++)
{
Approach(r);Assert.False(r.HasTranslation);
Assert.Contains("Berth railing",r.Flight.Problem);Assert.DoesNotContain(P.L.MinerEnvelopeScanPending,r.Flight.Problem);
}
}
[Fact]
public void NewObstacleImmediatelyShortensExistingClearanceBeforeSweepCompletion()
{
var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
for(int n=0;n<9;n++)Approach(r);Assert.True(r.Flight.ScanClearance>10);
r.Camera.Method("Raycast",a=>Wall(Vector3D.Forward*.8));Approach(r);
Assert.False(r.HasTranslation);Assert.InRange(r.Flight.ScanClearance,0,.8);Assert.Contains("Berth railing",r.Flight.Problem);
}
[Fact]
public void RailingClearanceIsReplacedOnlyAfterAnEntireFreshUnobstructedSweep()
{
var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
r.Camera.Method("Raycast",a=>Wall(Vector3D.Forward*.8));for(int n=0;n<9;n++)Approach(r);
r.Camera.Method("Raycast",a=>default(MyDetectedEntityInfo));
for(int n=0;n<8;n++){Approach(r);Assert.False(r.HasTranslation);Assert.Contains("Berth railing",r.Flight.Problem);}
Approach(r);Assert.True(r.HasTranslation);Assert.DoesNotContain("Berth railing",r.Flight.Problem);
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
public void MissingOrInvalidSurfaceHitNeverCertifiesFreeSpaceToTheEntityCentre(int kind)
{
var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
for(int n=0;n<9;n++)Approach(r);Assert.True(r.HasTranslation);
Vector3D? point=kind==0?(Vector3D?)null:new Vector3D(kind==1?double.NaN:double.PositiveInfinity);
r.Camera.Method("Raycast",a=>new MyDetectedEntityInfo(50,"Unknown surface",MyDetectedEntityType.LargeGrid,point,
MatrixD.Identity,Vector3.Zero,VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,
new BoundingBoxD(new Vector3D(-1,-1,-11),new Vector3D(1,1,-9)),1));
for(int n=0;n<20;n++)
{
Approach(r);Assert.Equal(0,r.Flight.ScanClearance);Assert.False(r.HasTranslation);
Assert.Contains(P.L.F(P.L.CameraObstacleUnknown,"Unknown surface"),r.Flight.Problem);
}
}
[Fact]
public void ALongNoseUsesVisibleSamplesInTheSameLanesInsteadOfImpossibleCloseCorners()
{
var r=new ReturnNavigationTests.Rig();r.Ship.Radius=44;r.Ship.BodyHalfSize=new Vector3D(5,15,40);
r.Ship.BodyMin=-r.Ship.BodyHalfSize;r.Ship.BodyMax=r.Ship.BodyHalfSize;
r.Ship.Cameras.Clear();Vector3D front=Vector3D.Forward*40;
r.Ship.Cameras.Add(Camera(r,"Front",1,()=>MatrixD.CreateWorld(front,Vector3D.Forward,Vector3D.Up)).Value);
r.Ship.Cameras.Add(Camera(r,"Rear",2,()=>MatrixD.CreateWorld(Vector3D.Backward*40,Vector3D.Backward,Vector3D.Up)).Value);
var endpoints=new List<Vector3D>();var camera=r.Ship.Cameras[0];
((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(camera)).Method("Raycast",a=>{
Assert.True(camera.CanScan((Vector3D)a[0]));endpoints.Add((Vector3D)a[0]);return default(MyDetectedEntityInfo);
});
for(int n=0;n<30 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
Assert.True(r.HasTranslation);Assert.Equal(18,endpoints.Count);Assert.Equal(50.2,r.Flight.ScanClearance,8);
Assert.Contains(endpoints.GetRange(0,9),point=>-point.Z>Field<double>(r.Flight,"scanLength")*.35+.1);
Assert.Contains(endpoints,point=>point.Y>15);Assert.Contains(endpoints,point=>point.Y<-15);
}
[Theory]
[InlineData("disabled")]
[InlineData("damaged")]
[InlineData("power")]
[InlineData("raycast")]
public void CameraHardwareFailuresHaveDistinctNamesAndNeverAuthoriseMotion(string kind)
{
var r=new ReturnNavigationTests.Rig();r.Camera.Set("CustomName","Lens").Method("CanScan",a=>false);
string format;
if(kind=="raycast") {r.Camera.Set("EnableRaycast",false);format=P.L.CameraRaycastDisabled;}
else
{
r.Camera.Set("IsWorking",false);
if(kind=="disabled") {r.Camera.Set("Enabled",false);format=P.L.CameraDisabled;}
else if(kind=="damaged") {r.Camera.Set("IsFunctional",false);format=P.L.CameraDamaged;}
else format=P.L.CameraUnpowered;
}
r.Step(Vector3D.Forward*100);
Assert.Equal(P.L.F(format,"Lens"),r.Flight.Problem);Assert.False(r.HasTranslation);
Assert.False(Field<bool>(r.Flight,"scanWaitingForCharge"));
}
[Fact]
public void AChargingCameraReportsAvailableAndRequiredRangeInsteadOfAnOrientationFailure()
{
var r=new ReturnNavigationTests.Rig();r.Charge=0;r.Camera.Set("CustomName","Lens").Method("TimeUntilScan",a=>1200);
r.Step(Vector3D.Forward*100);
Assert.True(Field<bool>(r.Flight,"scanWaitingForCharge"));Assert.False(r.HasTranslation);
Assert.Equal(P.L.F(P.L.CameraCharging,"Lens",0d,Field<double>(r.Flight,"scanLength")*.35,1.2),r.Flight.Problem);
}
[Fact]
public void ChargeRangeReductionCannotShrinkInsideTheLeadingHull()
{
var r=new ReturnNavigationTests.Rig();r.Charge=0;r.Ship.BodyMin=new Vector3D(-1,-1,-10);r.Ship.BodyMax=new Vector3D(1,1,1);
for(int n=0;n<500;n++)r.Step(Vector3D.Forward*100,false,1);
Assert.True(Field<double>(r.Flight,"scanLimit")>=11);Assert.False(r.HasTranslation);Assert.Equal(0,r.Flight.ScanClearance);
}
[Fact]
public void PrepareFlightEnablesOnlyTheManagedLocalCameras()
{
var r=new ReturnNavigationTests.Rig();r.Camera.Set("Enabled",false).Set("EnableRaycast",false);
var other=Camera(r,"Foreign",9,()=>MatrixD.Identity).Set("CubeGrid",new Stub<VRage.Game.ModAPI.Ingame.IMyCubeGrid>().Value).Set("Enabled",false).Set("EnableRaycast",false);
r.Ship.Cameras.Add(other.Value);r.Ship.PrepareFlight();
Assert.True(r.Camera.Value.Enabled);Assert.True(r.Camera.Value.EnableRaycast);
Assert.False(other.Value.Enabled);Assert.False(other.Value.EnableRaycast);
}
[Fact]
public void AStaticHundredCamerasReportTheActualDirectionalFailureWithoutLosingRange()
{
var r=new ReturnNavigationTests.Rig();r.Ship.Cameras.Clear();
for(int n=0;n<100;n++)
r.Ship.Cameras.Add(Camera(r,"Lens-"+n,n+1,()=>MatrixD.Identity).Value);
for(int n=0;n<90;n++)r.Step(Vector3D.Right*100);
Assert.Contains(P.L.F(P.L.CameraInventory,100,100,0),r.Flight.CameraDiagnostics);
Assert.False(r.HasTranslation);Assert.False(Field<bool>(r.Flight,"scanWaitingForCharge"));
Assert.Equal(2000,Field<double>(r.Flight,"scanLimit"));Assert.Contains("90.0",r.Flight.CameraDiagnostics);
Assert.Equal(P.L.F(P.L.CameraRegionUnavailable,1,100),r.Flight.Problem);
}
static List<IMyTerminalBlock> TerminalBlocks(MinerStateTests.MinerRig r)
{
var blocks=new List<IMyTerminalBlock> {r.Controller.Value,r.Connector.Value,r.Drill.Value,r.Camera.Value};
foreach(var thrust in r.Hardware.Thrusters)
{
var definition=thrust.BlockDefinition;definition.SubtypeName="MockThruster";
((Stub<IMyThrust>)RemotingServices.GetRealProxy(thrust)).Set("BlockDefinition",definition);
}
blocks.AddRange(r.Hardware.Thrusters);blocks.AddRange(r.Hardware.Gyros);blocks.AddRange(r.Hardware.Batteries);
return blocks;
}
static Stub<IMyCameraBlock> InstalledCamera(MinerStateTests.MinerRig r,long id)
{
return new Stub<IMyCameraBlock>().Set("CubeGrid",r.Program.Me.CubeGrid).Set("EntityId",id)
.Set("IsFunctional",true).Set("WorldMatrix",MatrixD.Identity).Set("Enabled",false).Set("EnableRaycast",false);
}
static void SupplyBlocks(MinerStateTests.MinerRig r,List<IMyTerminalBlock> blocks,List<IMyTerminalBlock> groupBlocks=null)
{
var system=new Stub<IMyGridTerminalSystem>().Method("GetBlocksOfType",a=>{
var list=(List<IMyTerminalBlock>)a[0];var filter=(Func<IMyTerminalBlock,bool>)a[1];
foreach(var block in blocks)if(filter==null || filter(block))list.Add(block);return null;
});
if(groupBlocks!=null)
{
var group=new Stub<IMyBlockGroup>().Method("GetBlocks",a=>{((List<IMyTerminalBlock>)a[0]).AddRange(groupBlocks);return null;});
system.Method("GetBlockGroupWithName",a=>{Assert.Equal("Managed",a[0]);return group.Value;});
}
TestRig.SetBase(r.Program,"GridTerminalSystem",system.Value);
}
[Fact]
public void MinerInitializationDiscoversAndEnablesLocalCamerasAndMeasuresTheHull()
{
var r=new MinerStateTests.MinerRig();var grid=(Stub<VRage.Game.ModAPI.Ingame.IMyCubeGrid>)RemotingServices.GetRealProxy(r.Program.Me.CubeGrid);
grid.Set("Min",new Vector3I(-10)).Set("Max",new Vector3I(10)).Method("GridIntegerToWorld",a=>new Vector3D((Vector3I)a[0])*2.5);
var added=InstalledCamera(r,2);var foreign=InstalledCamera(r,3).Set("CubeGrid",new Stub<VRage.Game.ModAPI.Ingame.IMyCubeGrid>().Value);
var blocks=TerminalBlocks(r);blocks.Add(added.Value);blocks.Add(foreign.Value);SupplyBlocks(r,blocks);
var miner=new P.MinerController(r.Program);var hardware=Field<P.ShipHardware>(miner,"ship");
Assert.Equal(2,hardware.Cameras.Count);Assert.True(added.Value.Enabled);Assert.True(added.Value.EnableRaycast);
Assert.False(foreign.Value.Enabled);Assert.True(hardware.BodyMax.X>25);
Assert.True(Field<bool>(miner,"healthy"));
}
[Theory]
[InlineData("init")]
[InlineData("rescan")]
public void ExplicitInitializationUpdatesAddedAndRemovedCameras(string command)
{
var r=new MinerStateTests.MinerRig();r.State(P.FlightState.Paused);
var added=InstalledCamera(r,2);var blocks=TerminalBlocks(r);blocks.Add(added.Value);SupplyBlocks(r,blocks);
Assert.Single(r.Hardware.Cameras);r.Miner.Command(command);
Assert.Equal(2,r.Hardware.Cameras.Count);Assert.True(added.Value.Enabled);Assert.True(added.Value.EnableRaycast);
blocks.RemoveAll(block=>object.ReferenceEquals(block,r.Camera.Value));r.Miner.Command(command);
Assert.Same(added.Value,Assert.Single(r.Hardware.Cameras));
}
[Fact]
public void InitializationRespectsTheConfiguredHardwareGroup()
{
var r=new MinerStateTests.MinerRig();r.Program.Config=new P.Settings("[Hardware]\nGroup=Managed","miner","test");
var selected=TerminalBlocks(r);var outside=InstalledCamera(r,2);var all=new List<IMyTerminalBlock>(selected) {outside.Value};
SupplyBlocks(r,all,selected);var miner=new P.MinerController(r.Program);
Assert.Same(r.Camera.Value,Assert.Single(Field<P.ShipHardware>(miner,"ship").Cameras));Assert.False(outside.Value.Enabled);
}
[Theory]
[InlineData(P.FlightState.Returning)]
[InlineData(P.FlightState.Holding)]
[InlineData(P.FlightState.Paused)]
[InlineData(P.FlightState.Manual)]
public void HealthyMinerNeverAutomaticallyRediscoversCameras(P.FlightState state)
{
var r=new MinerStateTests.MinerRig();r.State(state);int scans=0;
TestRig.SetBase(r.Program,"GridTerminalSystem",new Stub<IMyGridTerminalSystem>().Method("GetBlocksOfType",a=>{scans++;return null;}).Value);
foreach(double now in new[]{0d,5d,10d,30d}) {r.Program.Now=now;r.Miner.Tick(.1);}
Assert.Equal(0,scans);Assert.Same(r.Camera.Value,Assert.Single(r.Hardware.Cameras));
}
static ReturnNavigationTests.Rig ViewGapRig()
{
var r=new ReturnNavigationTests.Rig();r.Ship.Radius=16;r.Ship.BodyHalfSize=new Vector3D(5,5,10);
r.Ship.BodyMin=-r.Ship.BodyHalfSize;r.Ship.BodyMax=r.Ship.BodyHalfSize;r.Ship.Cameras.Clear();
r.Ship.Cameras.Add(Camera(r,"Front",1,()=>MatrixD.CreateWorld(Vector3D.Forward*10,Vector3D.Forward,Vector3D.Up)).Value);
r.Ship.Cameras.Add(Camera(r,"Rear",2,()=>MatrixD.CreateWorld(Vector3D.Backward*10,Vector3D.Backward,Vector3D.Up)).Value);
return r;
}
[Fact]
public void AShortWaypointDoesNotSqueezeTheCameraHorizonAgainstTheNose()
{
var r=ViewGapRig();int rays=0;
foreach(var camera in r.Ship.Cameras)((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(camera)).Method("Raycast",a=>{rays++;return default(MyDetectedEntityInfo);});
for(int n=0;n<30 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
Assert.True(r.HasTranslation);Assert.False(r.Flight.Blocked);Assert.Equal("",r.Flight.Problem);Assert.Equal(18,rays);
Assert.Equal(20.2,r.Flight.ScanClearance,8);
Assert.Contains(P.L.F(P.L.CameraCoverage,18,1),r.Flight.CameraDiagnostics);
}
[Fact]
public void AnIncompleteCombinedScanDoesNotCountInvisibleRegionsAsScanned()
{
var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);
for(int n=0;n<30;n++)r.Step(Vector3D.Right*100);
Assert.False(r.HasTranslation);Assert.Equal(0,r.Gyro.Value.Yaw);Assert.Equal(0,r.Flight.ScanClearance);
Assert.Equal(0,r.Rays);Assert.Contains(P.L.F(P.L.CameraCoverage,0,0),r.Flight.CameraDiagnostics);
}
[Fact]
public void CombinedScanningStillBrakesForObstaclesInsideTheCurrentLeg()
{
var r=ViewGapRig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);int hits=0;
var camera=(Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(r.Ship.Cameras[0]);
camera.Method("Raycast",a=>{
if(((Vector3D)a[0]).Z<-20) {hits++;return ReturnNavigationTests.Rig.Detection(42);}
return default(MyDetectedEntityInfo);
});
for(int n=0;n<20 && !r.Flight.Blocked;n++) {r.Step(Vector3D.Forward*10);Assert.False(r.HasTranslation);}
Assert.True(r.Flight.Blocked);Assert.Equal(1,hits);Assert.Equal(0,r.Flight.ScanClearance);
}
[Fact]
public void CombinedClearanceDoesNotSurviveARealObstacleOnTheNextSweep()
{
var r=ViewGapRig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);
for(int n=0;n<9;n++)r.Step(Vector3D.Forward*10);Assert.True(r.HasTranslation);
((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(r.Ship.Cameras[0])).Method("Raycast",a=>ReturnNavigationTests.Rig.Detection(42));
r.Program.Now+=.4;r.Step(Vector3D.Forward*10);
Assert.True(r.Flight.Blocked);Assert.False(r.HasTranslation);
}
[Fact]
public void CombinedClearanceExpiresWhenCamerasCannotRecharge()
{
var r=ViewGapRig();for(int n=0;n<9;n++)r.Step(Vector3D.Forward*10);Assert.True(r.HasTranslation);
foreach(var camera in r.Ship.Cameras)((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(camera)).Set("AvailableScanRange",0d);
for(int n=0;n<40;n++)r.Step(Vector3D.Forward*10);
Assert.False(r.HasTranslation);Assert.True(Field<bool>(r.Flight,"scanWaitingForCharge"));
}
[Fact]
public void AnObservedObstaclePastAShortWaypointDoesNotBlockTheCurrentLeg()
{
var r=ViewGapRig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);int hits=0;
((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(r.Ship.Cameras[0])).Method("Raycast",a=>{
if(((Vector3D)a[0]).Z>-25)return default(MyDetectedEntityInfo);
hits++;return new MyDetectedEntityInfo(42,"beyond waypoint",MyDetectedEntityType.LargeGrid,Vector3D.Forward*25,
MatrixD.Identity,Vector3.Zero,VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,new BoundingBoxD(new Vector3D(-1),new Vector3D(1)),1);
});
for(int n=0;n<30 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
Assert.True(r.HasTranslation);Assert.Equal(9,hits);Assert.False(r.Flight.Blocked);Assert.Equal(20.2,r.Flight.ScanClearance,8);
}
[Fact]
public void OneSelfObstructedCameraDoesNotInterruptTwoOtherCamerasOrExistingClearance()
{
var r=new ReturnNavigationTests.Rig();bool blocked=false;int selfHits=0,clearHits=0;
r.Hit=()=>{if(blocked) {selfHits++;return ReturnNavigationTests.Rig.Detection(r.Program.Me.CubeGrid.EntityId);}return default(MyDetectedEntityInfo);};
for(int id=2;id<=3;id++)r.Ship.Cameras.Add(r.MakeCamera(id,()=>10000,()=>{clearHits++;return default(MyDetectedEntityInfo);}).Value);
for(int n=0;n<9;n++)r.Step(Vector3D.Forward*100);Assert.True(r.HasTranslation);
blocked=true;
for(int n=0;n<90;n++)
{
r.Step(Vector3D.Forward*100);
Assert.True(r.HasTranslation);Assert.True(r.Flight.ScanClearance>0);Assert.False(r.Flight.Blocked);
}
Assert.True(selfHits>=18);Assert.True(clearHits>=18);
}
[Fact]
public void ACameraBlockedForOnePointCanStillCoverOtherPoints()
{
var r=new ReturnNavigationTests.Rig();int recovered=0,alternate=0;
r.Hit=()=>ReturnNavigationTests.Rig.Detection(r.Program.Me.CubeGrid.EntityId);
r.Ship.Cameras.Add(r.MakeCamera(2,()=>10000,()=>{alternate++;return default(MyDetectedEntityInfo);}).Value);
r.Step(Vector3D.Forward*100);
Assert.Equal(1,alternate);r.Hit=()=>{recovered++;return default(MyDetectedEntityInfo);};
for(int n=0;n<15 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*100);
Assert.Equal(17,recovered);Assert.True(r.Flight.ScanClearance>0);Assert.True(r.HasTranslation);
}
[Fact]
public void ThreeFrontCamerasShareRegionsEvenWhenTheCenterCameraIsObstructed()
{
var r=ViewGapRig();r.Ship.Cameras.Clear();int left=0,right=0,blocked=0;
var center=Camera(r,"Center",1,()=>MatrixD.CreateWorld(new Vector3D(0,0,-10),Vector3D.Forward,Vector3D.Up));
center.Method("Raycast",a=>{blocked++;return ReturnNavigationTests.Rig.Detection(r.Program.Me.CubeGrid.EntityId);});
var leftCamera=Camera(r,"Left",2,()=>MatrixD.CreateWorld(new Vector3D(-4,0,-10),Vector3D.Forward,Vector3D.Up));
leftCamera.Method("Raycast",a=>{left++;return default(MyDetectedEntityInfo);});
var rightCamera=Camera(r,"Right",3,()=>MatrixD.CreateWorld(new Vector3D(4,0,-10),Vector3D.Forward,Vector3D.Up));
rightCamera.Method("Raycast",a=>{right++;return default(MyDetectedEntityInfo);});
r.Ship.Cameras.Add(center.Value);r.Ship.Cameras.Add(leftCamera.Value);r.Ship.Cameras.Add(rightCamera.Value);
for(int n=0;n<40 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
Assert.True(r.HasTranslation);Assert.True(blocked>0);Assert.True(left>0);Assert.True(right>0);Assert.Equal(18,left+right);
Assert.Contains(P.L.F(P.L.CameraCoverage,18,2),r.Flight.CameraDiagnostics);
}
}
}