Page 1 of 1

SlopeMap access through Lua API

Posted: 05 Mar 2015, 11:08
by ivand
Hi!

I've been searching through Lua API documentation and noticed there is no explicit function to return spring slopeMap. Spring.GetGroundNormal exists but my (limited) dig into springrts code made me think that it references some other kind of Normals, than ones that slope map is based on.

Exposure of the Slope Map could be useful for a gadget/widget author in case own pathfinding algorithm is developed or in case there is a need to calculate an effective unit's speed at particular point on the ground.

Am I missing something or could this be considered as an improvement to Lua AI?

Re: SlopeMap access through Lua API

Posted: 05 Mar 2015, 11:25
by Google_Frog
Spring.GetGroundNormal is the function you want.

Re: SlopeMap access through Lua API

Posted: 05 Mar 2015, 12:57
by ivand
Google_Frog wrote:Spring.GetGroundNormal is the function you want.
Are you sure about that?

The code of getting GetGroundNormal values eventually converge to centerNormalsSynced/centerNormalsUnsynced arrays, while slopeMap is calculated using faceNormalsSynced array. Also code for getting two arrays looks very different and only slightly related.

Re: SlopeMap access through Lua API

Posted: 05 Mar 2015, 15:12
by Google_Frog
Are you sure about that?
Yes.

You would get further if you did stuff.

Re: SlopeMap access through Lua API

Posted: 05 Mar 2015, 17:32
by ivand
Google_Frog wrote:
Are you sure about that?
Yes.

You would get further if you did stuff.
I've just "done stuff", specifically my own Spring.GetGroundSlope implementation, and found out that slope and normal components values are different.

if you know what math to do to transform nx, ny, nz=Spring.GetGroundNormal(x,z) to slope value, don't hesitate to enlight me.

Re: SlopeMap access through Lua API

Posted: 05 Mar 2015, 18:27
by zoggop
see the GetSlopeMapImage function in http://springfiles.com/spring/lua-scrip ... eightmap-0
just use the Y normal value (why this works is beyond me, i don't understand what a normal is)

Re: SlopeMap access through Lua API

Posted: 05 Mar 2015, 18:53
by 8611
A normal vector stands at right angle to other things.
Spring.GetGroundNormal returns a vector that stands at right angle on the ground.

Image

So on flat terrain it points straight to the sky: x=0,y=1,z=0
If the terrain is tilted, so is the vector.

The steeper the terrain, the lower the y-componente becomes and x,z become larger.
On side of a vertical wall y would be zero, but spring does not have truely vertical walls, so it just gets closer to zero the steeper the slope gets.
If the slope-direction does not matter and one is only interessted in slope-slope (like http://springfiles.com/spring/lua-scrip ... eightmap-0 ) then it is enough to use only the y-component of the normal-vector:
y=1 means completly flat, y=0 means vertical.

How that relates to spring's pathing is explained here:
https://springrts.com/wiki/Movedefs.lua ... determined

Re: SlopeMap access through Lua API

Posted: 05 Mar 2015, 20:12
by ivand
As I wrote in my last post, in the meantime I've run few experiments.

Despite believe Google_Frog and others may have, that

Code: Select all

local _,yn=Spring.GetGroundNormal(x,z)
local slope=1-yn
it's not quite the case strictly. Here is the visual example:
http://snag.gy/eDbEi.jpg

I've also dumped whole array of Y_normals and slopeMap and found out, that while the sum of slope(taken from engine) and yn in most cases is close to 1.0, there are cases where sum goes as high as 1.722975 and as low as 0.676002 (map Zion_v1)
How that relates to spring's pathing is explained here:
https://springrts.com/wiki/Movedefs.lua ... determined
This description is right, but it doesn't say there is a number of normal arrays, as I mentioned in OP. Apparently they are calculated slightly differently.

So if you don't mind, I'd like to propose the following patch, which adds support of reading slopeMap

Code: Select all

diff --git a/rts/Lua/LuaSyncedRead.cpp b/rts/Lua/LuaSyncedRead.cpp
index f10cd2f..ec93209 100644
--- a/rts/Lua/LuaSyncedRead.cpp
+++ b/rts/Lua/LuaSyncedRead.cpp
@@ -289,6 +289,7 @@ bool LuaSyncedRead::PushEntries(lua_State* L)
        REGISTER_LUA_CFUNC(GetGroundHeight);
        REGISTER_LUA_CFUNC(GetGroundOrigHeight);
        REGISTER_LUA_CFUNC(GetGroundNormal);
+    REGISTER_LUA_CFUNC(GetGroundSlope);
        REGISTER_LUA_CFUNC(GetGroundInfo);
        REGISTER_LUA_CFUNC(GetGroundBlocked);
        REGISTER_LUA_CFUNC(GetGroundExtremes);
@@ -4739,6 +4740,13 @@ int LuaSyncedRead::GetGroundNormal(lua_State* L)
        return 3;
 }

+int LuaSyncedRead::GetGroundSlope(lua_State* L)
+{
+    const float x = luaL_checkfloat(L, 1);
+    const float z = luaL_checkfloat(L, 2);
+    lua_pushnumber(L, CGround::GetSlope(x, z, CLuaHandle::GetHandleSynced(L)));
+    return 1;
+}

 int LuaSyncedRead::GetGroundInfo(lua_State* L)
 {
diff --git a/rts/Lua/LuaSyncedRead.h b/rts/Lua/LuaSyncedRead.h
index b665959..eccbcda 100644
--- a/rts/Lua/LuaSyncedRead.h
+++ b/rts/Lua/LuaSyncedRead.h
@@ -199,6 +199,7 @@ class LuaSyncedRead {
                static int GetGroundHeight(lua_State* L);
                static int GetGroundOrigHeight(lua_State* L);
                static int GetGroundNormal(lua_State* L);
+        static int GetGroundSlope(lua_State* L);
                static int GetGroundInfo(lua_State* L);
                static int GetGroundBlocked(lua_State* L);
                static int GetGroundExtremes(lua_State* L);

Re: SlopeMap access through Lua API

Posted: 05 Mar 2015, 21:21
by FLOZi
Thanks for the patch, even better if a pull request on github :-)

Re: SlopeMap access through Lua API

Posted: 05 Mar 2015, 23:35
by ivand
FLOZi wrote:Thanks for the patch, even better if a pull request on github :-)
Your passionate speech made me learn basics of github & git. :-) Here it's https://github.com/spring/spring/pull/173

Re: SlopeMap access through Lua API

Posted: 06 Mar 2015, 00:11
by 8611
ivand wrote:
How that relates to spring's pathing is explained here:
https://springrts.com/wiki/Movedefs.lua ... determined
This description is right, but it doesn't say there is a number of normal arrays, as I mentioned in OP. Apparently they are calculated slightly differently.
Most stuff in wiki/forum is about mod making point of view. For example that text is just about explaining that the numbers for maxSlope of a unit are not in degreesĀ° and how they are to be understood.
For that it is not nessecary to know details like how many different arrays exist in engine etc.


This widget shows returned values of GetGroundNormal() and length of vector when a marker is placed. Did not check it in detail but the length is always 1 so seems correct. (floating point inaccury aside)

Code: Select all

function widget:MapDrawCmd(playerID, cmdType, px, py, pz, label)
	if cmdType ~= "point" then return end
	if label ~= "" then return end
	local nx,ny,nz = Spring.GetGroundNormal  (px,pz)
	local length = math.sqrt(nx*nx + ny*ny + nz*nz)
	Spring.MarkerAddPoint (px,py,pz, ("nx="..nx.." ny="..ny.." nz="..nz.." length="..length))	
end
Does http://snag.gy/eDbEi.jpg show something similiar?
Why is nz==1.00 there?

If there is something not matching up then that would be interessting. But it is not quite clear to me what excactly you compared and how/what you read from engine? (which array/function etc)

Re: SlopeMap access through Lua API

Posted: 06 Mar 2015, 06:45
by ivand
@8611,

Good spot. There was a mistake, where rounding was applied to nx and nz components of Normal erroneously, it didn't apply to ny, though. So what I wrote yesterday still holds.

Here is relevant part of widget code. Quick'n'dirty style. Also it shows the part of code there aforementioned bug was.

Code: Select all

function widget:MousePress(mx, my, button)
	if not Spring.IsAboveMiniMap(mx, my) and button==1 then
		local _, pos = Spring.TraceScreenRay(mx, my, true)
		if pos then
			local nx, ny, nz=Spring.GetGroundNormal(pos[1], pos[3])

			--local nx=math.round(nx) <------------mistake
			--local nz=math.round(nz) <------------mistake
			
			--local n=math.sqrt(nx*nx+ny*ny+nz*nz)
			local slope=Spring.GetGroundSlope(pos[1], pos[3])
			--local str=string.format("nx=%.2f ny=%.2f nz=%.2f n=%.2f sl=%.2f", nx, ny, nz, n, slope)
			local str=string.format("nx=%.2f ny=%.2f nz=%.2f sl=%.2f", nx, ny, nz, slope)
			Spring.MarkerAddPoint(pos[1], 0, pos[3], str, true)
		end
	end
end
Note Spring.GetGroundSlope requires modifications to the springrts engine.
If there is something not matching up then that would be interessting. But it is not quite clear to me what excactly you compared and how/what you read from engine? (which array/function etc)
Engine's movement math is centered around slopeMap array

Code: Select all

const float slope  = readMap->GetSlopeMapSynced()[square];
.......
const float* GetSlopeMapSynced() const { return &slopeMap[0]; }
The function I use to get slope value looks following:

Code: Select all

float CGround::GetSlope(float x, float z, bool synced)
{
	const int xhsquare = Clamp(int(x) / (2 * SQUARE_SIZE), 0, mapDims.hmapx - 1);
	const int zhsquare = Clamp(int(z) / (2 * SQUARE_SIZE), 0, mapDims.hmapy - 1);
	const float* slopeMap = readMap->GetSharedSlopeMap(synced);

	return slopeMap[xhsquare + zhsquare * mapDims.hmapx];
}
.......
const float* GetSharedSlopeMap(bool synced) const { return sharedSlopeMaps[synced]; }
.......
	sharedSlopeMaps[0] = &slopeMap[0]; // NO UNSYNCED VARIANT
	sharedSlopeMaps[1] = &slopeMap[0];

Re: SlopeMap access through Lua API

Posted: 06 Mar 2015, 07:09
by 8611
https://github.com/spring/spring/pull/1 ... 638fcR4748
Your new Spring.GetGroundSlope uses
CGround::GetSlope(x, z, CLuaHandle::GetHandleSynced(L))

https://github.com/lhog/spring/blob/dev ... .cpp#L4736
Spring.GetGroundNormal uses
normal = CGround::GetSmoothNormal(x, z, CLuaHandle::GetHandleSynced(L))

I have no idea which one is more correct...from quick look yours seems better to 'get slope like the engine uses it:'

Engine seems to use GetSlope() and GetNormal() in many places that seem physics/pathing related.
GetSmoothNormal() seems used in camera stuff and the parts where it is used in physics seem less relevant.

Another guess is that the x,z coordinates somehow get snapped/rounded to the heightmap square's resolution and so the functions are not reading the same coordinates.

But really no idea of those things, someone who knows better will probally reply to your PR anyway ;)
/edit: hm did you just edit post? Anyway, seems you know more than me there so "good luck!" :)

Re: SlopeMap access through Lua API

Posted: 06 Mar 2015, 07:21
by ivand
Hey 8611.

Yeah, I've edited post multiple times to support the message better. Sorry about that :?

The whole point of topic as it turns out, that engine has different flavors of normals, which are calculated a little bit differently.
Another guess is that the x,z coordinates somehow get snapped/rounded to the heightmap square's resolution and so the functions are not reading the same coordinates.
Thought so as well, that's why I made that silly mistake in the widget (rounding was supposed to be applied to coordinates not to normal's values). Anyway, when I dumped slopeMap and Y-component of GetGroundNormal for the whole map, I used this integer based code:

Code: Select all

	local ynormalMap = assert(io.open("ynormalMap.txt", "w"))
	for z=0, Game.mapSizeZ, 1 do
		for x=0, Game.mapSizeX, 1 do
			local _,val=Spring.GetGroundNormal(x, z)
			--local vals=string.format("%.2f\n", val)			
			local vals=string.format("%f\n", val)
			ynormalMap:write(vals)
		end
	end
	assert(ynormalMap:close())
	
	local slopeMap = assert(io.open("slopeMap.txt", "w"))
	for z=0, Game.mapSizeZ, 1 do
		for x=0, Game.mapSizeX, 1 do
			local val=Spring.GetGroundSlope(x, z)
			--local vals=string.format("%.2f\n", val)			
			local vals=string.format("%f\n", val)
			slopeMap:write(vals)
		end
	end		
	assert(slopeMap:close())
	
	local sumMap = assert(io.open("sumMap.txt", "w"))
	for z=0, Game.mapSizeZ, 1 do
		for x=0, Game.mapSizeX, 1 do
			local slope=Spring.GetGroundSlope(x, z)
			local _,ynorm=Spring.GetGroundNormal(x, z)
			local val=ynorm+slope
			--local vals=string.format("%.2f\n", val)			
			local vals=string.format("%f\n", val)
			sumMap:write(vals)
		end
	end		
	assert(sumMap:close())
So if it's somehow related to rounding, then it's an engine side thing, not widget side.

Re: SlopeMap access through Lua API

Posted: 06 Mar 2015, 12:29
by Kloot
Spring.GetGroundNormal returns a vector that is interpolated between four *center* normals.

Each center (quad) normal is simply two face (triangle) normals averaged together, while the slopemap is constructed from *face* normals.

For that reason, adding...

Code: Select all

diff --git a/rts/Lua/LuaSyncedRead.cpp b/rts/Lua/LuaSyncedRead.cpp
index f10cd2f..0716e01 100644
--- a/rts/Lua/LuaSyncedRead.cpp
+++ b/rts/Lua/LuaSyncedRead.cpp
@@ -4732,7 +4732,12 @@ int LuaSyncedRead::GetGroundNormal(lua_State* L)
 {
        const float x = luaL_checkfloat(L, 1);
        const float z = luaL_checkfloat(L, 2);
-       const float3 normal = CGround::GetSmoothNormal(x, z, CLuaHandle::GetHandleSynced(L));
+
+       // smoothed or raw center-normal
+       const float3 normal = luaL_checkboolean(L, 3)?
+               CGround::GetNormal(x, z, CLuaHandle::GetHandleSynced(L)):
+               CGround::GetSmoothNormal(x, z, CLuaHandle::GetHandleSynced(L));
+
        lua_pushnumber(L, normal.x);
        lua_pushnumber(L, normal.y);
        lua_pushnumber(L, normal.z);
... would also be a good idea.

Re: SlopeMap access through Lua API

Posted: 06 Mar 2015, 18:04
by ivand
@Kloot, Code blocks for getting CGround::GetNormal and CGround::GetSlope look related, but still a bit different:

Code: Select all

const float3& CGround::GetNormal(float x, float z, bool synced)
....
const float3* normalMap = readMap->GetSharedCenterNormals(synced);
....
const float3* GetSharedCenterNormals(bool synced) const { return sharedCenterNormals[synced]; }
....
sharedCenterNormals[0] = &centerNormalsUnsynced[0];
sharedCenterNormals[1] = &centerNormalsSynced[0];
.....
centerNormalsSynced[y * mapDims.mapx + x] = (fnTL + fnBR).Normalize();

Code: Select all

float CGround::GetSlope(float x, float z, bool synced)
....
const float* slopeMap = readMap->GetSharedSlopeMap(synced);
....
const float* GetSharedSlopeMap(bool synced) const { return sharedSlopeMaps[synced]; }
.....
sharedSlopeMaps[0] = &slopeMap[0]; // NO UNSYNCED VARIANT
sharedSlopeMaps[1] = &slopeMap[0];
.....
faceNormalsSynced[(y * mapDims.mapx + x) * 2    ] = fnTL;
faceNormalsSynced[(y * mapDims.mapx + x) * 2 + 1] = fnBR;
.....
float avgslope = 0.0f;
avgslope += faceNormalsSynced[(idx0    ) * 2    ].y;
avgslope += faceNormalsSynced[(idx0    ) * 2 + 1].y;
avgslope += faceNormalsSynced[(idx0 + 1) * 2    ].y;
avgslope += faceNormalsSynced[(idx0 + 1) * 2 + 1].y;
avgslope += faceNormalsSynced[(idx1    ) * 2    ].y;
avgslope += faceNormalsSynced[(idx1    ) * 2 + 1].y;
avgslope += faceNormalsSynced[(idx1 + 1) * 2    ].y;
avgslope += faceNormalsSynced[(idx1 + 1) * 2 + 1].y;
avgslope *= 0.125f;

float maxslope =              faceNormalsSynced[(idx0    ) * 2    ].y;
maxslope = std::min(maxslope, faceNormalsSynced[(idx0    ) * 2 + 1].y);
maxslope = std::min(maxslope, faceNormalsSynced[(idx0 + 1) * 2    ].y);
maxslope = std::min(maxslope, faceNormalsSynced[(idx0 + 1) * 2 + 1].y);
maxslope = std::min(maxslope, faceNormalsSynced[(idx1    ) * 2    ].y);
maxslope = std::min(maxslope, faceNormalsSynced[(idx1    ) * 2 + 1].y);
maxslope = std::min(maxslope, faceNormalsSynced[(idx1 + 1) * 2    ].y);
maxslope = std::min(maxslope, faceNormalsSynced[(idx1 + 1) * 2 + 1].y);

// smooth it a bit, so small holes don't block huge tanks
const float lerp = maxslope / avgslope;
const float slope = mix(maxslope, avgslope, lerp);
Is it worth checking that slopeMap[x,z] is indeed easy to get from CGround::GetNormal[x,z](y component)?

Also does it make sense to Lua export these two functions (again for pathfinding & ETA)?

Code: Select all

float CMoveMath::GetPosSpeedMod(const MoveDef& moveDef, int xSquare, int zSquare)
float CMoveMath::GetPosSpeedMod(const MoveDef& moveDef, int xSquare, int zSquare, const float3& moveDir)