/// <summary> /// Creates splatmaps which are (by default) applied to terrain splat shaders. /// </summary> /// <param name="applySplats">When true, the generated splat is automatically applied to the terrain. /// Otherwise, it can be applied by calling ApplySplatmapsToShader</param> /// <param name="debug">When true, the generated alpha maps are saved to the disk. /// They're located at [Your project's root dir]/SplatImages/</param> /// <returns>Created alphamap textures</returns> public List <Texture2D> GenerateSplatmaps(bool applySplats = true) { //Ensure correct shader is set SetFirstPassShader(true); //Set amount of required maps List <Texture2D> maps = new List <Texture2D>(); for (int i = 0; i < Mathf.CeilToInt(SplatSettings.Count / 4f); i++) { maps.Add(new Texture2D(AlphaMapResolution, AlphaMapResolution)); } //Sample weights and fill in textures for (int x = 0; x < AlphaMapResolution; x++) { for (int y = 0; y < AlphaMapResolution; y++) { MeshSampler.MeshSample sample = Sampler.SampleAt(y / (float)AlphaMapResolution, x / (float)AlphaMapResolution); AddWeightsToTextures(CalculateWeights(sample), ref maps, y, x); } } if (TerraDebug.WRITE_SPLAT_TEXTURES) { TerrainTile tile = TerrainObject.GetComponent <TerrainTile>(); string tileName = tile != null ? "Tile[" + tile.Position.x + "_" + tile.Position.y + "]" : "Tile[0_0]"; string folderPath = Application.dataPath + "/SplatImages/"; if (!Directory.Exists(folderPath)) { Directory.CreateDirectory(folderPath); } for (var i = 0; i < maps.Count; i++) { byte[] bytes = maps[i].EncodeToPNG(); string name = "Splat" + i + "_" + tileName + ".png"; File.WriteAllBytes(folderPath + name, bytes); } } //Apply set pixel values to textures maps.ForEach(t => t.Apply()); if (applySplats) { ApplySplatmapsToShaders(maps); } return(maps); }
/// <summary> /// First creates a poisson grid based on the passed density. /// Positions are then filtered based on the passed object placement /// type taking into account height and angle constraints. /// /// Unlike the <c>GetFilteredGrid(ObjectPlacementType, float)</c> method /// this method samples from the passed Mesh rather than pulling /// mesh information from TerraSettings. /// </summary> /// <param name="m">Mesh to sample height and angle values from</param> /// <param name="type">object placement type to sample</param> /// <returns>List of vectors within the grid and sample constraints</returns> public List <Vector3> GetFilteredGrid(Mesh m, ObjectPlacementType type) { MeshSampler sampler = new MeshSampler(m, Settings.MeshResolution); List <Vector2> grid = GetPoissonGrid(type.Spread / 10); List <Vector3> toAdd = new List <Vector3>(); foreach (Vector2 pos in grid) { MeshSampler.MeshSample sample = sampler.SampleAt(pos.x, pos.y); if (type.ShouldPlaceAt(sample.Height, sample.Angle)) { Vector3 newPos = new Vector3(pos.x, sample.Height, pos.y); toAdd.Add(newPos); } } return(toAdd); }
/// <summary> /// First creates a poisson grid based on the passed density. /// Positions are then filtered based on the passed object placement /// type taking into account height and angle constraints. /// /// Unlike the <c>GetFilteredGrid(ObjectPlacementType, float)</c> method /// this method samples from the passed Mesh rather than pulling /// mesh information from TerraSettings. /// </summary> /// <param name="m">Mesh to sample height and angle values from</param> /// <param name="objectPlacementData">object placement type to sample</param> /// <returns>List of vectors within the grid and sample constraints</returns> public List <Vector3> GetFilteredGrid(Mesh m, ObjectPlacementData objectPlacementData) { MeshSampler sampler = /**new MeshSampler(m, Settings.Generator.MeshResolution);*/ new MeshSampler(m, 128); //TODO Resolution from TerraSettings List <Vector2> grid = GetPoissonGrid(objectPlacementData.Spread / 10); List <Vector3> toAdd = new List <Vector3>(); foreach (Vector2 pos in grid) { MeshSampler.MeshSample sample = sampler.SampleAt(pos.x, pos.y); if (objectPlacementData.ShouldPlaceAt(sample.Height, sample.Angle)) { Vector3 newPos = new Vector3(pos.x, sample.Height, pos.y); toAdd.Add(newPos); } } return(toAdd); }
/// <summary> /// Calculates the weights that can be used to create a splatmap /// based on the passed sample and splats. /// </summary> /// <param name="sample">Sample to base calculation on</param> /// <param name="splat">Splat setting to base calculation on</param> /// <returns>Weight values in the same order of the </returns> float[] CalculateWeights(MeshSampler.MeshSample sample) { float height = sample.Height; float angle = sample.Angle; float[] weights = new float[SplatSettings.Count]; var orderMap = new Dictionary <PlacementType, int>() { { PlacementType.ElevationRange, 0 }, { PlacementType.Angle, 1 } }; List <SplatSetting> ordered = SplatSettings .OrderBy(s => orderMap[s.PlacementType]) //Order elevation before angle //.OrderBy(s => s.MinRange) //Order lower ranges //.OrderBy(s => s.IsMinHeight) //Order min height first .ToList(); for (int i = 0; i < SplatSettings.Count; i++) { SplatSetting splat = ordered[i]; float min = splat.IsMinHeight ? float.MinValue : splat.MinRange; float max = splat.IsMaxHeight ? float.MaxValue : splat.MaxRange; switch (splat.PlacementType) { case PlacementType.Angle: if (angle > splat.AngleMin && angle < splat.AngleMax) { float factor = Mathf.Clamp01(((angle - splat.AngleMin)) / splat.Blend); weights[i] = factor; } break; case PlacementType.ElevationRange: if (height > min && height < max) { if (i > 0) //Can blend up { float factor = Mathf.Clamp01((splat.Blend - (height - min)) / splat.Blend); weights[i - 1] = factor; weights[i] = 1 - factor; } else { weights[i] = 1f; } } break; } } //Normalize weights float sum = weights.Sum(); for (var i = 0; i < weights.Length; i++) { weights[i] /= sum; } return(weights); }