// Drops nature flats based on random chance scaled by simple rules public static void LayoutNatureBillboards(DaggerfallTerrain dfTerrain, DaggerfallBillboardBatch dfBillboardBatch, float terrainScale) { const float maxSteepness = 50f; // 50 const float chanceOnDirt = 0.2f; // 0.2 const float chanceOnGrass = 0.9f; // 0.4 const float chanceOnStone = 0.05f; // 0.05 // Get terrain Terrain terrain = dfTerrain.gameObject.GetComponent <Terrain>(); if (!terrain) { return; } // Get terrain data TerrainData terrainData = terrain.terrainData; if (!terrainData) { return; } // Remove exiting billboards dfBillboardBatch.Clear(); // Seed random with terrain key UnityEngine.Random.seed = MakeTerrainKey(dfTerrain.MapPixelX, dfTerrain.MapPixelY); // Just layout some random flats spread evenly across entire map pixel area // Flats are aligned with tiles, max 127x127 in billboard batch Vector2 tilePos = Vector2.zero; float scale = terrainData.heightmapScale.x; int dim = TerrainHelper.terrainTileDim - 1; for (int y = 0; y < dim; y++) { for (int x = 0; x < dim; x++) { // Reject based on steepness float steepness = terrainData.GetSteepness((float)x / dim, (float)y / dim); if (steepness > maxSteepness) { continue; } // Reject if inside location rect // Rect is expanded slightly to give extra clearance around locations tilePos.x = x; tilePos.y = y; const int natureClearance = 4; Rect rect = dfTerrain.MapData.locationRect; if (rect.x > 0 && rect.y > 0) { rect.xMin -= natureClearance; rect.xMin += natureClearance; rect.yMin -= natureClearance; rect.yMax += natureClearance; if (rect.Contains(tilePos)) { continue; } } // Chance scaled based on map pixel height // This tends to produce sparser lowlands and denser highlands // Adjust or remove clamp range to influence nature generation float elevationScale = (dfTerrain.MapData.worldHeight / 128f); elevationScale = Mathf.Clamp(elevationScale, 0.4f, 1.0f); // Chance scaled by base climate type float climateScale = 1.0f; DFLocation.ClimateSettings climate = MapsFile.GetWorldClimateSettings(dfTerrain.MapData.worldClimate); switch (climate.ClimateType) { case DFLocation.ClimateBaseType.Desert: // Just lower desert for now climateScale = 0.25f; break; } // Chance also determined by tile type WorldSample sample = TerrainHelper.GetSample(ref dfTerrain.MapData.samples, x, y); if (sample.record == 1) { // Dirt if (UnityEngine.Random.Range(0f, 1f) > chanceOnDirt * elevationScale * climateScale) { continue; } } else if (sample.record == 2) { // Grass if (UnityEngine.Random.Range(0f, 1f) > chanceOnGrass * elevationScale * climateScale) { continue; } } else if (sample.record == 3) { // Stone if (UnityEngine.Random.Range(0f, 1f) > chanceOnStone * elevationScale * climateScale) { continue; } } else { // Anything else continue; } // Sample height and position billboard Vector3 pos = new Vector3(x * scale, 0, y * scale); float height = terrain.SampleHeight(pos + terrain.transform.position); pos.y = height; // Reject if too close to water float beachLine = DaggerfallUnity.Instance.TerrainSampler.BeachElevation * terrainScale; if (height < beachLine) { continue; } // Add to batch int record = UnityEngine.Random.Range(1, 32); dfBillboardBatch.AddItem(record, pos); } } // Apply new batch dfBillboardBatch.Apply(); }