/// <summary> /// Assigns the colour value based on the heatMap data to a range of pixels. /// </summary> /// <param name="heatmap"></param> /// <param name="startX"></param> /// <param name="startY"></param> /// <param name="width"></param> /// <param name="height"></param> void AssignVisualisationTextureColorValue(ref Heatmap heatmap, int startX, int startY, int width, int height) { // Assign the correct pixel value to each requested pixel. for (int x = startX; x < (startX + width); x++) { for (int y = startY; y < (startY + height); y++) { AssignHeatmapColorValue(ref heatmap, x, y); } } }
/// <summary> /// Flip the heatmap constraints. /// </summary> /// <param name="heatmapIndex"></param> /// <param name="heatmaps"></param> /// <returns></returns> Heatmap FlipHeatmapConstraints(int heatmapIndex, ref List <Heatmap> heatmaps) { Heatmap heatmap = heatmaps[heatmapIndex]; float temp = heatmap.higherValueLimit; heatmap.higherValueLimit = heatmap.lowerValueLimit; heatmap.lowerValueLimit = temp; return(heatmap); }
/// <summary> /// Genereates the correct type of Splatprototypes for the heatmap /// </summary> /// <param name="heatmap"></param> /// <returns></returns> SplatPrototype[] GenerateSplatPrototypes(Heatmap heatmap) { switch (heatmap.texSource) { case (TextureSource.Custom): return(GenerateSplatPrototypesCustom(heatmap)); default: return(GenerateSplatPrototypesDefault()); } }
/// <summary> /// No Interpolation, just extract the mapData we have (When DataPoint resolution matches the alphamapResolution this is the default). /// </summary> /// <param name="heatmap"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="width"></param> /// <param name="height"></param> /// <returns></returns> public float[,] InterpolationMethodNone(ref Heatmap heatmap, int x, int y, int width, int height) { float[,] heatmapValues = new float[width, height]; for (int i = x; i < x + width; i++) { for (int j = y; j < y + height; j++) { heatmapValues[i, j] = heatmap.heatmapDataPoints[i, j].value; } } return(heatmapValues); }
/// <summary> /// Chooses the correct method to assign the values to the Heatmap's data points. /// </summary> /// <param name="heatmap"></param> /// <param name="alphaMapResolution"></param> /// <param name="terrainObjectSize"></param> /// <param name="heightMap"></param> /// <param name="heightMapScale"></param> /// <param name="customDataList"></param> /// <param name="positionOffset"></param> void AssignMapData(Heatmap heatmap, int alphaMapResolution, Vector3 terrainObjectSize, float[,] heightMap, Vector3 heightMapScale, HeatmapNode[] customDataList, Vector3 positionOffset, int terrainHeightmapResolution) { switch (heatmap.dataType) { case (HeatmapData.Custom): CustomDataToHeatmapData(heatmap, customDataList, false, terrainObjectSize, alphaMapResolution, positionOffset); break; default: TerrainHeightToHeatmapData(heatmap, terrainObjectSize, alphaMapResolution, heightMap, heightMapScale, terrainHeightmapResolution); break; } }
/// <summary> /// Retrieves QPoints for interpolation. /// </summary> /// <param name="heatmap"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="alphaMapResolution"></param> /// <param name="incrementInterval"></param> /// <returns></returns> HeatmapDatum[,] GetQPoints(ref Heatmap heatmap, int x, int y, int alphaMapResolution, float incrementInterval) { HeatmapDatum[,] qPoints = new HeatmapDatum[2, 2]; int xGridSquare = Mathf.FloorToInt(((float)x / incrementInterval)); int yGridSquare = Mathf.FloorToInt(((float)y / incrementInterval)); qPoints[0, 0] = heatmap.heatmapDataPoints[xGridSquare, yGridSquare]; qPoints[0, 1] = heatmap.heatmapDataPoints[xGridSquare, yGridSquare + 1]; qPoints[1, 0] = heatmap.heatmapDataPoints[xGridSquare + 1, yGridSquare]; qPoints[1, 1] = heatmap.heatmapDataPoints[xGridSquare + 1, yGridSquare + 1]; return(qPoints); }
/// <summary> /// Returns the closest data point to texture coordinate. /// </summary> /// <param name="heatmap"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="alphaMapResolution"></param> /// <param name="incrementInterval"></param> /// <returns></returns> HeatmapDatum GetClosestDataPoint(ref Heatmap heatmap, int x, int y, int alphaMapResolution, float incrementInterval) { HeatmapDatum closetDataPoint; int xGridSquare = Mathf.RoundToInt(x / incrementInterval); int yGridSquare = Mathf.RoundToInt(y / incrementInterval); try { closetDataPoint = heatmap.heatmapDataPoints[xGridSquare, yGridSquare]; } catch (System.IndexOutOfRangeException) { return(null); } return(closetDataPoint); }
/// <summary> /// Chooses the correct interpolation method. /// </summary> /// <param name="heatmap"></param> /// <param name="alphaMapResolution"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="width"></param> /// <param name="height"></param> /// <returns></returns> float[,] InterpolateColorTextureValues(ref Heatmap heatmap, int alphaMapResolution, int x, int y, int width, int height) { // If the dataTexture Resoloution and the alphaMapResolution match then there is no need to interpolate the data. if (heatmap.heatmapResolution == alphaMapResolution) { return(InterpolationMethodNone(ref heatmap, alphaMapResolution)); } switch (heatmap.interpolationMode) { case InterpolationMode.NearestNeighbor: return(InterpolationMethodNearestNeighbor(ref heatmap, alphaMapResolution, x, y, width, height)); case InterpolationMode.Bilinear: return(InterpolationMethodBilinear(ref heatmap, alphaMapResolution, x, y, width, height)); default: return(InterpolationMethodNearestNeighbor(ref heatmap, alphaMapResolution, x, y, width, height)); } }
/// <summary> /// Finds the highest and lowest values and uses them as the constraints. /// </summary> /// <param name="heatmap"></param> /// <param name="flipConstraints"></param> void AutoConstrainValues(ref Heatmap heatmap) { float upperValue = heatmap.heatmapDataPoints[0, 0].value; float lowerValue = heatmap.heatmapDataPoints[0, 0].value; foreach (HeatmapDatum datum in heatmap.heatmapDataPoints) { if (datum.value > upperValue) { upperValue = datum.value; } if (datum.value < lowerValue) { lowerValue = datum.value; } } heatmap.higherValueLimit = heatmap.flipAutoConstrain ? lowerValue : upperValue; heatmap.lowerValueLimit = heatmap.flipAutoConstrain ? upperValue : lowerValue; }
/// <summary> /// Nearest neighbour interpolation. /// </summary> /// <param name="heatmap"></param> /// <param name="alphaMapResolution"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="width"></param> /// <param name="height"></param> /// <returns></returns> public float[,] InterpolationMethodNearestNeighbor(ref Heatmap heatmap, int alphaMapResolution, int x, int y, int width, int height) { float[,] heatmapValues = new float[width, height]; float incrementInterval = (alphaMapResolution / heatmap.heatmapResolution); for (int i = x; i < x + width; i++) { for (int j = y; j < y + height; j++) { heatmapValues[i, j] = this.GetClosestDataPoint(ref heatmap, i, j, alphaMapResolution, incrementInterval).value; if (float.IsNaN(heatmapValues[i, j])) { return(null); } } } return(heatmapValues); }
/// <summary> /// Generate heatmap data from the Terrain's height map. /// </summary> /// <param name="heatmap"></param> /// <param name="terrainObjectSize"></param> /// <param name="alphaMapResolution"></param> /// <param name="heightMap"></param> /// <param name="heightMapScale"></param> void TerrainHeightToHeatmapData(Heatmap heatmap, Vector3 terrainObjectSize, int alphaMapResolution, float[,] heightMap, Vector3 heightMapScale, int terrainHeightmapResolution) { HeatmapDatum[,] heatmapValues = new HeatmapDatum[heatmap.heatmapResolution + 1, heatmap.heatmapResolution + 1]; for (int x = 0; x <= heatmap.heatmapResolution; x++) { for (int y = 0; y <= heatmap.heatmapResolution; y++) { float resScaleFactorX = terrainObjectSize.x / heatmap.heatmapResolution; float resScaleFactorZ = terrainObjectSize.z / heatmap.heatmapResolution; Vector3 nodeLocation = new Vector3((int)(y * resScaleFactorZ / (terrainObjectSize.z / (terrainHeightmapResolution - 1))), 0.0f, (int)(x * resScaleFactorX / (terrainObjectSize.x / (terrainHeightmapResolution - 1)))); float nodeValue = heightMap[(int)nodeLocation.z, (int)nodeLocation.x] * heightMapScale.y; heatmapValues[x, y] = new HeatmapDatum(); heatmapValues[x, y].Init(nodeLocation, nodeValue, Coordinates.WorldToTerrainCoords(nodeLocation, terrainObjectSize, alphaMapResolution)); } } heatmap.heatmapDataPoints = heatmapValues; }
/// <summary> /// Perform Bi-Linear interpolation with fewer parameters. /// </summary> /// <param name="heatmap"></param> /// <param name="alphaMapResolution"></param> /// <returns></returns> public float[,] InterpolationMethodBilinear(ref Heatmap heatmap, int alphaMapResolution) { return(InterpolationMethodBilinear(ref heatmap, alphaMapResolution, 0, 0, alphaMapResolution, alphaMapResolution)); }
/// <summary> /// Perform Nearest-Neighbour interpolation with fewer parameters. /// </summary> /// <param name="heatmap"></param> /// <param name="alphaMapResolution"></param> /// <returns></returns> public float[,] InterpolationMethodNearestNeighbor(ref Heatmap heatmap, int alphaMapResolution) { return(InterpolationMethodNearestNeighbor(ref heatmap, alphaMapResolution, 0, 0, alphaMapResolution, alphaMapResolution)); }
/// <summary> /// Assigns the colour value based on the heatMap data to a given terrain Pixel. /// </summary> /// <param name="heatmap"></param> /// <param name="x"></param> /// <param name="y"></param> void AssignHeatmapColorValue(ref Heatmap heatmap, int x, int y) { // Calculate the value as a % of HottestValueThreshold. float value = heatmap.heatmapValues[x, y]; value += CalculateLowerUpperThresholdValueOffset(heatmap.lowerValueLimit); // Offset the value to ensure it's positive. value += heatmap.higherValueLimit / heatmap.splatPrototypes.Length; // Calculate the value as a % of the range of values it could be. value = (value / (heatmap.higherValueLimit - heatmap.lowerValueLimit)); // Normalise the value so if the value is at HottestValueThreshold, that threshold is equal to the last index in heatMapSplatPrototypes. value = (value * heatmap.splatPrototypes.Length - 1); if (float.IsNaN(value)) { value = 0.0f; } // If the value is less than zero, then set the bottom layer to an opacity of 100% and all the other layers to 0% opacity. if (value < 0) { // Set the bottom layer to be 100% opaque. heatmap.alphaMapData[x, y, 0] = 1.0f; // Loop through all other layers setting their values to 0% opacity. for (int layerAboveZero = 1; layerAboveZero < heatmap.splatPrototypes.Length; layerAboveZero++) { heatmap.alphaMapData[x, y, layerAboveZero] = 0.0f; } return; } // If the value is above the index number of the highest layer. else if (value >= heatmap.splatPrototypes.Length - 1) { // Set the value fot the hottest layer to be 100% opaque. heatmap.alphaMapData[x, y, heatmap.splatPrototypes.Length - 1] = 1.0f; // Loop through all the other layers setting their values to 0% opacity. for (int layersBelowTop = (heatmap.splatPrototypes.Length - 2); layersBelowTop >= 0; layersBelowTop--) { heatmap.alphaMapData[x, y, layersBelowTop] = 0.0f; } return; } // If the value is within the range of the heatMap. else { // Loop through all the layers to find the layers where the blending should occur. for (int layernum = heatmap.splatPrototypes.Length - 1; layernum > 0; layernum--) { // If this condition is true, we need to blend these layers. if (value < layernum && value >= layernum - 1) { heatmap.alphaMapData[x, y, layernum] = value - (layernum - 1); heatmap.alphaMapData[x, y, layernum - 1] = 1.0f - (value - (layernum - 1)); // Loop through all layers above this layer and set to 0% opacity. for (int num = layernum + 1; num < heatmap.splatPrototypes.Length; num++) { heatmap.alphaMapData[x, y, num] = 0.0f; } // Loop through all layers below this layer and set to 0% opacity. for (int num = layernum - 2; num >= 0; num--) { heatmap.alphaMapData[x, y, num] = 0.0f; } // Return as correct value has been set for all layers. return; } } Debug.LogError("The color value of " + value + " could not bet set at location [" + x + "," + y + "]"); return; } }
/// <summary> /// Generate Heatmap data from custom data provided. /// </summary> /// <param name="heatmap"></param> /// <param name="customData"></param> /// <param name="addValuesOfIntersectingPoints"></param> /// <param name="terrainSize"></param> /// <param name="alphaMapResolution"></param> /// <param name="positionOffset"></param> void CustomDataToHeatmapData(Heatmap heatmap, HeatmapNode[] customData, bool addValuesOfIntersectingPoints, Vector3 terrainSize, int alphaMapResolution, Vector3 positionOffset) { HeatmapDatum[,] heatmapDataArray = new HeatmapDatum[heatmap.heatmapResolution + 1, heatmap.heatmapResolution + 1]; // Assign the resulting value of the alphaMapResolution divided by the dataTextureResolution to a variable, as this is used a lot. int alphaMapDividedByHeatmapResolution = (alphaMapResolution / heatmap.heatmapResolution); // Populate the newly created returnVal array with default data. for (int x = 0; x <= heatmap.heatmapResolution; x++) { for (int y = 0; y <= heatmap.heatmapResolution; y++) { heatmapDataArray[x, y] = new HeatmapDatum(); heatmapDataArray[x, y].value = heatmap.baseValue; heatmapDataArray[x, y].nodePosition = new Vector3((int)(x * alphaMapDividedByHeatmapResolution), 0.0f, (int)(y * alphaMapDividedByHeatmapResolution)); heatmapDataArray[x, y].terrainCoords = new Pair <int>((int)(x * alphaMapDividedByHeatmapResolution), (int)(y * alphaMapDividedByHeatmapResolution)); } } float incrementInterval = (alphaMapResolution / heatmap.heatmapResolution); //Assign the values of all the points we know to the correct position in the array. foreach (HeatmapNode customDatum in customData) { int arrayPositionX, arrayPositionY; int brushSize = customDatum.brushSize; float brushHardness = customDatum.brushHardness; float brushOpacity = customDatum.brushOpacity; // Ensure that the data is valid. brushOpacity = Mathf.Clamp(brushOpacity, 0.0f, 100.0f) / 100; brushHardness = Mathf.Clamp(brushHardness, 0.0f, 100.0f) / 100; // Find the correct position in the array based on the object's transform. Vector3 closetMapPointPosition = GetClosestDataPointPosition((customDatum.position - positionOffset), terrainSize, heatmap.heatmapResolution, alphaMapResolution, incrementInterval); Vector2 textureCoordianates = Coordinates.WorldToTerrainCoords(closetMapPointPosition, terrainSize, alphaMapResolution); // Using the dataTexture Resolution and the Terrain coordiantes, calculate the correct position in the array. arrayPositionX = (int)textureCoordianates.x / alphaMapDividedByHeatmapResolution; arrayPositionY = (int)textureCoordianates.y / alphaMapDividedByHeatmapResolution; // Calculate the correct value when taking the opacity of the brush into account. float value = customDatum.value * brushOpacity; if (!customDatum.overwriteOtherNodeValues) { if (arrayPositionX >= 0 && arrayPositionY >= 0 && arrayPositionX < heatmap.heatmapResolution && arrayPositionY < heatmap.heatmapResolution) { value = customDatum.value * brushOpacity + heatmapDataArray[arrayPositionX, arrayPositionY].value; } } // Using the terrain Coordinates, create the Datum that we're going to store in the Array. HeatmapDatum textureDatum = new HeatmapDatum(); textureDatum.Init(closetMapPointPosition, value, new Pair <int>((int)textureCoordianates.x, (int)textureCoordianates.y)); // Assign the VisualisationTextureDatum to the correct Index in the Array. if (arrayPositionX < heatmap.heatmapResolution && arrayPositionY < heatmap.heatmapResolution && arrayPositionX > 0 && arrayPositionY > 0) { heatmapDataArray[arrayPositionX, arrayPositionY] = textureDatum; } // If the brushSize is less than zero then we won't attempt to update the values of the surrounding nodes. if (brushSize > 0) { // Calculate how many steps outwards we need to take (and thus how many other nodes we need to update). int diameter = (int)(brushSize / alphaMapDividedByHeatmapResolution); // We must have at least 2 steps outwards to be able to define a brush. if (diameter > 0) { int radius = diameter / 2; int radiusSquared = radius * radius; for (int x = -radius; x < +radius; x++) { for (int y = -radius; y < +radius; y++) { int arryPosX = arrayPositionX + x; int arryPosY = arrayPositionY + y; if (arryPosX == arrayPositionX && arryPosY == arrayPositionY) { continue; } if (arryPosX >= 0 && arryPosY >= 0 && arryPosX <= heatmap.heatmapResolution && arryPosY <= heatmap.heatmapResolution) { int dx = arryPosX - arrayPositionX; int dy = arryPosY - arrayPositionY; float distanceSquared = (dx * dx) + (dy * dy); if (distanceSquared <= radiusSquared) { float brushHardnessModifier = radiusSquared - (distanceSquared * (1.0f - brushHardness)); brushHardnessModifier /= radiusSquared; if (!customDatum.overwriteOtherNodeValues) { heatmapDataArray[arryPosX, arryPosY].value += (brushHardnessModifier * customDatum.value) * brushOpacity; } else { heatmapDataArray[arryPosX, arryPosY].value = (brushHardnessModifier * customDatum.value) * brushOpacity; } } } } } } } } heatmap.heatmapDataPoints = heatmapDataArray; }