Jump to content

Recommended Posts

Timid-Icarus-1m.png

 

FILE

CFS 1.01 (Dropbox, APL-SA license)

 

 

INTRODUCTION

CFS script attempts to guide pointed helicopter along his waypoints using very low altitude similar to contour flight. CFS's control is paused as helicopter approaches current waypoint and resumed, when new waypoint become current. 

Testing run footage

 

 

Parameters description:

 

Helicopter - a chopper to be controlled.

Desired speed - speed in m/s, that heli will try to maintain most of the time. Default: max config speed. The faster, the more risky flight.

Desired altitude - AGL ceiling in meters: controlled heli will strive to maintain that height above the ground as much, as possible. Default: 10. Values below 10 not recommended.

Overfly buffer - additional buffer in meters added to calculated minimal height necessary to fly over an obstacle (map object etc.). Default: 2. Values below 2 not recommended. 

Obstacle detection mode - setting for obstacles detection method. 0 - less CPU hungry, pays attention only to the map objects, ignores editor-placed objects and very few exceptions amongst map objects too (example: Tanoa's sea bridge roadway). 1 - detects non-map objects as well, may cause a bit more bumpy ride, than necessary and recognize as an obstacle also objects, that should be ignored, can be more CPU-hungry. Default: 0 (recommended unless insuffice in certain scenarios).

Obstacle detection range - a multiplier of obstacles detection radius. Default: 1, which translates to the bigger of the two: (heli full length + 10) OR 20 meters. Too big radius will eat FPS fast, too low will tend to ignore big objects, that still can be collided with.

Emergency pull up - if this is enabled and code would detect, that heli gets dangerously close to the ground, emergency "pull up!" vertical velocity will be added, which often (not always) may help to avoid collision. In such situation it will override sharpness setting. Default: true.

Sharpness factor - the lower value, the more smoothed flight, but also more sluggish reactions which increase collision risk. Default: 0.2. Values 0.1-0.2 are risky. Values below 0.1 not recommended. Maximum: 1 (safest, but least elegant). Values above 1 or below 0 may cause weird behavior.  

Debug mode - added 3D visualisation markers and some numerical data on the screen. Default: false. 

 

KNOWN ISSUES & LIMITATIONS

CFS was tested with few helicopters (Ghost Hawk, Hummingbird and Huron) and settings along various, demanding paths, but further tests with various settings/on different terrains may still reveal exceptions, where collisions may occur.

Although I did, what I could to make it looking as natural, as possible, do not expect fully realistic flight model here. It's simplified, scripted makeshift. 

CFS will fail, if given map object has its shape bigger, than bounding box. One example found: sea bridges elements (pillars) on Tanoa - manual correction for them included.  

CFS will not try to pass obstacles sideways nor will try to guide the helicopter below any obstacle even, if possible (high bridges etc.). It will always try to overfly the obstacle instead.

CFS guides the helicopter towards current waypoint along the straight line, there's no path preplanning to use terrain, avoid hilltops etc.

The code is run per frame to make it fluid and reliable, so it may cause some FPS drops especially over areas with many objects, like forests etc. 

 

CODE

 

Initialization:

_handle = [heli1] spawn RYD_TI_TimidIcarus;//simple
_handle = [heli1,83.3,10,2,1,1,false,0.2,true] spawn RYD_TI_TimidIcarus;//advanced
//[helicopter,desired speed (m/s), desired altitude AGL (m), obstacle overfly buffer (m), obstacle detect mode (0/1), obstacle detect range (m), emergency pull up, smoothness factor, debug mode]

 

Ending:

removeMissionEventHandler ["EachFrame", RYD_TI_EFEH];

 

Source:

Spoiler

RYD_TI_TimidIcarus = 
	{
	private _heli = param [0,objNull,[objNull]];//helicopter to be controlled. Default : objNull (code exits)
	if (isNull _heli) exitWith {};
	
	private _desiredSpeed = param [1,((getNumber (configFile >> "CfgVehicles" >> (typeOf _heli) >> "maxSpeed"))/3.6),[0]];//speed in m/s, that heli will try to maintain most of the time. Default: max config speed. The faster, the more risky flight.
	private _desiredAltitude = param [2,10,[0]];//AGL ceiling in meters: controlled heli will strive to maintain that height abouve the ground as much, as possible. Default: 10. Values below 10 not recommended. 
	private _overflyBuffer = param [3,2,[0]];//additional buffer in meters added to calculated minimal height necessary to fly over an obstacle (map object etc.). Default: 2. Values below 2 not recommended. 
	private _obstacleDetectMode = param [4,0,[0]];//setting for obstacles detection method. 0 - less CPU hungry, pays attention only to the map objects, ignores editor-placed objects and very few exceptions amongst map objects. 1 - detects non-map objects as well, may cause a bit more bumpy ride, than necessary and recognize as an obstacle also objects, that should be ignored, can be more CPU-hungry. Default: 0 (recommended unless insuffice in certain scenarios)
	private _obstacleDetectRange = param [5,1,[0]];//a multiplier of obstacles detection radius. Default: 1, which translates to the bigger of the two: (heli full length + 10) OR 20 meters. Too big will eat FPS fast, too low will tend to ignore big objects, that still can be collided with.
	private _safetyPush = param [6,true,[true]];//if this is enabled and code would detect, that heli gets dangerously close to the ground, emergency "pull up!" vertical velocity will be added, which often may help to avoid collision. In such situation it will override sharpness setting. Default: true.
	private _sharpness = param [7,0.2,[0]];//sharpness factor. The lower value, the more smoothed flight, but also more sluggish reactions which increase collision risk. Default: 0.2. Values 0.1-0.2 are risky. Values below 0.1 not recommended. Maximum: 1. Values above 1 may cause crazy behavior.  
	private _debug = param [8,false,[true]];//Debug mode adding 3D visualisation markers and some numerical data on the screen. Default: false. 
	
	RYD_TI_Params = [_heli,_desiredSpeed,_desiredAltitude,_overflyBuffer,_obstacleDetectMode,_obstacleDetectRange,_safetyPush,_sharpness,_debug];
	
	RYD_TI_ContourFlight = 
		{
		if (isGamePaused) exitWith {};
		params ["_heli","_desiredSpeed","_desiredAltitude","_overflyBuffer","_obstacleDetectMode","_obstacleDetectRange","_safetyPush","_sharpness","_debug"];
		if (isNull _heli) exitWith {};

		RYD_TI_Control = false;
		if (_debug) then
			{			
			if (isNil "RYD_TI_a1") then
				{
				RYD_TI_a1 = createvehicle ["Sign_Arrow_Large_F",(_heli modelToWorld [0,0,10]),[],0,"CAN_COLLIDE"];
				RYD_TI_a2 = createvehicle ["Sign_Arrow_Large_Blue_F",(_heli modelToWorld [0,0,10]),[],0,"CAN_COLLIDE"];
				RYD_TI_a3 = createvehicle ["Sign_Arrow_Large_Green_F",(_heli modelToWorld [0,0,10]),[],0,"CAN_COLLIDE"];
				};			
			};
		
		_gp = group _heli;
		_bbH = boundingBoxReal _heli;
		_size = ((_bbH select 1) select 0) - ((_bbH select 0) select 0);
		_sizeL = ((_bbH select 1) select 1) - ((_bbH select 0) select 1);

		_addH = abs ((_bbH select 0) select 2);
		_overflyBuffer = _overflyBuffer + _addH;
		_finish = 0;
		_vel = _desiredSpeed;
		_desiredAlt = _desiredAltitude;

		if ((isNull _heli) or {not (alive _heli) or {(isNull _gp)}}) exitWith {true};

		_cP = currentPilot _heli;
		
		if (not (isTouchingGround _heli) and {(canMove _heli) and {((fuel _heli) > 0) and {not (isNull _cP) and {(alive _cP) and {not (isPlayer _cP) and {((count (waypoints _gp)) > 0)}}}}}}) then
			{
			_cw = currentWaypoint _gp;
			_cw = [_gp,_cw];
			_wPos = waypointPosition _cw;
			if (_wPos isEqualTo [0,0,0]) exitWith {};
			
		
			_hPos = getPosASL _heli;
			
			_cAlt = _hPos select 2;	
			_cVel = velocity _heli;
			_dst = _hPos distance2D _wPos;
			_cVelH = _cVel distance2D [0,0,0];
			
			_rAlt = (_heli modelToWorld [0,((_bbH select 1) select 1),((_bbH select 0) select 2)]) select 2;
			_rAlt2 = (_heli modelToWorldWorld [0,((_bbH select 1) select 1),((_bbH select 0) select 2)]) select 2;
			
			_rAltFront = _rAlt min _rAlt2;
			
			_rAlt3 = (_heli modelToWorld [0,((_bbH select 0) select 1),((_bbH select 0) select 2)]) select 2;
			_rAlt4 = (_heli modelToWorldWorld [0,((_bbH select 0) select 1),((_bbH select 0) select 2)]) select 2;
			
			_rAltRear = _rAlt3 min _rAlt4;
			
			_cAltATL = _rAltFront min _rAltRear;

			if ((_dst > 100) and {(_cAltATL > 5) or {(_cVelH > 10)}}) then
				{				
				_dirH = getDir _heli;
				_dir = _heli getDir _wPos;
				_diff0 = _dirH - _dir;
				_diff0 = (sin _diff0) atan2 (cos _diff0);
				_diff = abs _diff0;
				
				if ((_diff < 1) or {(_cVelH > 5) and {(_diff < 10)}}) then
					{
					RYD_TI_Control = true;
					if not (RYD_TI_wasControl) then
						{
						_heli setVariable ["RYD_TI_lastWP",_hPos]
						};
											
					_myLastWP = _heli getVariable ["RYD_TI_lastWP",[-1000,-1000,-1000]];
					_finish = (linearConversion [100,200,_dst,1,0,true]) max (linearConversion [0,100,(_heli distance2D _myLastWP),1,0,true]);
					
					_cVelN = _cVel distance [0,0,0];
					
					_range = _size + _cVelN + 10;
					_rad = ((_size + 10) max 20) * _obstacleDetectRange;
					_mplOH = 1;
					
					_passingO = _heli getVariable ["RYD_TI_PassingO",objNull];
					if not (isNull _passingO) then
						{
						_dirPO = _heli getDir _passingO;
						_diffPO = _dirH - _dirPO;
						_diffP0 = abs ((sin _diffPO) atan2 (cos _diffPO));
						
						if (_diffPO > 90) then
							{
							_bhd = _heli distance2D _passingO;
							if (_bhd > ((_sizeL/2) + 10)) then
								{
								_passingO = objNull;
								_heli setVariable ["RYD_TI_PassingO",objNull]
								}
							else
								{
								_mplOH = (1 - ((_bhd - (_sizeL/2))/((_sizeL/2) + 10))) min 1
								}
							}
						};
					
					_topPC = _passingO modelToWorld [0,0,(((boundingBoxReal _passingO) select 1) select 2)];				
					_obstacleHASL = (ATLtoASL _topPC) select 2;
					if ("bridgesea" in (toLower (str _passingO))) then
						{
						_obstacleHASL = _obstacleHASL + 5
						};	
						
					_obstacleHASL = _obstacleHASL * _mplOH;
					_corr = 0;
					_moDst = _range + _rad;
					_obstacle = objNull;
					_altASL = ((getTerrainHeightASL _hPos) max 0) + _desiredAltitude;

					for "_i" from (_size/2) to (_range + 50) step (_size/2) do
						{
						_pingPos = _heli getPos [_i,_dir];
						
						_objects = if (_obstacleDetectMode == 0) then
							{
							(nearestTerrainObjects [_pingPos, [], _rad, false, true])
							}
						else
							{
							_rem = [_heli];
							_rem appEnd (crew _heli);
							_birds = _pingPos nearObjects ["Bird",(_rad * 1.5)];
							_rem appEnd _birds;
							
							if (_debug) then
								{
								_rem appEnd [RYD_TI_a1,RYD_TI_a2,RYD_TI_a3]
								};
							
							((nearestObjects [_pingPos,[],_rad,true]) - _rem)
							};
						
							{
							_bbr = boundingBoxReal _x;
							_topC = _x modelToWorld [0,0,((_bbr select 1) select 2)];
							_top = _topC select 2;
							_topASL = (ATLtoASL _topC) select 2;
							
							if ("bridgesea" in (toLower (str _x))) then
								{
								_topASL = _topASL + 5
								};
							
							_aDst = (_heli distance2D _x) max 1;
													
							if ((_topASL > (_altASL - _overflyBuffer)) and {((_top * 4.5) > _aDst) or {(_aDst < _range)}}) then
								{
								if (_debug) then
									{
									drawIcon3D ["#(argb,8,8,3)color(0,0,0,0)",[1,1,1,1],(_x modelToWorld [0,0,((_bbr select 1) select 2)]),1,1,0,(str (round _topASL)),1,0.035,"PuristaSemibold"];
									};
								
								if (_topASL > _obstacleHASL) then
									{
									_obstacleHASL = _topASL;
									_obstacle = _x;
									_moDst = _moDst min _aDst;
									
									if (_debug) then
										{
										RYD_TI_a1 setPosATL (_x modelToWorld [0,0,((_bbr select 1) select 2)]);
										};
									};
								}
							}
						foreach _objects;
						};
						
					_heli setVariable ["RYD_TI_PassingO",_obstacle];
					_obsDst = _moDst;
					
					_desiredAlt = _altASL max (_obstacleHASL + _overflyBuffer);
					
					if (_finish > 0) then
						{
						_desiredAlt = linearConversion [0,1,_finish,_desiredAlt,((((getTerrainHeightASL _hPos) max 0) + 30) max _desiredAlt),true];
						};
															
					_diffAlt = _desiredAlt - _cAlt;
					_rPingPos = _hPos;
					_corrM = (_obstacleHASL + _diffAlt)/(sqrt _obsDst);
					
					for "_i" from (_size/2) to (_size + _cVelN) step (_size/8) do
						{
						_pingPos = _heli getPos [_i,_dir];
						_pingPos set [2,0];
						
						_corr = ((getTerrainHeightASL _pingPos) max 0) - ((getTerrainHeightASL _hPos) max 0) + _diffAlt;
						if (((abs _corr) > 0) and {(isNull _obstacle) or {(_corr > (_corrM * (sqrt _obsDst)))}}) then
							{
							_rPingPos = _pingPos;
							
							if (_debug) then
								{
								RYD_TI_a2 setPosATL _pingPos;
								};
								
							if ((_corr/(sqrt _i)) > _corrM) then
								{
								if (_debug) then
									{
									drawIcon3D ["#(argb,8,8,3)color(0,0,0,0)",[1,0.5,0,1],_pingPos,1,1,0,(str (round _corr)),1,0.035,"PuristaSemibold"];
									};
									
								_moDst = _moDst min (_heli distance2D _pingPos);
								_corrM = _corr/(sqrt _i)
								};
							};
						};

					_cAlt = _cAlt - (((getTerrainHeightASL _rPingPos) max 0) - ((getTerrainHeightASL _hPos) max 0));
					_diffAlt = _desiredAlt - _cAlt;
					
					_halfSpeed = (getNumber (configFile >> "CfgVehicles" >> (typeOf _heli) >> "maxSpeed"))/7.2;
					_vel = linearConversion [0,1,_finish,_desiredSpeed,(_halfSpeed min _vel),true];
													
					if ((_cVelN < _vel) or {((abs _diffAlt) > (10/(_cVelN max 10)))}) then
						{
						_altAm = (_diffAlt max 0)/(sqrt (_moDst max 1));
						_fPos = _hPos getPos [(_moDst max (_altAm * 3)),_dir];
						_fPos set [2,(_desiredAlt + _altAm)];

						if (_debug) then
							{
							RYD_TI_a3 setPosASL _fPos;
							};

						_safety = if (_safetyPush) then
							{
							(sqrt ((((_desiredAltitude/2) max 5) - _cAltATL) max 1))
							}
						else
							{
							1
							};
							
						_sharpnessF = if (_safety > 1) then
							{
							1
							}
						else
							{
							_sharpness
							};
						
						_vel2 = (_vel * (1 - _sharpnessF)) + (_vel * ((((_moDst max 1))/((_diffAlt max 1)^0.75)) min 1) * _sharpnessF);
						_vel = (_vel * (1 - _sharpnessF)) + (_vel * ((((_moDst max 1))/((_diffAlt max 1)^0.75)) min 1) * _sharpnessF);
						
						_dirV = _hPos vectorFromTo _fPos;
						_velV = [((((_dirV select 0) * _vel2) * _sharpnessF) + ((_cVel select 0) * (1 - _sharpnessF))),((((_dirV select 1) * _vel2) * _sharpnessF) + ((_cVel select 1) * (1 - _sharpnessF))),(((_dirV select 2) * (_vel * _safety * _sharpnessF)) + ((_cVel select 2) * (1 - _sharpnessF)))];
						_velF = _velV distance [0,0,0];

						_heli addTorque (_heli vectorModelToWorld [(((getMass _heli)^1.3)/16) * (sqrt (_velF/100)),0,0]);
						_heli setVelocity _velV;
						}
					}
				else
					{
					if (not (_diff < 5) and {(_cAlt > 1)}) then
						{
						if (_diff0 > 0) then
							{
							_heli addTorque (_heli vectorModelToWorld [0,0,-((((getMass _heli)^1.35)/25) * 3)]);
							}
						else
							{
							_heli addTorque (_heli vectorModelToWorld [0,0,((((getMass _heli)^1.35)/25) * 3)]);
							}
						};
					}
				}
			};
			
		RYD_TI_wasControl = RYD_TI_Control;
			
		if (_debug) then
			{
			hintSilent format ["spd: %1\nalt: %2\nfps: %3\nfn: %4\nctrl: %5",((velocity _heli) distance [0,0,0]),(((getPosATL _heli) select 2) min ((getPosASL _heli) select 2)),diag_fps,_finish,RYD_TI_Control];
			};
		};	

	if (_debug) then
		{
		systemChat format ["Params: %1",RYD_TI_Params];
		};

	RYD_TI_wasControl = false;	
	RYD_TI_Control = false;
	RYD_TI_EFEH = addMissionEventHandler ["EachFrame",{RYD_TI_Params call RYD_TI_ContourFlight}];
	};

sleep 1;

player allowDamage false;
player moveInCargo heli1;

_handle = [heli1] spawn RYD_TI_TimidIcarus;//simple
//_handle = [heli1,83.3,10,2,1,1,false,0.2,true] spawn RYD_TI_TimidIcarus;//advanced
//[helicopter,desired speed (m/s), desired altitude AGL (m), obstacle overfly buffer (m), obstacle detect mode (0/1), obstacle detect range (m), emergency pull up, smoothness factor, debug mode]

_wp = (group heli1) addWaypoint [(getMarkerPos "m1"),0];
_wp setWaypointType "MOVE";
_wp = (group heli1) addWaypoint [(getMarkerPos "m2"),0];
_wp setWaypointType "MOVE";
_wp = (group heli1) addWaypoint [(getMarkerPos "m3"),0];
_wp setWaypointType "MOVE";
_wp = (group heli1) addWaypoint [(getMarkerPos "m4"),0];
_wp setWaypointType "MOVE";

 

 

  • Like 6
  • Thanks 3

Share this post


Link to post
Share on other sites

Fantastic, and great news that you plan to add it to Hermes Airlift.  And love the name Timid Icarus!

  • Like 4

Share this post


Link to post
Share on other sites

Nice work Rydygier!

I'm wondering if its possible to add a tactical type flight path to how the ai flies like say through the valleys,

what im saying is, the lowest point of a valley is obviously at the bottom in between the two hills.

  

        Take Stratis map for example as seen in your two videos, they ai could seek the lowest point between two hills, or the lowest point of a ravine

or a path that is not only the shortest distance from its destination but the most concealed route.

      Concealed route would be ideal for transport of friendly ai as well as player.

 

Plans for an editor module too like Hermes?

  • Like 2

Share this post


Link to post
Share on other sites
Quote

Plans for an editor module too like Hermes?

 

I would rather first try to integrate this into HAS (depending on encountered difficulties...), say as lowest possible flight ceiling, that player can pick via mouse actions (maybe excluding slingloaded helis). Of course, if there will be a demand to turn also standalone CFS into module popular enough to justify additional work, I can try this too, but before I take this code anywhere else, let's just see some feedback, bug reports and requests. Speaking of which...

 

Quote

I'm wondering if its possible to add a tactical type flight path

 

Currently it works without any pre-planning. That means, it guides the heli along straight path and reacts dynamically on terrain shape and detected obstacles.

 

Adding such type of flight, with preliminary path choice along lowest terrain, is probably possible, something to think about. Also making the heli follow such path should be doable, perhaps using additional, dynamically added mid-waypoints along such path - that will maybe not ensure some terrific accuracy, but seems much simplier to implement than implementing CFS-controlled horizontal maneuvering, which would be a greater challenge. Not sure yet though. BTW IMO such addition would make things too complex for CFS inside HAS (the HAS logic is a maze already), so if I manage to put CFS in current form into HAS, it would rather stay in current, straight line, form while tactical form I would keep for standalone CFS. 

  • Like 2

Share this post


Link to post
Share on other sites

Good looking stuff!

 

Regarding this:

On 6/27/2020 at 6:55 PM, Rydygier said:

Heli during CFS-controlled flight doesn't tolerate well game pausing. After resuming does crazy movements for a while that depending on situation may lead to a crash. Seems, it's something between per frame object's physics manipulation and simulation pausing.

 

See note in link: https://community.bistudio.com/wiki/isGamePaused

 

In your function RYD_TI_ContourFlight put this...

RYD_TI_ContourFlight =
	{
		if (isGamePaused) exitWith {}; 
  
		/**** your code here ****/
	};

...or this...

RYD_TI_ContourFlight = 
	{
		if(not isGamePaused) then {
			/**** your code here ****/
		};
	};

...which ever floats your flying boat.

  • Like 3
  • Thanks 1

Share this post


Link to post
Share on other sites

Thanks for the info! Something, I didn't know. And pretty freshly introduced command as a solution, I see. Great. 🙂

 

EDIT: code updated accordingly.

  • Like 4

Share this post


Link to post
Share on other sites
7 hours ago, Rydygier said:

 

Quote

I'm wondering if its possible to add a tactical type flight path

 

Currently it works without any pre-planning. That means, it guides the heli along straight path and reacts dynamically on terrain shape and detected obstacles.

Can mission designer give a heli waypoints that this script will then use?  Then mission designers can define tactical paths using valleys, etc.  I'm guessing that sharp turns from one way point to another could be an issue though.

Share this post


Link to post
Share on other sites
Quote

Can mission designer give a heli waypoints that this script will then use?

 

Yes, that's how it is done now - CFS works basing on heli's waypoints and leads the heli from current position straight towards current waypoint etc, etc. 

 

Quote

 I'm guessing that sharp turns from one way point to another could be an issue though.

 

Yep, also the fact, CFS influence is gradually reduced to 0 as heli approaches current waypoint, which also could mean, heli will go up at each waypoint then back close to the ground when new waypoint become current. You can observe that in the demo mission. 

  • Like 1

Share this post


Link to post
Share on other sites
1 minute ago, Rydygier said:

Yes, that's how it is done now - CFS works basing on heli's waypoints and leads the heli from current position straight towards current waypoint etc, etc. 

That's great, as it makes it easy for mission makers to use.  It may not be dynamically calculating tactical paths, but that is really an unrelated task (that someone else could take on if its not on your priority list).  As a mission maker, I could use the editor to plot multiple paths using waypoints or markers, then dynamically/conditionally choose one of these heli paths during mission execution (so player feels like path is dynamic).

  • Like 2

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×