Advertisement

How to prepare an influence map to take the influence of obstacles into account

Started by May 03, 2021 12:38 PM
22 comments, last by JoeJ 3 years, 7 months ago

This is awesome @JoeJ! Can I see the blur function? I did currently set the Influence on the closest Node to a Enemy or Ally to 1 or either -1 and now trying to blur out somehow from those nodes to its neighbours

trying it like this: but nothing changes

public void CalculateInfluenceBlurring(ref InfluenceMap influenceMap, Transform[] enemyAgentPosition, Transform[] alliedAgentPosition)
    {
        InfluenceMap m = influenceMap;

        // Set Influence
        foreach (Transform t in enemyAgentPosition) m.setClosestIVector(t.position, -1);
        foreach (Transform t in alliedAgentPosition) m.setClosestIVector(t.position, 1);

        // Blur
        foreach (InfluenceMap.IVector2 v in m._imap)
        {
            if (v.state != InfluenceMap.VectorState.OBSTACLE)
            {
                for (int i = 0; i < v.neighbours.Length - 1; i++)
                {
                    v.neighbours[i].inf = v.inf * Mathf.Sqrt(-1 * Decay);
                }

                if (v.inf > m.maxInf) m.maxInf = v.inf;
                if (v.inf < m.minInf) m.minInf = v.inf;
            }
        }

        map = m;
    }

None

Done with it. I call it ‘Escape the dots’ : )

		static bool visDiffusionPF = 1; ImGui::Checkbox("visDiffusionPF", &visDiffusionPF);
		if (visDiffusionPF)
		{
			constexpr int grid_exp = 6;
			constexpr int grid_res = 1<<grid_exp;
			constexpr int grid_mask = grid_res-1;
			constexpr float delta = 1.f / float(grid_res);

			static std::vector<float> map(grid_res*grid_res, 0);
			static std::vector<float> temp(grid_res*grid_res, 0);
			static std::vector<vec2> gradientField(grid_res*grid_res, vec2(0));

			struct Agent
			{
				vec2 position = vec2(0);
				vec2 velocity = vec2(0); // EDIT: this is not used - forgot to delete it
			};

			static std::vector<Agent> agents (5);

			// init

			static int init = true;
			if (init) 
			{
				// make level
				for (int y=0; y<grid_res; y++)
				for (int x=0; x<grid_res; x++)
				{
					int i = x + y*grid_res;
					map[i] = 0;

					if (x+2==grid_mask-y) map[i] = -1; // diagonal wall
					if (x==10) map[i] = -1; // vert. wall
					if (y==20) map[i] = -1; // horiz. wall

					if (x==0 || y==0 || x==grid_mask || y==grid_mask) 
						map[i] = -1; // boundary
					else					
						if (((x^y)&24) == 0) map[i] = 0; // open walls
				}

				int prn = 0;
				for (auto &a : agents)
				{
					for (int n=0; n<2; n++)
					{
						prn = prn * 1664525 + 371;
						a.position[n] = float (prn) * 0.0001f;
						a.position[n] -= floor(a.position[n]);
						a.position[n] = 0.05f + a.position[n] * 0.9f;
					}
					a.velocity = vec2(0);
				}

				init = false;
			}

			// game loop

			// bilinear filter

			auto SetupSampleCoords2 = [&] (int *gridI, float *gridF, const float *coords)
			{
				float offset[2] = {
					coords[0] - .5f,
					coords[1] - .5f};
				float fl[3] = { 
					floor(offset[0]), 
					floor(offset[1]) }; 
				for (int i=0; i<2; i++)
				{
					gridI[i] = int(fl[i]) & grid_mask; 
					gridF[i] = offset[i] - fl[i]; 
				}
			};

			auto Inject2 = [&] (auto &map,
				const int *gridI, const float *gridF, const float sample)
			{
				const int dimX = grid_res;
				const int mX = grid_mask;
				const int mY = grid_mask;
							
				float gridG[2] = {
					1-gridF[0],
					1-gridF[1]};
		
				int g00 = (gridI[0]&mX)		+ (gridI[1]&mY)		* dimX;
				int g01 = (gridI[0]&mX)		+ ((gridI[1]+1)&mY) * dimX;
				int g10 = ((gridI[0]+1)&mX) + (gridI[1]&mY)		* dimX;
				int g11 = ((gridI[0]+1)&mX) + ((gridI[1]+1)&mY) * dimX;
		
				bool isObstacle00 = map[g00] < 0;
				bool isObstacle01 = map[g01] < 0;
				bool isObstacle10 = map[g10] < 0;
				bool isObstacle11 = map[g11] < 0;
				if (isObstacle00)
					return;

				if (!isObstacle00) map[g00] += sample * gridG[0] * gridG[1];
				if (!isObstacle01) map[g01] += sample * gridG[0] * gridF[1];
				if (!isObstacle10) map[g10] += sample * gridF[0] * gridG[1];
				if (!isObstacle11 && (!isObstacle01 || !isObstacle10)) map[g11] += sample * gridF[0] * gridF[1];
			};

			auto Sample2 = [&] (const auto gradientMap, const auto &map,
				const int *gridI, const float *gridF)
			{
				const int dimX = grid_res;
				const int mX = grid_mask;
				const int mY = grid_mask;					
							
				float gridG[2] = {
					1-gridF[0],
					1-gridF[1]};

				int g00 = (gridI[0]&mX)		+ (gridI[1]&mY)		* dimX;
				int g01 = (gridI[0]&mX)		+ ((gridI[1]+1)&mY) * dimX;
				int g10 = ((gridI[0]+1)&mX) + (gridI[1]&mY)		* dimX;
				int g11 = ((gridI[0]+1)&mX) + ((gridI[1]+1)&mY) * dimX;

				bool isObstacle00 = map[g00] < 0;
				bool isObstacle01 = map[g01] < 0;
				bool isObstacle10 = map[g10] < 0;
				bool isObstacle11 = map[g11] < 0;
				if (isObstacle00)
					return vec2(0);

				vec2 sample (0);
				if (!isObstacle00) sample += gradientMap[g00] * gridG[0] * gridG[1];
				if (!isObstacle01) sample += gradientMap[g01] * gridG[0] * gridF[1];
				if (!isObstacle10) sample += gradientMap[g10] * gridF[0] * gridG[1];
				if (!isObstacle11 && (!isObstacle01 || !isObstacle10)) sample += gradientMap[g11] * gridF[0] * gridF[1];
				return sample;
			};
#if 0
			// cubic filter (unused)

			auto SetupSampleCoords3 = [&] (int *gridI, float *gridF, const float *coords)
			{
				float fl[2] = { 
					floor(coords[0]), 
					floor(coords[1]) }; 
				for (int i=0; i<2; i++)
				{
					gridI[i] = (int(fl[i]) - 1) & grid_mask; 
					gridF[i] = coords[i] - fl[i]; 
				}
			};

			auto Inject3 = [&] (auto &map,
				const int *gridI, const float *gridF, const float sample)
			{
				auto square = [] (const vec &v)
				{
					return mulPerElem(v,v); // v[0]*=v[0]; v[1]*=v[1]; v[2]*=v[2]; 
				};

				vec p(gridF[0], gridF[1], gridF[2]); 
				vec fw[3];
				{
					fw[0] = mulPerElem (vec(.5f), square(vec(1.f) - p));
					fw[1] = vec(.75f) - square(p - vec(.5f));
					fw[2] = mulPerElem (vec(.5f), square(p));
				}

				for (int j=0; j<3; j++)
				for (int i=0; i<3; i++)
				{

					float w = fw[i][0] * fw[j][1];
					int X = (gridI[0] + i) & grid_mask;
					int Y = (gridI[1] + j) & grid_mask;
			
					int g = X + Y * grid_res;
					if (map[g] >= 0) // don't change obstacles
						map[g] += sample * w;
				}
			};
#endif


			{
				// player movement

				constexpr float playerSpeed = 0.001f;
				Agent &player = agents[0];
				vec2 vel = vec2(0);
				if (application.KeyDown('K')) vel += vec2(0,-1.f);
				if (application.KeyDown('I')) vel += vec2(0,1.f);
				if (application.KeyDown('J')) vel += vec2(-1.f,0);
				if (application.KeyDown('L')) vel += vec2(1.f,0);
				vel *= playerSpeed;
				player.position += vel;
				player.position[0] -= floor(player.position[0]);
				player.position[1] -= floor(player.position[1]);


				// inject fluid energy from player

				constexpr float injectAmount = 10.f;
				float tc[2] = {player.position[0] * float(grid_res), player.position[1] * float(grid_res)};
				int gridI[2]; float gridF[2];
#if 0 // cubic
				SetupSampleCoords3 (gridI, gridF, tc);
				Inject3 (map, gridI, gridF, injectAmount);
#else // bilinear
				SetupSampleCoords2 (gridI, gridF, tc);
				Inject2 (map, gridI, gridF, injectAmount);
#endif
				if (map[gridI[0] + gridI[1] * grid_res] < 0)
					init = true; // game over if running into wall


				// blur fluid and calc gradient

				constexpr float evaporate = 0.01f;

#if 0 // HQ blur but leaks through diagonal walls :(
				for (int y=0; y<grid_res; y++)
				for (int x=0; x<grid_res; x++)
				{
					int i = x + y*grid_res;
					float value = map[i];

					if (value < 0)
					{
						temp[i] = value;
						continue;
					}

					float weightedSum = 0;
					vec2 gradient(0);
					for (int dy=-1; dy<=1; dy++)
					for (int dx=-1; dx<=1; dx++)
					{
						// blur

						int ay = (y+dy) & grid_mask;
						int ax = (x+dx) & grid_mask;
						int ai = ax + ay * grid_res;
						float aValue = map[ai];
						if (aValue < 0) aValue = 0; // set obstacles to zero fluid

						float weight =	(dx==0 ? 2.0f : 1.0f) * 
										(dy==0 ? 2.0f : 1.0f) / 16.f;
						weightedSum += weight * aValue;

						// gradient (from the old state)

						vec2 gDir (dx,dy);
						float repellingAValue = (aValue > 0 ? aValue : injectAmount * -0.5f); // hack to prevent velocity pointing towards walls; would be better to make sure injection avoids leaking
						float vDiff = repellingAValue - value;
						gDir *= vDiff;
						gradient += gDir * weight;
					}
					temp[i] = weightedSum * (1.f - evaporate);
					gradientField[i] = gradient;
#else
				for (int y=0; y<grid_res; y++)
				for (int x=0; x<grid_res; x++)
				{
					int i = x + y*grid_res;
					float value = map[i];

					if (value < 0)
					{
						temp[i] = value;
						continue;
					}

					float aValueL = max(0.f, map[((x-1)&grid_mask) + y * grid_res]);
					float aValueR = max(0.f, map[((x+1)&grid_mask) + y * grid_res]);
					float aValueT = max(0.f, map[x + ((y-1)&grid_mask) * grid_res]);
					float aValueB = max(0.f, map[x + ((y+1)&grid_mask) * grid_res]);
					float weightedSum = (aValueL + aValueR + aValueT + aValueB) * .25f;
					temp[i] = weightedSum * (1.f - evaporate);
					
					vec2 gradient = vec2(
						(aValueR - aValueL),
						(aValueB - aValueT));
					gradientField[i] = gradient;
#endif
				}
				std::swap(temp, map);


				// enemy movement

				constexpr float enemySpeed = playerSpeed * .75f;
				vec2 playerPosition = agents[0].position;
				for (int i=1; i<(int)agents.size(); i++)
				{
					Agent &enemy = agents[i];

					float tc[2] = {enemy.position[0] * float(grid_res), enemy.position[1] * float(grid_res)};
					int gridI[2]; float gridF[2];
					SetupSampleCoords2 (gridI, gridF, tc);
					vec2 vel = Sample2 (gradientField, map, gridI, gridF);
					
					float mag = vel.Length();
					if (mag > 1.0e-12f)
						vel *= enemySpeed / mag;
					else
						vel = vec2(0);
					enemy.position += vel;

					float distToPlayer = vec2(enemy.position - playerPosition).Length();
					if (distToPlayer < delta)
						init = 1; // game over
				}

			}

			// visualize

			auto VisCell = [&](int x, int y, float value)
			{
				sVec3 w[4];
				for (int n=0; n<4; n++)
				{
					int u = x + (n&1);
					int v = y + ((n>>1)&1);
					w[n] = sVec3(	(float(u)) / float(grid_res),
									(float(v)) / float(grid_res), -0.01f);
				}
				sVec3 color (1);
				if (value>=0)
				{
					color[0] = max(0.f,min(1.f, value));
					color[1] = max(0.f,min(1.f, value * 10.f));
					color[2] = max(0.f,min(.5f, value * 100.f));
				}
				RenderTriangle(w[1], w[2], w[0], color[0], color[1], color[2]);
				RenderTriangle(w[2], w[1], w[3], color[0], color[1], color[2]);
			};

			static bool visLevel = 1; ImGui::Checkbox("visLevel", &visLevel);
			if (visLevel)
			{
				for (int y=0; y<grid_res; y++)
				for (int x=0; x<grid_res; x++)
				{
					int i = x + y*grid_res;
					float value = map[i];
					VisCell(x, y, value);
				}
			}
			static bool visGradient = 1; ImGui::Checkbox("visGradient", &visGradient);
			if (visGradient)
			{
				for (int y=0; y<grid_res; y++)
				for (int x=0; x<grid_res; x++)
				{
					int i = x + y*grid_res;
					vec2 gradient = gradientField[i];
					float mag = gradient.Length();
					if (mag > 1.0e-12f)
						gradient *= delta / mag;
					else
						gradient = vec2(0);
					RenderArrow(vec2((float(x)+.5f)/float(grid_res), (float(y)+.5f)/float(grid_res)), gradient, delta/4, 1,0,0); 
				}
			}

			static bool visAgents = 1; ImGui::Checkbox("visAgents", &visAgents);
			if (visAgents)
			{
				for (int i=0; i<(int)agents.size(); i++)
				{
					Agent &agent = agents[i];
					sVec3 color;
					color[0] = float ((i+0)*3 * 1664525) *.01f; color[0] -= floor(color[0]);
					color[1] = float ((i+1)*3 * 1664525) *.01f; color[1] -= floor(color[1]);
					color[2] = float ((i+2)*3 * 1664525) *.01f; color[2] -= floor(color[2]);
					RenderPoint (agent.position, color[0], color[1], color[2]); 
				}
			}

		}

Should be easy to port. In case you're unfamiliar with C++, this code is called once per frame. Static variables keep in memory between the calls and are initialized only once. This way i have level setup and game loop all in one place to keep it simple.

All my higher quality ideas lead to leaking problems. They would work of all obstacle walls would be thicker, so i kept the code in but disabled with #if 0. I don't think it would be worth it. Current cheaper methods are faster and have nice quality too.

One problem is, if there are singularities in the field, enemies get stuck there for a moment. But then they move on and find the player. Otherwise it seems to work robustly.

Now i think it's still a bit different from the video. I assume they do not blur, but instead do region growing, taking min / max value from neighborhood and increasing / decreasing it. So it's likely an integer approach, even if they use floating point numbers. My method gives smoother / more continuous results i guess, as it's based on scalar fluid energy and gradual flow. Cost shoudl be pretty equal for both, but behavior differs slightly.

I don't use any max or min energy (like clamping between -1 and 1) - would not know what's that good for. Instead i use negative energy to mark obstacles but there is no maximum. To prevent growing energy to infinity, i scale the whole energy map down each step, see ‘evaporate’ constant.

I think it would work to update only a subset of cells each step, e.g.using a checkerboard or dithering pattern to make it faster while still good enough. Multi level grids would also work to find paths faster, but could not handle narrow corridors which would collapse to be solid at lower resolution.

Advertisement

JoeJ said:
One problem is, if there are singularities in the field, enemies get stuck there for a moment. But then they move on and find the player. Otherwise it seems to work robustly.

Interesting to mention a potential fix would be using a real fluid simulation enforcing an incompressible vector field for velocity. This would also prevent the enemies to meet at a single point and so replace a need for collision detection.

freddynewtondev said:
trying it like this: but nothing changes

Not sure how it is intended to work. Seemingly you have an adjacency list for each cell, but note this is not needed if you use a regular grid, where neighbors can be found simply by modifying lookup coordinates, like i did. (Adjacency list means resolving indirections and potential cache misses. Nav mesh would need it, but grid does not.)

Thanks a lot! I try to archive some similar. I need something like -x to x value on the map because there are enemies and allies and you the ally agents need to know where are them other allies and the enemies so thats why

Looks like bilinear is the right approach. I try to do it either - never did it before

None

I need something like -x to x value on the map because there are enemies and allies and you the ally agents need to know where are them other allies and the enemies so thats why

That's the big limitation here.

What would work easily is to use multiple dimensions, e.g. a vec4 instead a single float.

If you had 4 fractions, each agent could find its closest alley, and the closest enemy of the other 3 fractions.
But only the closest one, not the second closest or all of them.

Advertisement

So now I'm a bit stuck on the problem - I don't understand in your code exactly how you put the filter. I'm currently stuck in the Set Influence part in my codesnippet after rewriting everything into arrays.

   public void MapFlooding()
    {
        foreach (Imap map in imaps)
        {
            // Set Enemy and Ally Inf
            foreach (Transform t in map.allyPosition) map.setWorldPosition(t.position.x, t.position.y, 1f);
            foreach (Transform t in map.enemyPosition) map.setWorldPosition(t.position.x, t.position.y, -1f);

            // Iterate through map
            for (int x = 0; x < map._map.GetLength(0); x++)
            {
                for (int y = 0; y < map._map.GetLength(1); y++)
                {
                    // Check if point is walkable
                    if (map._map[x, y].y == 0)
                    {
                        // Set Inf
                        map._map[x, y].x = UnityEngine.Random.Range(-1f, 1f);

                        // Set Max and Min Inf
                        maxInf = Mathf.Max(maxInf, map._map[x, y].x);
                        minInf = Mathf.Min(minInf, map._map[x, y].x);

                        // Momentum
                        map._map[x, y].x = Mathf.Lerp(map._map[x, y].x, map._map[x, y].x < 0 ? minInf : maxInf, Momentum);
                    }
                }
            }
        }
    }

There is also a Propargation part from the video but I'm really not very knowledgeable in c++.

void InfluenceMap::propagateInfluence()
{
	for (size_t i = 0; i < m_pAreaGraph->getSize(); ++i)
	{
		float maxInf = 0.0f;
		Connections& connections = m_pAreaGraph->getEdgeIndices(i);

		for (Connections::const_iterator it = connections.begin(); it != connections.end(); ++it)
		{
			const AreaConnection& c = m_pAreaGraph->getEdge(*it);
			float inf = m_Influences[c.neighbor] * expf(-c.dist * m_fDecay);
			maxInf = std::max(inf, maxInf);
		}

	m_Influences = lerp(m_Influences, maxInf, m_fMomentum);
	}
}

https://www.gamedev.net/tutorials/programming/artificial-intelligence/the-core-mechanics-of-influence-mapping-r2799/

None

So now I'm a bit stuck on the problem - I don't understand in your code exactly how you put the filter. I'm currently stuck in the Set Influence part in my codesnippet after rewriting everything into arrays.

Reading a bit the influence mapping tutorial i see this is tackling some AI problems, while my given example really only is about path finding. I have no experience with advanced AI in games, so i'd hope somebody else can help further.

However, i guess the situation is confusing to anybody, so you might want to describe more precisely what goals you have, and what does not work as expected.

Maybe one source of confusion is the tutorial itself. Did not read it fully, but it proposes different methods to represent the world: Grid, areas with adjacency, and waypoint network. I do not know what's the difference between the latter two (both seems to be adjacency graphs like navmesh), and i do not know if multiple representations are necessary, or if using either grid or some graph is meant?
Seems there is some kind of predictions going on, maybe the lower resolution graphs are used to propagate some information ahead? If so, this is more then than just path finding.

I don't understand in your code exactly how you put the filter.

It's quite a bit of code, so the overall function is like so:

Initialize the grid with walls and zeros otherwise. Create some agents at random positions.

For each frame:

  • The player agent injects energy to the grid at its current position and moves from input (Using bilinear filter)
  • Diffuse that energy over the grid - each cell iterates it's neighbor cells to calculate an average (blur)
  • Calculate gradient for each grid cell from difference of neighboring cells energy - this gives gradual direction vector per cell which points towards increasing energy.
  • Enemy agents sample the gradient vectors from grid and move in that direction to find the player (bilinear filter again)
  • Multiply grid energy with a number like 0.99 to limit energy and repeat the process for the next frame.

    Notice, the bilinear filter is not really important. It would also work if you inject/sample only to/from the single cell the agent is inside of.

    Also notice in my code there is no max of neighbors influence, but an average to diffuse the energy. If i would use max and increase result by one, the result would tell me an approximate distance of cells to some local maxima. In contrast, my energy does not tell about distance directly, but only gives gradual direction towards the closest maxima in the scalar energy field.

Ahhhh okay - thanks @joej for your time and help

My goal is simply to spread the influence of different agents as said in the video and then later additive influence, such as where a grenade is. Pathfinding etc. is not even that important. But at the moment it's more important to blur the influence in the game world in order to use Anhand in the decision-making process, depending on the influence.

So I tried it with a Meanblur but somehow it only blurs downwards as you can see in the gif.

Momentum = 0.1f; Delay = 0.99f; Update Frequenzy = 0.079 sec; - Some Extremes to visualize it better



I got here to show you the memory part which is very important later on for the Decision Finding:


At the moment I go for the Influence Map like this:

  1. set the influence where the allied units are to 1 and for the enemies to -1 so to speak like the inject you meant. Just with a Foreach loop and depending on which cell is closest, it will be set to the value. (Later I'll try to solve this a bit more performant with modulo).
  2. i iterate over each cell and take its neighboring cells including itself its influence (x value) and divide it by its number. so just take its average value.
  3. Then I multiply it by a "delay" to cause a drop in influence.

I don't quite understand the momentum yet, but I tried to reproduce it as in the article. And what do you mean with multiply the map gradient with 0.99? So you mean I should skip the “momentum”?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA i am not sure if I'm okay with that what i got or if it is trash

public void MapFlooding()
    {
        foreach (Imap map in imaps)
        {
            // Set Enemy and Ally Inf
            foreach (Transform t in map.allyPosition) map.setWorldPosition(t.position.x, t.position.y, 1f);
            foreach (Transform t in map.enemyPosition) map.setWorldPosition(t.position.x, t.position.y, -1f);

            // Iterate through map
            for (int x = 0; x < map._map.GetLength(0); x++)
            {
                for (int y = 0; y < map._map.GetLength(1); y++)
                {
                    // Check if point is walkable
                    if (map._map[x, y].y == 0)
                    {
                        float inf = 0;
                        float count = 0;

                        // mean blur
                        for (int kx = -1; kx < 2; kx++)
                        {
                            for (int ky = -1; ky < 2; ky++)
                            {
                                if (y + ky < 0 || y + ky > map._map.GetLength(1) - 1 || x + kx < 0 || x + kx > map._map.GetLength(0) - 1) continue;
                                else
                                {
                                    count++;
                                    inf += map._map[x + kx, y + ky].x;
                                }

                            }
                        }

                        // Set Inf
                        if (count != 0)
                            map._map[x, y].x = (inf / count) * Decay;

                        // Set Max and Min Inf
                        maxInf = Mathf.Max(maxInf, map._map[x, y].x);
                        minInf = Mathf.Min(minInf, map._map[x, y].x);

                        // Momentum
                        if (map._map[x, y].x < 0) Mathf.Lerp(map._map[x, y].x, minInf, Momentum);
                        else Mathf.Lerp(map._map[x, y].x, maxInf, Momentum);

                        // map._map[x, y].x = Mathf.Lerp(map._map[x, y].x, map._map[x, y].x < 0 ? minInf : maxInf, Momentum);
                    }
                }
            }
        }
    }

None

freddynewtondev said:
So I tried it with a Meanblur but somehow it only blurs downwards as you can see in the gif.

Looks all fine to me. I can't see it only goes downwards. Code looks correct too.

Oh, now i think i know what you mean. You use just one grid for source and destination of the blur. Thus, as the algorithm iterates the grid, it will happen (e.g.) top left cells are already blurred and updated, while right/bottom cells are still at the state from the previous frame. This adds some form of directional bias, which might be a problem in practice or not. To prevent it, store results in a temporary grid and swap after the whole grid is processed.

I don't quite understand the momentum yet, but I tried to reproduce it as in the article. And what do you mean with multiply the map gradient with 0.99? So you mean I should skip the “momentum”?

My 0.99 is the same as your decay.

‘Momentum’ seems to be a exponential average: newState = oldState * (1-m) + newResult * m, with m being a constant between 0 and 1.

I don't know what it is good for, other than smoothing influence at the cost of slowing down the diffusion. I guess you could just remove it - blur should give enough smoothing already. But it gives some control over response latency of behavior maybe.

maxInf = Mathf.Max(maxInf, map._map[x, y].x);

minInf = Mathf.Min(minInf, map._map[x, y].x);

I would avoid this clamping if possible. Reason is: You can no longer calculate a gradient within areas where all neighbors are clamped - gradient vector becomes zero and path finding is no more possible, if you need that later.

This topic is closed to new replies.

Advertisement