Jump to content
Tankbuster

Adding functionality to fnc_findsafepos

Recommended Posts

Because findsafepos will happily choose position inside large buildings, including unenterable ones, I've made a small addition to my version of findsafepos

 

/*
	Author:
		Joris-Jan van 't Land, optimised by Killzone_Kid, expanded by tankbuster

	Description:
		Function to generate position in the world according to several parameters.

	Parameters:
		0: (Optional) ARRAY - center position
				Note: passing [] (empty Array), the world's "safePositionAnchor" entry will be used.

		1: (Optional) NUMBER - minimum distance from the center position
		2: (Optional) NUMBER - maximum distance from the center position
				Note: passing -1, the world's "safePositionRadius" entry will be used.

		3: (Optional) NUMBER - minimum distance from the nearest object
		4: (Optional) NUMBER - water mode
				0 - cannot be in water
				1 - can either be in water or not
				2 - must be in water

		5: (Optional) NUMBER - maximum terrain gradient (hill steepness)
		6: (Optional) NUMBER - shore mode:
				0 - does not have to be at a shore
				1 - must be at a shore

		7: (Optional) ARRAY - blacklist (Array of Arrays):
				(_this select 7) select 0: ARRAY - top-left coordinates of blacklisted area
				(_this select 7) select 1: ARRAY - bottom-right coordinates of blacklisted area

		8: (Optional) ARRAY - default positions (Array of Arrays):
				(_this select 8) select 0: ARRAY - default position on land
				(_this select 8) select 1: ARRAY - default position on water

		9: (Optional) number - Outside mode:
				0 - Can be in a building
				1 - Will not be in a building

	Returns:
		Coordinate array with a position solution.

*/

scopeName "main";

params [
	["_checkPos",[]],
	["_minDistance",0],
	["_maxDistance",-1],
	["_objectProximity",0],
	["_waterMode",0],
	["_maxGradient",0],
	["_shoreMode",0],
	["_posBlacklist",[]],
	["_defaultPos",[]],
	["_outsideMode", 0]
];

// support object for center pos as well
if (_checkPos isEqualType objNull) then {_checkPos = getPos _checkPos};

/// --- validate input
#include "\a3\functions_f\paramsCheck.inc"
#define arr1 [_checkPos,_minDistance,_maxDistance,_objectProximity,_waterMode,_maxGradient,_shoreMode,_posBlacklist,_defaultPos]
#define arr2 [[],0,0,0,0,0,0,[],[]]
paramsCheck(arr1,isEqualTypeParams,arr2)

private _defaultMaxDistance = worldSize / 2;
private _defaultCenterPos = [_defaultMaxDistance, _defaultMaxDistance, 0];

private _fnc_defaultPos =
{
	_defaultPos = _defaultPos param [parseNumber _this, []];
	if !(_defaultPos isEqualTo []) exitWith {_defaultPos};

	_defaultPos = getArray (configFile >> "CfgWorlds" >> worldName >> "Armory" >> ["positionStart", "positionStartWater"] select _this);
	if !(_defaultPos isEqualTo []) exitWith {_defaultPos};

	_defaultPos = getArray (configFile >> "CfgWorlds" >> worldName >> "centerPosition");
	if !(_defaultPos isEqualTo []) exitWith {_defaultPos};

	_defaultCenterPos
};

if (_checkPos isEqualTo []) then
{
	_checkPos = getArray (configFile >> "CfgWorlds" >> worldName >> "safePositionAnchor");
	if (_checkPos isEqualTo []) then {_checkPos = _defaultCenterPos};
};

if (_maxDistance < 0) then
{
	_maxDistance = getNumber (configFile >> "CfgWorlds" >> worldName >> "safePositionRadius");
	if (_maxDistance <= 0) then {_maxDistance = _defaultMaxDistance};
};

private _checkProximity = _objectProximity > 0;
private _checkBlacklist = !(_posBlacklist isEqualTo []);
private _deltaDistance = _maxDistance - _minDistance;

_shoreMode = _shoreMode != 0;

if (_checkBlacklist) then
{
	_posBlacklist = _posBlacklist apply
	{
		call
		{
			if (_x isEqualTypeParams [[],[]]) exitWith
			{
				_x select 0 params [["_x0", 0], ["_y0", 0]];
				_x select 1 params [["_x1", 0], ["_y1", 0]];
				private _a = (_x1 - _x0) / 2;
				private _b = (_y0 - _y1) / 2;
				[[_x0 + _a, _y0 - _b], abs _a, abs _b, 0, true]
			};
			if (_x isEqualTypeAny [objNull, "", locationNull]) exitWith {_x};
			objNull
		};
	};
};

for "_i" from 1 to 3000 do
{
	_checkPos getPos [_minDistance + random _deltaDistance, random 360] call
	{
		if (_this isFlatEmpty [-1, -1, _maxGradient, _objectProximity, _waterMode, _shoreMode] isEqualTo []) exitWith {}; // true & exits if ife fails because not flat/empty
		if (_checkProximity && {!(nearestTerrainObjects [_this, [], _objectProximity, false] isEqualTo [])}) exitWith {}; // true & exits if nto says nothing nearby
		if (_outsideMode && {(lineIntersectsSurfaces [(ATLToASL _this), ((ATLToASL _this) vectorAdd [0,0,50]), objNull, objNull, true,1, "GEOM", "NONE"] ) select 0 params ["","","", ""]} ) exitWith {}; // true & exits if indoors
		if (_checkBlacklist && {{if (_this inArea _x) exitWith {true}; false} forEach _posBlacklist}) exitWith {};
		_this select [0, 2] breakOut "main";
	};
};

// search failed, try default position
(_waterMode != 0) call _fnc_defaultPos

The addition is cobbled together from Killzone Kid's code that checks for players being inside buildings. I simplified it a little but if I'm honest, I don't fully understand how it does it's stuff, especially the params part.

The 2 lines I've added are for the _outsideMode.

Share this post


Link to post
Share on other sites

Obvs, I'll happily accept optimisations of my code. :)

Share this post


Link to post
Share on other sites

I intend to add 2 further things to this, though they will have to be used sparingly as they might be cpu expensive.

 

(nearestObjects [_droppos, ["AllVehicles", "Ruins_F", "House_f", "Wall_F","BagBunker_base_f"], _objdist, true])

The current code uses nearestTerrainObjects which gets only objects 'baked' into the terrain (and it doesn't get all of those, in fact) so adding this check will make it even safer.

 

The other thing I want to add is a strict mode that will use lineIntersectsSurfaces to check for objects near the target position. This will be instead of, rather than in addition to the nearTerrainObjects and nearestObjects checks

Edited by Tankbuster
argument 4 for nearestobjects should be true not false for a 2d search

Share this post


Link to post
Share on other sites

neat :)

 

yeah i did some tweaks to BIS_fnc_findSafePos as well. Perhaps BI needs to add another similar but modernized function to take advantage of the new post-1.50 script commands.

 

I notice a lot of designers have trouble on the Tanoa terrain, since there are few/no BIS functions which can handle the jungles and object density.

  • Like 1

Share this post


Link to post
Share on other sites

In fairness, @killzone_kid has already tweaked it. I'm more into expanding it.

 

For example, it uses nearestterrainobjects which isn't getting quite enough objects. It doesn't get anything placed dynamically by the mission and it doesn't even get all the terrain objects. 

Also, and this is common to all the near* commands, it's looking for the centre of an object, rather than the limits of where that object actually exists. When you're talking about large objects, the difference between the object centre and the outer limits of its actual model can be big and can cause problems,

 

Adding functionality and making it by default off, is the way forward. Obviously, we have to not break anything that's already out there and we have to make mission designers aware of the pitfalls of being gung-ho with search radii.

 

 

 

Share this post


Link to post
Share on other sites

nearestTerrainObjects vs nearestObjects? 

because as far as I can tell, the nearestTerrainObjects picks up on pretty much everything in my testing. Not sure if it is applied differently on your testing Tank. 

Share this post


Link to post
Share on other sites

Add blacklist support for Area format [center, a, b, angle, isRectangle, c]. (optional c)

I noticed it was missing the other day and had to create invisible markers instead. I already new the position, size and direction of the area I wanted to blacklist and would have been easier if this was supported.

if (_x isEqualTypeParams [[],[]]) exitWith
{
	_x select 0 params [["_x0", 0], ["_y0", 0]];
	_x select 1 params [["_x1", 0], ["_y1", 0]];
	private _a = (_x1 - _x0) / 2;
	private _b = (_y0 - _y1) / 2;
	[[_x0 + _a, _y0 - _b], abs _a, abs _b, 0, true]
};
if (_x isEqualTypeAny [objNull, "", locationNull]) exitWith {_x};
if (
	_x isEqualTypeParams [[],0,0,0,true] &&
	count _x < 7 &&
	{[true,(_x select 5) isEqualType 0] select (count _x > 5)} 
) exitWith {_x};
objNull

 

Edited by Larrow
Suppose some checking for the optional param should be included

Share this post


Link to post
Share on other sites
3 hours ago, Larrow said:

Add blacklist support for Area format [center, a, b, angle, isRectangle, c]. (optional c)

I noticed it was missing the other day and had to create invisible markers instead. I already new the position, size and direction of the area I wanted to blacklist and would have been easier if this was supported.


if (_x isEqualTypeParams [[],[]]) exitWith
{
	_x select 0 params [["_x0", 0], ["_y0", 0]];
	_x select 1 params [["_x1", 0], ["_y1", 0]];
	private _a = (_x1 - _x0) / 2;
	private _b = (_y0 - _y1) / 2;
	[[_x0 + _a, _y0 - _b], abs _a, abs _b, 0, true]
};
if (_x isEqualTypeAny [objNull, "", locationNull]) exitWith {_x};
if (
	_x isEqualTypeParams [[],0,0,0,true] &&
	count _x < 7 &&
	{[true,(_x select 5) isEqualType 0] select (count _x > 5)} 
) exitWith {_x};
objNull

 

 
 

The last time I used blacklist, it was behaving weirdly, so I've stopped. The function was returning a position at the corner of the blacklisted area EVERY time. So yes, it needs some TLC

Share this post


Link to post
Share on other sites
5 hours ago, Midnighters said:

nearestTerrainObjects vs nearestObjects? 

because as far as I can tell, the nearestTerrainObjects picks up on pretty much everything in my testing. Not sure if it is applied differently on your testing Tank. 

 

Trees. It doesn't get trees for one.

Share this post


Link to post
Share on other sites

The problems with nearestterrainobjects are firstly, that it doesn't get trees, and secondly that it does get all kinds of objects that are not relevant to what findsafepos should do. Bushes, runway lights, kerbs and sidewalks to name but a few. All of these objects won't interfere with the spawning of a vehicle there (which is what findsafepos is supposed to do) but nearrestterrainobjects will find them and deem them as not safe. Crazy.

Share this post


Link to post
Share on other sites
6 hours ago, Midnighters said:

nearestTerrainObjects vs nearestObjects? 

because as far as I can tell, the nearestTerrainObjects picks up on pretty much everything in my testing. Not sure if it is applied differently on your testing Tank. 

 

At least, the nearestTerrainObjects is more useful for returning "HOUSE" objects. (as example easy to check on Vanilla ARMA).

Just test it! NearestObjects returns  far more objects, which couldn't be taken for HOUSE in real world, like every runway lamps... and much more.

It's far more consistent for what you're waiting for, with nearestTerrainObjects HOUSE.

On the other hands, you can find some objects detected by nearestTerrainObjects HOUSE and not by nearestObjects HOUSE, like cargo20_grey_f.p3d. (explanation seems to be in the BIKI page and reported here at the end).

 

Now, about placed objects via editor, still using HOUSE with both commands, placing 2 or 3 similar airport towers (next to Stratis airfield) within the airport area, nearestTerrainObjects will fail to detect the added towers (detect only the map embedded one) meanwhile nearestObjects will included all of them within the same radius.

The BI documentation is not very clear about that. You can even think the contrary after reading: " In contrast to nearestObjects this command [nearestTerrainObjects] returns terrain placed objects like trees, rocks and buildings which don't necessarily need an associated config class.

 

 

Share this post


Link to post
Share on other sites
Just now, pierremgi said:

 

At least, the nearestTerrainObjects is more useful for returning "HOUSE" objects. (as example easy to check on Vanilla ARMA).

Just test it! NearestObjects returns  far more objects, which couldn't be taken for HOUSE in real world, like every runway lamps... and much more.

It's far more consistent for what you're waiting for, with nearestTerrainObjects HOUSE.

On the other hands, you can find some objects detected by nearestTerrainObjects HOUSE and not by nearestObjects HOUSE, like cargo20_grey_f.p3d. (explanation seems to be in the BIKI page and reported here at the end).

 

Now, about placed objects via editor, still using HOUSE with both commands, placing 2 or 3 similar airport towers (next to Stratis airfield) within the airport area, nearestTerrainObjects will fail to detect the added towers (detect only the map embedded one) meanwhile nearestObjects will included all of them within the same radius.

The BI documentation is not very clear about that. You can even think the contrary after reading: " In contrast to nearestObjects this command [nearestTerrainObjects] returns terrain placed objects like trees, rocks and buildings which don't necessarily need an associated config class.

 

 

I've been able to find runway lights and houses with nearestTerrainObjects before. I understand the difference between editor placed versus terrain builder shit. But in general use I don't see much difference, of course if a command yields something the other variant doesn't for your particular project. Then so be it, I was just wondering in this instance why trees weren't being picked up.

 

Share this post


Link to post
Share on other sites
1 hour ago, Tankbuster said:

The problems with nearestterrainobjects are firstly, that it doesn't get trees, and secondly that it does get all kinds of objects that are not relevant to what findsafepos should do. Bushes, runway lights, kerbs and sidewalks to name but a few. All of these objects won't interfere with the spawning of a vehicle there (which is what findsafepos is supposed to do) but nearrestterrainobjects will find them and deem them as not safe. Crazy.


It does get trees and it doesn't get all sorts of non-relevant objects. Having a bush at selected position could be undesirable and well as other vegetation. Rocks also could create problems. Anyway, after trying different approaches, brute intersect to check if position is not inside some object seems to be the fastest and most reliable solution. Would most likely be added by default. Also noted Larrow's suggestion.

Share this post


Link to post
Share on other sites

I've used nearestObjects and found no real difficulty or negative issues with placing.  I first check isflatempty in an area then I use

 

 

if (count (nearestObjects [position _this, ["LandVehicle","Air"], 10]) > 0) exitWith {hint "Clear the area before mounting the FOB";hint str((nearestObjects [position _this, ["LandVehicle","Air"], 10]));};  for generating a land base

Share this post


Link to post
Share on other sites
45 minutes ago, killzone_kid said:


It does get trees and it doesn't get all sorts of non-relevant objects. Having a bush at selected position could be undesirable and well as other vegetation. Rocks also could create problems. Anyway, after trying different approaches, brute intersect to check if position is not inside some object seems to be the fastest and most reliable solution. Would most likely be added by default. Also noted Larrow's suggestion.

 

It gets SOME trees, mostly small ones. As you say, it also doesn't return rocks. So, as I say, it's not very good for use in findsafepos.

 

I am writing up a lineINtersects based solution now. Hope to have it useable in a few days. Surely this is the way forward?

Share this post


Link to post
Share on other sites
On 27/04/2017 at 7:53 AM, killzone_kid said:

What trees or rocks do not get detected? have you got a repro example?

 

 

 

No. :) Because I was using too small a radius and the object centre of a tree can be quite high up and until I used 2d mode (which I've only just noticed) the taller trees weren't showing up. Same with rocks - with the big ones, it's hard to get close enough to the object centre when using radii less than 5 or 6. 2d is going to help a little.

 

While we're on the subject, I see that findsafepos is using 3d mode. Surely 2d mode as default would be best for this? Firstly, it's cheaper on CPU and secondly, findsafepos is terrain-level, 2d type of thing (You wouldn't be looking for safe pos above ground level) so until we can code up and deploy a lineintersects upgrade for findsafepos, I suggest we all uses2d mode and if I'm reasoning this right, could the default be changed in that function?

  • Like 1

Share this post


Link to post
Share on other sites
50 minutes ago, Tankbuster said:

I see that findsafepos is using 3d mode.


old code probably, it uses 2D mode now 

nearestTerrainObjects [_this, [], _objectProximity, false, true]

Share this post


Link to post
Share on other sites
On 25/04/2017 at 11:52 AM, Tankbuster said:

 



for "_i" from 1 to 3000 do
{
	_checkPos getPos [_minDistance + random _deltaDistance, random 360] call
	{
		if (_this isFlatEmpty [-1, -1, _maxGradient, _objectProximity, _waterMode, _shoreMode] isEqualTo []) exitWith {}; // true & exits if ife fails because not flat/empty
		if (_checkProximity && {!(nearestTerrainObjects [_this, [], _objectProximity, false] isEqualTo [])}) exitWith {}; // true & exits if nto says nothing nearby
		if (_outsideMode && {(lineIntersectsSurfaces [(ATLToASL _this), ((ATLToASL _this) vectorAdd [0,0,50]), objNull, objNull, true,1, "GEOM", "NONE"] ) select 0 params ["","","", ""]} ) exitWith {}; // true & exits if indoors
		if (_checkBlacklist && {{if (_this inArea _x) exitWith {true}; false} forEach _posBlacklist}) exitWith {};
		_this select [0, 2] breakOut "main";
	};
};

// search failed, try default position
(_waterMode != 0) call _fnc_defaultPos

 

Hi all,

you do a loop, 3000 times to check a position at random within an area. This seems to me too approximative. You can optimize that, using a Fermat spiral, for a far better distribution like this:

 

If you want to test that in a trigger, actual way, for a 300m area / 20 m min distance, looks like:
 

for "_i" from 1 to 3000 do {
 _mk = createMarker [str (_i), thisTrigger getpos [20 + random 300, random 360]];
 _mk setmarkertype "mil_dot"
};

I suggest:
 

 _n = 300;
 _c = 300 / (sqrt (3000 - 1));
 for '_i' from 3000 to 60 step -1 do {
   _rho = _c * sqrt (_i + 0.5);
   _theta = 137.508 * (_i + 0.5);
   _ckPos = thistrigger getPos [_rho,_theta];
   _mk = createMarker [str(_i), _ckpos];
   _mk setmarkertype "mil_dot"
};

 

http://steamcommunity.com/sharedfiles/filedetails/?id=915167519

http://steamcommunity.com/sharedfiles/filedetails/?id=915167878

Demonstration is more relevant with wider area and/or less points.

So, in 2nd case, you can even reduce drastically the number of points or adjust it along with the radius of area.

 

NB: you can change what you want, it's just an example, but keep the 137,508 as "golden angle in degree" for the spiral.

 

Share this post


Link to post
Share on other sites

What's the CPU impact, @pierre MGI?  I mean, I love the results, but as always, I worry about the expense on the CPU.

Ever since I got the function code out of the game, I've wondered why it needs to do 3000 iterations and if that could be optimised. The 3000 takes no account of the radius in use, so at really big radii, it doesn't really improve the chance of returning a safepos that's close to the centre when mindist if zero or 1, which I think it should.

I guess there's a tradeoff to be had. If your code can return a better result, quicker, then we don't mind it being more CPU expensive.

Share this post


Link to post
Share on other sites

I missed to mention you have an already nearest/farthest result, depending on the way you write the spiral (farthest in my example). For the code optimization, and CPU impact, we must decide what is the best ratio points/area. Depending on what mesh you need. On sure thing, you have to compare for the same area. Randomization lets some holes in the sock. formerly, I tested with success a highest point of an island such as Altis from any position on the map. I needed 2 loops like this one (the second one 10 times smaller than the world sized one.Quick result enough.

Share this post


Link to post
Share on other sites

These commands are more related to the audio system but they could be useful to us if we decide to devote any further time to this. Note that they are not in the commands directory page of the biki

 

https://community.bistudio.com/wiki/getAllEnvSoundControllers

 

https://community.bistudio.com/wiki/getEnvSoundController

 

https://community.bistudio.com/wiki/Arma_3_Sound:_SoundControllers

 

I already use them when looking for a nice place to put a minefield. "Meadow" works well there.

  • Like 3

Share this post


Link to post
Share on other sites
13 hours ago, Tankbuster said:

These commands are more related to the audio system but they could be useful to us if we decide to devote any further time to this. Note that they are not in the commands directory page of the biki

 

https://community.bistudio.com/wiki/getAllEnvSoundControllers

 

https://community.bistudio.com/wiki/getEnvSoundController

 

https://community.bistudio.com/wiki/Arma_3_Sound:_SoundControllers

 

I already use them when looking for a nice place to put a minefield. "Meadow" works well there.

 

i use these commands extensively in an AI mod, I was wondering when people would find out about their terrain-finding uses :)

  • 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

×