Improve radar simulation

This commit is contained in:
Brandon Danyluk 2021-04-15 22:29:02 -06:00
parent d0775283e3
commit 8318b60982
9 changed files with 410 additions and 17 deletions

View File

@ -15,8 +15,8 @@ class CfgAmmo {
canVanillaLock = 1; // Can this default vanilla lock? Only applicable to non-cadet mode
// Guidance type for munitions
defaultSeekerType = "ARH";
seekerTypes[] = { "ARH" };
defaultSeekerType = "DopplerRadar";
seekerTypes[] = { "DopplerRadar" };
lockableTypes[] = {"Air"};
defaultSeekerLockMode = "LOAL";
@ -55,8 +55,8 @@ class CfgAmmo {
canVanillaLock = 1; // Can this default vanilla lock? Only applicable to non-cadet mode
// Guidance type for munitions
defaultSeekerType = "ARH";
seekerTypes[] = { "ARH" };
defaultSeekerType = "DopplerRadar";
seekerTypes[] = { "DopplerRadar" };
lockableTypes[] = {"Air"};
defaultSeekerLockMode = "LOAL";

View File

@ -76,8 +76,8 @@ class CfgAmmo {
canVanillaLock = 1;
enabled = 1; // Missile Guidance must be explicitly enabled
seekLastTargetPos = 0;
defaultSeekerType = "ARH";
seekerTypes[] = { "ARH" };
defaultSeekerType = "MillimeterWaveRadar";
seekerTypes[] = { "MillimeterWaveRadar" };
defaultSeekerLockMode = "LOBL";
seekerLockModes[] = { "LOBL" };

View File

@ -80,13 +80,21 @@ class GVAR(SeekerTypes) {
functionName = QFUNC(seekerType_SACLOS);
onFired = QFUNC(SACLOS_onFired);
};
class ARH {
class MillimeterWaveRadar {
name = "";
visualName = "";
description = "";
functionName = QFUNC(seekerType_ARH);
onFired = QFUNC(ahr_onFired);
functionName = QFUNC(seekerType_MWR);
onFired = QFUNC(mwr_onFired);
};
class DopplerRadar {
name = "";
visualName = "";
description = "";
functionName = QFUNC(seekerType_Doppler);
onFired = QFUNC(doppler_onFired);
};
class IR {
name = "";

View File

@ -16,6 +16,8 @@ PREP(doSeekerSearch);
PREP(doHandoff);
PREP(handleHandoff);
PREP(shouldFilterRadarHit);
// Attack Profiles
PREP(attackProfile_AIR);
PREP(attackProfile_DIR);
@ -38,7 +40,8 @@ PREP(navigationType_lineOfSight);
PREP(seekerType_SALH);
PREP(seekerType_Optic);
PREP(seekerType_SACLOS);
PREP(seekerType_ARH);
PREP(seekerType_Doppler);
PREP(seekerType_MWR);
PREP(seekerType_IR);
// Attack Profiles OnFired
@ -46,7 +49,8 @@ PREP(wire_onFired);
// Seeker OnFired
PREP(SACLOS_onFired);
PREP(ahr_onFired);
PREP(doppler_onFired);
PREP(mwr_onFired);
PREP(IR_onFired);
// Navigation OnFired

View File

@ -0,0 +1,76 @@
#include "script_component.hpp"
/*
* Author: Brandon (TCVM)
* Sets up doppler radar state arrays (called from missileGuidance's onFired).
*
* Arguments:
* Guidance Arg Array <ARRAY>
*
* Return Value:
* None
*
* Example:
* [] call ace_missileguidance_fnc_mwr_onFired
*
* Public: No
*/
params ["_firedEH", "_launchParams", "", "", "_stateParams"];
_firedEH params ["_shooter","","","","","","_projectile"];
_stateParams params ["", "_seekerStateParams"];
_launchParams params ["","_targetLaunchParams"];
_targetLaunchParams params ["_target"];
_target = missileTarget _projectile;
if (isNull _target && isVehicleRadarOn vehicle _shooter) then {
_target = cursorTarget;
};
if !(_target isKindOf "AllVehicles") then {
_target = nil;
};
_launchParams set [0, _target];
_projectile setMissileTarget objNull; // to emulate a no launch warning
private _projectileConfig = configOf _projectile;
private _config = _projectileConfig >> "ace_missileguidance";
private _isActive = false;
private _activeRadarDistance = [_config >> "activeRadarEngageDistance", "NUMBER", 500] call CBA_fnc_getConfigEntry;
private _projectileThrust = [_projectileConfig >> "thrust", "NUMBER", 0] call CBA_fnc_getConfigEntry;
private _projectileThrustTime = [_projectileConfig >> "thrustTime", "NUMBER", 0] call CBA_fnc_getConfigEntry;
private _lockTypes = [_config >> "lockableTypes", "ARRAY", ["Air", "LandVehicle", "Ship"]] call CBA_fnc_getConfigEntry;
private _velocityAtImpact = _projectileThrust * _projectileThrustTime;
private _timeToActive = 0;
if (!isNil "_target" && _velocityAtImpact > 0) then {
private _distanceUntilActive = (((getPosASL _shooter) vectorDistance (getPosASL _target)) - _activeRadarDistance);
_timeToActive = 0 max (_distanceUntilActive / _velocityAtImpact);
};
if (isNil "_target") then {
_timeToActive = 0;
_isActive = true;
_target = objNull;
};
private _shooterHasActiveRadar = {
if ("ActiveRadarSensorComponent" in _x) exitWith { true };
false
} forEach listVehicleSensors vehicle _shooter;
if !(isVehicleRadarOn vehicle _shooter) then {
_isActive = true;
};
_seekerStateParams set [0, _isActive];
_seekerStateParams set [1, _activeRadarDistance];
_seekerStateParams set [2, CBA_missionTime + _timeToActive];
_seekerStateParams set [3, getPosASL _target];
_seekerStateParams set [4, CBA_missionTime];
_seekerStateParams set [5, _shooterHasActiveRadar];
_seekerStateParams set [6, false];
_seekerStateParams set [7, [0, 0, 0]];
_seekerStateParams set [8, CBA_missionTime];
_seekerStateParams set [9, isNull _target];
_seekerStateParams set [10, _lockTypes];

View File

@ -1,7 +1,7 @@
#include "script_component.hpp"
/*
* Author: Brandon (TCVM)
* Sets up Active Radar state arrays (called from missileGuidance's onFired).
* Sets up MWR state arrays (called from missileGuidance's onFired).
*
* Arguments:
* Guidance Arg Array <ARRAY>
@ -10,7 +10,7 @@
* None
*
* Example:
* [] call ace_missileguidance_fnc_ahr_onFired
* [] call ace_missileguidance_fnc_mwr_onFired
*
* Public: No
*/

View File

@ -0,0 +1,160 @@
#include "script_component.hpp"
/*
* Author: Brandon (TCVM)
* doppler radar seeker
*
* Arguments:
* 1: Guidance Arg Array <ARRAY>
* 2: Seeker State <ARRAY>
*
* Return Value:
* Seeker Pos <ARRAY>
*
* Example:
* [] call call ace_missileguidance_fnc_seekerType_MWR;
*
* Public: No
*/
params ["", "_args", "_seekerStateParams", "", "_timestep"];
_args params ["_firedEH", "_launchParams", "", "_seekerParams", "_stateParams", "_targetData"];
_firedEH params ["_shooter","","","","","","_projectile"];
_launchParams params ["_target","","","",""];
_seekerParams params ["_seekerAngle", "", "_seekerMaxRange"];
_seekerStateParams params ["_isActive", "_activeRadarEngageDistance", "_timeWhenActive", "_expectedTargetPos", "_lastTargetPollTime", "_shooterHasRadar", "_wasActive", "_lastKnownVelocity", "_lastTimeSeen", "_doesntHaveTarget", "_lockTypes"];
private _minimumFilterSpeed = 25;
private _minimumFilterTime = 1e-4;
private _maxTerrainCheck = 16000;
if (_isActive || { CBA_missionTime >= _timeWhenActive }) then {
if !(_isActive) then {
_seekerStateParams set [0, true];
};
if !(_wasActive) then {
_seekerStateParams set [6, true];
TRACE_1("Missile Pitbull",_seekerStateParams);
};
// Internal radar homing
// For performance reasons only poll for target every so often instead of each frame
if ((_lastTargetPollTime + ACTIVE_RADAR_POLL_FREQUENCY) - CBA_missionTime < 0) then {
private _searchPos = _expectedTargetPos;
if (_searchPos isEqualTo [0, 0, 0] || { _doesntHaveTarget }) then {
_seekerStateParams set [9, true];
// no target pos - shot without lock. Have the missile's radar search infront of it on the ground
_searchPos = (getPosASL _projectile) vectorAdd (_projectile vectorModelToWorld [0, _seekerMaxRange, -((getPos _projectile)#2)]);
};
_target = objNull;
_lastTargetPollTime = CBA_missionTime;
_seekerStateParams set [4, _lastTargetPollTime];
private _distanceToExpectedTarget = _seekerMaxRange min ((getPosASL _projectile) vectorDistance _searchPos);
// Simulate how much the seeker can see at the ground
private _projDir = vectorDir _projectile;
private _projYaw = getDir _projectile;
private _rotatedYaw = (+(_projDir select 0) * sin _projYaw) + (+(_projDir select 1) * cos _projYaw);
if (_rotatedYaw isEqualTo 0) then { _rotatedYaw = 0.001 };
private _projPitch = atan ((_projDir select 2) / _rotatedYaw);
private _a1 = abs _projPitch;
private _a2 = 180 - ((_seekerAngle / 2) + _a1);
private _seekerBaseRadiusAtGround = ACTIVE_RADAR_MINIMUM_SCAN_AREA max (_distanceToExpectedTarget / sin(_a2) * sin(_seekerAngle / 2));
private _lastKnownSpeed = if (_lastKnownVelocity isEqualTo [0, 0, 0]) then {
0
} else {
vectorMagnitude _lastKnownVelocity;
};
private _seekerBaseRadiusAdjusted = linearConversion [0, _seekerBaseRadiusAtGround, (CBA_missionTime - _lastTimeSeen) * vectorMagnitude _lastKnownVelocity, ACTIVE_RADAR_MINIMUM_SCAN_AREA, _seekerBaseRadiusAtGround, false];
if (_doesntHaveTarget) then {
_seekerBaseRadiusAdjusted = _seekerBaseRadiusAtGround;
};
// Look in front of seeker for any targets
private _nearestObjects = nearestObjects [ASLtoAGL _searchPos, _lockTypes, _seekerBaseRadiusAdjusted, false];
_nearestObjects = _nearestObjects apply {
// I check both Line of Sight versions to make sure that a single bush doesnt make the target lock dissapear but at the same time ensure that this can see through smoke. Should work 80% of the time
if ([_projectile, getPosASL _x, _seekerAngle] call FUNC(checkSeekerAngle) && { ([_projectile, _x, true] call FUNC(checkLOS)) || { ([_projectile, _x, false] call FUNC(checkLOS)) } }) then {
if !([_x, _projectile, _minimumFilterSpeed, _minimumFilterTime, _maxTerrainCheck, _seekerAngle] call FUNC(shouldFilterRadarHit)) exitWith {
_x
};
objNull;
} else {
objNull
};
};
_nearestObjects = _nearestObjects select { !isNull _x };
// Select closest object to the expected position to be the current radar target
if ((count _nearestObjects) <= 0) exitWith {
_projectile setMissileTarget objNull;
_seekerStateParams set [3, _searchPos];
_searchPos
};
private _closestDistance = _seekerBaseRadiusAtGround;
{
if ((_x distance2d _searchPos) < _closestDistance) then {
_closestDistance = _x distance2d _searchPos;
_target = _x;
};
} forEach _nearestObjects;
_expectedTargetPos = _searchPos;
};
} else {
#ifdef DRAW_GUIDANCE_INFO
_seekerTypeName = "DOPPLER - EXT";
#endif
// External radar homing
// if the target is in the remote targets for the side, whoever the donor is will "datalink" the target for the hellfire.
private _remoteTargets = listRemoteTargets side _shooter;
if ((_remoteTargets findIf { (_target in _x) && (_x#1 > 0) }) < 0) then {
// I check both Line of Sight versions to make sure that a single bush doesnt make the target lock dissapear but at the same time ensure that this can see through smoke. Should work 80% of the time
if (!_shooterHasRadar || { !isVehicleRadarOn vehicle _shooter } || { !alive vehicle _shooter } || { !([vehicle _shooter, _target, true] call FUNC(checkLOS)) && { !([vehicle _shooter, _target, false] call FUNC(checkLOS)) } }) then {
_seekerStateParams set [0, true];
_target = objNull; // set up state for active guidance
};
};
};
#ifdef DRAW_GUIDANCE_INFO
drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [1,0,0,1], ASLtoAGL _expectedTargetPos, 0.75, 0.75, 0, "expected target pos", 1, 0.025, "TahomaB"];
#endif
if !(isNull _target) then {
// we check if the target is moving away from us or perpendicular to see if we maintain lock
private _centerOfObject = getCenterOfMass _target;
private _targetAdjustedPos = _target modelToWorldVisualWorld _centerOfObject;
_expectedTargetPos = _targetAdjustedPos;
private _filterTarget = [_target, _projectile, _minimumFilterSpeed, _minimumFilterTime, _maxTerrainCheck, _seekerAngle] call FUNC(shouldFilterRadarHit);
if (_filterTarget) then {
// filter out target
_target = objNull;
_seekerStateParams set [9, true];
} else {
private _lineOfSight = vectorDirVisual _projectile;
private _realTargetVelocity = velocity _target;
private _projectedTargetVelocity = _lineOfSight vectorMultiply (_realTargetVelocity vectorDotProduct _lineOfSight);
private _relativeTargetVelocity = _realTargetVelocity vectorDiff _projectedTargetVelocity;
_seekerStateParams set [7, _relativeTargetVelocity];
_seekerStateParams set [8, CBA_missionTime];
_seekerStateParams set [9, false];
_targetData set [2, _projectile distance _target];
_targetData set [3, _relativeTargetVelocity];
if (_timestep != 0) then {
private _acceleration = (_relativeTargetVelocity vectorDiff _lastKnownVelocity) vectorMultiply (1 / _timestep);
_targetData set [4, _acceleration];
};
};
_projectile setMissileTarget _target;
};
_targetData set [0, (getPosASLVisual _projectile) vectorFromTo _expectedTargetPos];
_seekerStateParams set [3, _expectedTargetPos];
_launchParams set [0, _target];
_expectedTargetPos

View File

@ -1,7 +1,7 @@
#include "script_component.hpp"
/*
* Author: Brandon (TCVM)
* Active Radar Homing seeker
* millimeter wave radar seeker
*
* Arguments:
* 1: Guidance Arg Array <ARRAY>
@ -11,11 +11,11 @@
* Seeker Pos <ARRAY>
*
* Example:
* [] call call ace_missileguidance_fnc_seekerType_ARH;
* [] call call ace_missileguidance_fnc_seekerType_MWR;
*
* Public: No
*/
params ["", "_args", "_seekerStateParams"];
params ["", "_args", "_seekerStateParams", "", "_timestep"];
_args params ["_firedEH", "_launchParams", "", "_seekerParams", "_stateParams", "_targetData"];
_firedEH params ["_shooter","","","","","","_projectile"];
_launchParams params ["_target","","","",""];
@ -119,7 +119,11 @@ if !(isNull _target) then {
_targetData set [2, _projectile distance _target];
_targetData set [3, velocity _target];
_targetData set [4, [0, 0, 0]]; // todo: acceleration
if (_timestep != 0) then {
private _acceleration = ((velocity _target) vectorDiff _lastKnownVelocity) vectorMultiply (1 / _timestep);
_targetData set [4, _acceleration];
};
};
_targetData set [0, (getPosASLVisual _projectile) vectorFromTo _expectedTargetPos];

View File

@ -0,0 +1,141 @@
#include "script_component.hpp"
/*
* Author: Brandon (TCVM)
* Whether or not we should filter out this target due to masking. Useful for only doppler radar types
*
* Arguments:
* 0: Target <OBJECT>
* 1: Projectile <OBJECT>
* 2: Minimum speed for doppler return to filter out <NUMBER>
* 3: Minimum time for doppler return to filter out <NUMBER>
* 4: Maximum distance to check for terrain <NUMBER>
* 5: Radar seeker angle <NUMBER>
*
* Return Value:
* Whether or not to filter out this target
*
* Example:
* [] call call ace_missileguidance_fnc_shouldFilterRadarHit;
*
* Public: Yes
*/
// as defined by lineIntersectsSurfaces
#define MAX_LINE_DISTANCE 5000
params ["_target", "_projectile", "_minimumSpeed", "_minimumTime", "_maximumTerrainCheck", "_seekerAngle"];
TRACE_5("should filter target",_projectile,_target,_minimumSpeed,_minimumTime,_maximumTerrainCheck);
// Theory developed from doppler radar visuals at https://www.mudspike.com/dcs-f-15c-combat-guide-for-beginners/
// helicopter blades will always produce a doppler shift due to their nature. Don't filter
if (_target isKindOf "Helicopter" && isEngineOn _target) exitWith {
TRACE_2("dont filter helicopters",_target isKindOf "Helicopter",isEngineOn _target);
false
};
private _lineOfSight = (getPosASLVisual _projectile) vectorFromTo (getPosASLVisual _target);
private _realTargetVelocity = velocity _target;
private _closingVelocity = _lineOfSight vectorMultiply (_realTargetVelocity vectorDotProduct _lineOfSight);
private _closingSpeed = vectorMagnitude _closingVelocity;
// if relative target velocity is greather than threshold, we can easily see it. Don't filter
if (_closingSpeed > _minimumSpeed) exitWith {
TRACE_2("dont filter fast objects approaching",_closingSpeed,_minimumSpeed);
false
};
private _projectilePos = getPosASLVisual _projectile;
private _stepDistance = _lineOfSight vectorMultiply (MAX_LINE_DISTANCE min _maximumTerrainCheck);
private _checkPos = _projectilePos;
private _maskedByGround = false;
// Check for all surfaces until we reach our max range
for "_i" from 0 to _maximumTerrainCheck step MAX_LINE_DISTANCE do {
// determine if target is masked by ground
private _endPos = _checkPos vectorAdd _stepDistance;
private _groundHit = lineIntersectsSurfaces [_checkPos, _endPos, _projectile, _target];
_maskedByGround = _groundHit isNotEqualTo [];
_checkPos = _endPos;
if (_maskedByGround || (_checkPos select 2) <= 0) then {
// for ease assume that we can't check underwater
if ((_checkPos select 2) < 0) then {
_checkPos set [2, 0];
};
break
};
};
// looking at sky, target is clear as day. Check for chaff before filtering
if !(_maskedByGround) exitWith {
TRACE_1("dont filter stuff in the sky",_maskedByGround);
// If there is chaff nearby, check if they will confuse missile
private _nearby = _target nearObjects 50;
_nearby = _nearby select {
// 8 = radar blocking
private _blocking = configOf _x >> "weaponLockSystem";
private _isChaff = false;
if (isNumber _blocking) then {
_isChaff = (8 == getNumber _blocking);
};
if (isText _blocking) then {
_isChaff = ("8" in getText _blocking);
};
private _withinView = [_projectile, getPosASLVisual _x, _seekerAngle] call FUNC(checkSeekerAngle);
private _canSee = [_projectile, _x, false] call FUNC(checkLos);
{ (_withinView && _canSee && _isChaff) }
};
private _foundDecoy = false;
private _projectileVelocity = velocity _projectile;
{
private _considering = false;
private _distanceToFlare = _target distanceSqr _x;
if !(_foundDecoy) then {
private _chaffRelativeVelocity = (velocity _x) vectorDiff _projectileVelocity;
private _angleBetweenVelocities = acos (_closingVelocity vectorCos _chaffRelativeVelocity);
if (_angleBetweenVelocities <= 2) then {
_considering = true;
if (0.95 <= random 1) then {
_foundDecoy = true;
};
};
};
#ifdef DRAW_GUIDANCE_INFO
private _chaffPos = ASLToAGL getPosASLVisual _x;
private _colour = [1, 0, 0, 1];
if (_considering) then {
_colour = [0, 1, 0, 1];
};
if (_foundDecoy) then {
_colour = [0, 0, 1, 1];
};
drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", _colour, _chaffPos, 0.75, 0.75, 0, "F", 1, 0.025, "TahomaB"];
#endif
} forEach _nearby;
_foundDecoy
};
private _distanceToTerrain = _checkPos vectorDistance _projectilePos;
private _checkTime = _distanceToTerrain / 3e8;
// Time to ground is large enough to know if we are looking at a target, don't filter
if (_checkTime > _minimumTime) exitWith {
TRACE_2("dont filter targets that we can see in ground clutter",_checkTime,_minimumTime);
false
};
// filter out the target since we can't reasonably see it
true