public static void Process(MapMagic.CoordRect rect, Chunk.Results results, GeneratorsAsset gens, Chunk.Size terrainSize, Func <float, bool> stop = null) { if (stop != null && stop(0)) { return; } Noise noise = new Noise(12345, permutationCount: 128); //to pick objects based on biome //preparing output Dictionary <Transform, List <ObjectPool.Transition> > transitions = new Dictionary <Transform, List <ObjectPool.Transition> >(); //find all of the biome masks - they will be used to determine object probability List <TupleSet <MadMapsObjectOutput, Matrix> > allGensMasks = new List <TupleSet <MadMapsObjectOutput, Matrix> >(); foreach (MadMapsObjectOutput gen in gens.GeneratorsOfType <MadMapsObjectOutput>(onlyEnabled: true, checkBiomes: true)) { Matrix biomeMask = null; if (gen.biome != null) { object biomeMaskObj = gen.biome.mask.GetObject(results); if (biomeMaskObj == null) { continue; //adding nothing if biome has no mask } biomeMask = (Matrix)biomeMaskObj; if (biomeMask == null) { continue; } if (biomeMask.IsEmpty()) { continue; //optimizing empty biomes } } allGensMasks.Add(new TupleSet <MadMapsObjectOutput, Matrix>(gen, biomeMask)); } int allGensMasksCount = allGensMasks.Count; //biome rect to find array pos faster MapMagic.CoordRect biomeRect = new MapMagic.CoordRect(); for (int g = 0; g < allGensMasksCount; g++) { if (allGensMasks[g].item2 != null) { biomeRect = allGensMasks[g].item2.rect; break; } } //prepare biome mask values stack to re-use it to find per-coord biome float[] biomeVals = new float[allGensMasksCount]; //+1 for not using any object at all //iterating all gens for (int g = 0; g < allGensMasksCount; g++) { MadMapsObjectOutput gen = allGensMasks[g].item1; //iterating in layers for (int b = 0; b < gen.baseLayers.Length; b++) { if (stop != null && stop(0)) { return; //checking stop before reading output } Layer layer = gen.baseLayers[b]; if (!layer.prefab) { continue; } //loading objects from input SpatialHash hash = (SpatialHash)gen.baseLayers[b].input.GetObject(results); if (hash == null) { continue; } //finding/creating proper transitions list List <ObjectPool.Transition> transitionsList; if (!transitions.ContainsKey(layer.prefab)) { transitionsList = new List <ObjectPool.Transition>(); transitions.Add(layer.prefab, transitionsList); } else { transitionsList = transitions[layer.prefab]; } //filling instances (no need to check/add key in multidict) foreach (SpatialObject obj in hash.AllObjs()) { //blend biomes - calling continue if improper biome if (biomeBlendType == BiomeBlendType.Sharp) { float biomeVal = 1; if (allGensMasks[g].item2 != null) { biomeVal = allGensMasks[g].item2[obj.pos]; } if (biomeVal < 0.5f) { continue; } } else if (biomeBlendType == BiomeBlendType.AdditiveRandom) { float biomeVal = 1; if (allGensMasks[g].item2 != null) { biomeVal = allGensMasks[g].item2[obj.pos]; } float rnd = noise.Random((int)obj.pos.x, (int)obj.pos.y); if (biomeVal > 0.5f) { rnd = 1 - rnd; } if (biomeVal < rnd) { continue; } } else if (biomeBlendType == BiomeBlendType.NormalizedRandom) { //filling biome masks values int pos = biomeRect.GetPos(obj.pos); for (int i = 0; i < allGensMasksCount; i++) { if (allGensMasks[i].item2 != null) { biomeVals[i] = allGensMasks[i].item2.array[pos]; } else { biomeVals[i] = 1; } } //calculate normalized sum float sum = 0; for (int i = 0; i < biomeVals.Length; i++) { sum += biomeVals[i]; } if (sum > 1) //note that if sum is <1 usedBiomeNum can exceed total number of biomes - it means that none object is used here { for (int i = 0; i < biomeVals.Length; i++) { biomeVals[i] = biomeVals[i] / sum; } } //finding used biome num float rnd = noise.Random((int)obj.pos.x, (int)obj.pos.y); int usedBiomeNum = biomeVals.Length; //none biome by default sum = 0; for (int i = 0; i < biomeVals.Length; i++) { sum += biomeVals[i]; if (sum > rnd) { usedBiomeNum = i; break; } } //disable object using biome mask if (usedBiomeNum != g) { continue; } } //scale mode is applied a bit later //flooring float terrainHeight = 0; //if (layer.relativeHeight && results.heights != null) //if checbox enabled and heights exist (at least one height generator is in the graph) // terrainHeight = results.heights.GetInterpolated(obj.pos.x, obj.pos.y); //if (terrainHeight > 1) terrainHeight = 1; //world-space object position Vector3 position = new Vector3( (obj.pos.x) / hash.size * terrainSize.dimensions, // relative (0-1) position * terrain dimension (obj.height + terrainHeight) * terrainSize.height, (obj.pos.y) / hash.size * terrainSize.dimensions); //rotation + taking terrain normal Quaternion rotation; float objRotation = layer.rotate ? obj.rotation % 360 : 0; if (layer.takeTerrainNormal) { Vector3 terrainNormal = GetTerrainNormal(obj.pos.x, obj.pos.y, results.heights, terrainSize.height, terrainSize.pixelSize); Vector3 sideVector = new Vector3(Mathf.Sin((obj.rotation + 90) * Mathf.Deg2Rad), 0, Mathf.Cos((obj.rotation + 90) * Mathf.Deg2Rad)); Vector3 frontVector = Vector3.Cross(sideVector, terrainNormal); rotation = Quaternion.LookRotation(frontVector, terrainNormal); } else { rotation = objRotation.EulerToQuat(); } //scale + biome scale mode Vector3 scale = layer.scale ? new Vector3(layer.scaleY ? 1 : obj.size, obj.size, layer.scaleY ? 1 : obj.size) : Vector3.one; if (biomeBlendType == BiomeBlendType.Scale) { float biomeVal = 1; if (allGensMasks[g].item2 != null) { biomeVal = allGensMasks[g].item2[obj.pos]; } if (biomeVal < 0.001f) { continue; } scale *= biomeVal; } transitionsList.Add(new ObjectPool.Transition() { pos = position, rotation = rotation, scale = scale }); } } } //queue apply if (stop != null && stop(0)) { return; } results.apply.CheckAdd(typeof(MadMapsObjectOutput), transitions, replace: true); }
public static void Process(MapMagic.CoordRect rect, Chunk.Results results, GeneratorsAsset gens, Chunk.Size terrainSize, Func <float, bool> stop = null) { if (stop != null && stop(0)) { return; } Noise noise = new Noise(12345, permutationCount: 128); //to pick objects based on biome List <TreeInstance> instancesList = new List <TreeInstance>(); List <TreePrototype> prototypesList = new List <TreePrototype>(); //find all of the biome masks - they will be used to determine object probability List <TupleSet <MadMapsTreeOutput, Matrix> > allGensMasks = new List <TupleSet <MadMapsTreeOutput, Matrix> >(); foreach (MadMapsTreeOutput gen in gens.GeneratorsOfType <MadMapsTreeOutput>(onlyEnabled: true, checkBiomes: true)) { Matrix biomeMask = null; if (gen.biome != null) { object biomeMaskObj = gen.biome.mask.GetObject(results); if (biomeMaskObj == null) { continue; //adding nothing if biome has no mask } biomeMask = (Matrix)biomeMaskObj; if (biomeMask == null) { continue; } if (biomeMask.IsEmpty()) { continue; //optimizing empty biomes } } allGensMasks.Add(new TupleSet <MadMapsTreeOutput, Matrix>(gen, biomeMask)); } int allGensMasksCount = allGensMasks.Count; //biome rect to find array pos faster MapMagic.CoordRect biomeRect = new MapMagic.CoordRect(); for (int g = 0; g < allGensMasksCount; g++) { if (allGensMasks[g].item2 != null) { biomeRect = allGensMasks[g].item2.rect; break; } } //prepare biome mask values stack to re-use it to find per-coord biome float[] biomeVals = new float[allGensMasksCount]; //+1 for not using any object at all //iterating all gens for (int g = 0; g < allGensMasksCount; g++) { MadMapsTreeOutput gen = allGensMasks[g].item1; //iterating in layers for (int b = 0; b < gen.baseLayers.Length; b++) { if (stop != null && stop(0)) { return; //checking stop before reading output } Layer layer = gen.baseLayers[b]; // if (layer.prefab == null) continue; //loading objects from input SpatialHash hash = (SpatialHash)gen.baseLayers[b].input.GetObject(results); if (hash == null) { continue; } //adding prototype // if (layer.prefab == null) continue; TreePrototype prototype = new TreePrototype() { prefab = layer.prefab, bendFactor = layer.bendFactor }; prototypesList.Add(prototype); int prototypeNum = prototypesList.Count - 1; //filling instances (no need to check/add key in multidict) foreach (SpatialObject obj in hash.AllObjs()) { //blend biomes - calling continue if improper biome if (biomeBlendType == BiomeBlendType.Sharp) { float biomeVal = 1; if (allGensMasks[g].item2 != null) { biomeVal = allGensMasks[g].item2[obj.pos]; } if (biomeVal < 0.5f) { continue; } } else if (biomeBlendType == BiomeBlendType.AdditiveRandom) { float biomeVal = 1; if (allGensMasks[g].item2 != null) { biomeVal = allGensMasks[g].item2[obj.pos]; } float rnd = noise.Random((int)obj.pos.x, (int)obj.pos.y); if (biomeVal > 0.5f) { rnd = 1 - rnd; } if (biomeVal < rnd) { continue; } } else if (biomeBlendType == BiomeBlendType.NormalizedRandom) { //filling biome masks values int pos = biomeRect.GetPos(obj.pos); for (int i = 0; i < allGensMasksCount; i++) { if (allGensMasks[i].item2 != null) { biomeVals[i] = allGensMasks[i].item2.array[pos]; } else { biomeVals[i] = 1; } } //calculate normalized sum float sum = 0; for (int i = 0; i < biomeVals.Length; i++) { sum += biomeVals[i]; } if (sum > 1) //note that if sum is <1 usedBiomeNum can exceed total number of biomes - it means that none object is used here { for (int i = 0; i < biomeVals.Length; i++) { biomeVals[i] = biomeVals[i] / sum; } } //finding used biome num float rnd = noise.Random((int)obj.pos.x, (int)obj.pos.y); int usedBiomeNum = biomeVals.Length; //none biome by default sum = 0; for (int i = 0; i < biomeVals.Length; i++) { sum += biomeVals[i]; if (sum > rnd) { usedBiomeNum = i; break; } } //disable object using biome mask if (usedBiomeNum != g) { continue; } } //scale mode is applied a bit later //flooring float terrainHeight = 0; if (layer.relativeHeight && results.heights != null) //if checbox enabled and heights exist (at least one height generator is in the graph) { terrainHeight = results.heights.GetInterpolated(obj.pos.x, obj.pos.y); } if (terrainHeight > 1) { terrainHeight = 1; } TreeInstance tree = new TreeInstance(); tree.position = new Vector3( (obj.pos.x - hash.offset.x) / hash.size, obj.height + terrainHeight, (obj.pos.y - hash.offset.y) / hash.size); tree.rotation = layer.rotate ? obj.rotation % 360 : 0; tree.widthScale = layer.widthScale ? obj.size : 1; tree.heightScale = layer.heightScale ? obj.size : 1; tree.prototypeIndex = prototypeNum; tree.color = layer.color; tree.lightmapColor = layer.color; if (biomeBlendType == BiomeBlendType.Scale) { float biomeVal = 1; if (allGensMasks[g].item2 != null) { biomeVal = allGensMasks[g].item2[obj.pos]; } if (biomeVal < 0.001f) { continue; } tree.widthScale *= biomeVal; tree.heightScale *= biomeVal; } instancesList.Add(tree); } } } //setting output if (stop != null && stop(0)) { return; } if (instancesList.Count == 0 && prototypesList.Count == 0) { return; //empty, process is caused by height change } TupleSet <TreeInstance[], TreePrototype[]> treesTuple = new TupleSet <TreeInstance[], TreePrototype[]>(instancesList.ToArray(), prototypesList.ToArray()); results.apply.CheckAdd(typeof(MadMapsTreeOutput), treesTuple, replace: true); }
public static void Process(CoordRect rect, Chunk.Results results, GeneratorsAsset gens, Chunk.Size terrainSize, Func<float,bool> stop = null) { #if __MEGASPLAT__ if (stop!=null && stop(0)) return; //using the first texture list for all MegaSplatTextureList textureList = null; bool smoothFallof = false; float clusterScale = 0.05f; foreach (MegaSplatOutput gen in gens.GeneratorsOfType<MegaSplatOutput>(onlyEnabled: true, checkBiomes: true)) { if (gen.textureList != null) textureList = gen.textureList; smoothFallof = gen.smoothFallof; clusterScale = gen.clusterNoiseScale; } //creating color arrays MegaSplatData result = new MegaSplatData(); result.control = new Color[MapMagic.instance.resolution * MapMagic.instance.resolution]; result.param = new Color[MapMagic.instance.resolution * MapMagic.instance.resolution]; //creating all and special layers/biomes lists List<Layer> allLayers = new List<Layer>(); //all layers count = gen num * layers num in each gen (excluding empty biomes, matrices, etc) List<Matrix> allMatrices = new List<Matrix>(); List<Matrix> allBiomeMasks = new List<Matrix>(); List<Matrix> specialWetnessMatrices = new List<Matrix>(); //special count = number of generators (excluding empty biomes only) List<Matrix> specialPuddlesMatrices = new List<Matrix>(); List<Matrix> specialDampeningMatrices = new List<Matrix>(); List<Matrix> specialBiomeMasks = new List<Matrix>(); //filling all layers/biomes foreach (MegaSplatOutput gen in gens.GeneratorsOfType<MegaSplatOutput>(onlyEnabled: true, checkBiomes: true)) { gen.textureList = textureList; //loading biome matrix Matrix biomeMask = null; if (gen.biome != null) { object biomeMaskObj = gen.biome.mask.GetObject(results); if (biomeMaskObj == null) continue; //adding nothing if biome has no mask biomeMask = (Matrix)biomeMaskObj; if (biomeMask == null) continue; if (biomeMask.IsEmpty()) continue; //optimizing empty biomes } for (int i = 0; i < gen.baseLayers.Length; i++) { //reading output directly Output output = gen.baseLayers[i].output; if (stop!=null && stop(0)) return; //checking stop before reading output if (!results.results.ContainsKey(output)) continue; Matrix matrix = (Matrix)results.results[output]; if (matrix.IsEmpty()) continue; if (i >= textureList.clusters.Length) { Debug.LogError("Cluster out of range"); continue; } //adding to lists allLayers.Add(gen.baseLayers[i]); allMatrices.Add(matrix); allBiomeMasks.Add(gen.biome == null ? null : biomeMask); } //adding special object wetnessObj = gen.wetnessIn.GetObject(results); specialWetnessMatrices.Add( wetnessObj!=null? (Matrix)wetnessObj : null ); object puddlesObj = gen.puddlesIn.GetObject(results); specialPuddlesMatrices.Add( puddlesObj!=null? (Matrix)puddlesObj : null ); object dampeingObj = gen.displaceDampenIn.GetObject(results); specialDampeningMatrices.Add( dampeingObj!=null? (Matrix)dampeingObj : null ); specialBiomeMasks.Add(gen.biome == null ? null : biomeMask); } //if no texture list found in any of generators - returning if (textureList == null || allLayers.Count==0) return; //processing int allLayersCount = allLayers.Count; int specialCount = specialWetnessMatrices.Count; for (int x = 0; x<rect.size.x; x++) for (int z = 0; z<rect.size.z; z++) { int pos = rect.GetPos(x + rect.offset.x, z + rect.offset.z); // doesn't use height, normal, but I'm not sure how to get that here.. Vector3 worldPos = new Vector3( 1f * (x+rect.offset.x) / MapMagic.instance.resolution * rect.size.x, 0, 1f * (z+rect.offset.z) / MapMagic.instance.resolution * rect.size.z); float heightRatio = results.heights!=null? results.heights.array[pos] : 0.5f; //0 is the bottom point, 1 is the maximum top Vector3 normal = new Vector3(0,1,0); // find highest two layers int botIdx = 0; int topIdx = 0; float botWeight = 0; float topWeight = 0; for (int i = 0; i<allLayersCount; i++) { float val = allMatrices[i].array[pos]; if (allBiomeMasks[i] != null) val *= allBiomeMasks[i].array[pos]; // really want world position, Normal, and height ratio for brushes, but for now, just use x/z.. if (val > botWeight) { topWeight = botWeight; topIdx = botIdx; botWeight = val; botIdx = i; } else if (val > topWeight) { topIdx = i; topWeight = val; } } //converting layer index to texture index topIdx = textureList.clusters[ allLayers[topIdx].index ].GetIndex(worldPos * clusterScale, normal, heightRatio); botIdx = textureList.clusters[ allLayers[botIdx].index ].GetIndex(worldPos * clusterScale, normal, heightRatio); //swapping indexes to make topIdx always on top if (botIdx > topIdx) { int tempIdx = topIdx; topIdx = botIdx; botIdx = tempIdx; float tempWeight = topWeight; topWeight = botWeight; botWeight = tempWeight; } //finding blend float totalWeight = topWeight + botWeight; if (totalWeight<0.01f) totalWeight = 0.01f; //Mathf.Max and Clamp are slow float blend = botWeight / totalWeight; if (blend>1) blend = 1; //adjusting blend curve if (smoothFallof) blend = (Mathf.Sqrt(blend) * (1-blend)) + blend*blend*blend; //Magic secret formula! Inverse to 3*x^2 - 2*x^3 //setting color result.control[pos] = new Color(botIdx / 255.0f, topIdx / 255.0f, 1.0f - blend, 1.0f); //params for (int i = 0; i<specialCount; i++) { float biomeVal = specialBiomeMasks[i]!=null? specialBiomeMasks[i].array[pos] : 1; if (specialWetnessMatrices[i]!=null) result.param[pos].b = specialWetnessMatrices[i].array[pos] * biomeVal; if (specialPuddlesMatrices[i]!=null) { result.param[pos].a = specialPuddlesMatrices[i].array[pos] * biomeVal; result.param[pos].r = 0.5f; result.param[pos].g = 0.5f; } if (specialDampeningMatrices[i]!=null) result.control[pos].a = specialDampeningMatrices[i].array[pos] * biomeVal; } } //pushing to apply if (stop!=null && stop(0)) return; results.apply.CheckAdd(typeof(MegaSplatOutput), result, replace: true); #endif }
public static new void Process(CoordRect rect, Chunk.Results results, GeneratorsAsset gens, Chunk.Size terrainSize, Func <float, bool> stop = null) { #if CTS_PRESENT if (stop != null && stop(0)) { return; } //gathering prototypes and matrices lists List <int> indexesList = new List <int>(); List <float> opacities = new List <float>(); List <Matrix> matrices = new List <Matrix>(); List <Matrix> biomeMasks = new List <Matrix>(); foreach (CTSOutput gen in gens.GeneratorsOfType <CTSOutput>(onlyEnabled: true, checkBiomes: true)) { //loading biome matrix Matrix biomeMask = null; if (gen.biome != null) { object biomeMaskObj = gen.biome.mask.GetObject(results); if (biomeMaskObj == null) { continue; //adding nothing if biome has no mask } biomeMask = (Matrix)biomeMaskObj; if (biomeMask == null) { continue; } if (biomeMask.IsEmpty()) { continue; //optimizing empty biomes } } for (int i = 0; i < gen.baseLayers.Length; i++) { //reading output directly Output output = gen.baseLayers[i].output; if (stop != null && stop(0)) { return; //checking stop before reading output } if (!results.results.ContainsKey(output)) { continue; } Matrix matrix = (Matrix)results.results[output]; matrix.Clamp01(); //adding to lists matrices.Add(matrix); biomeMasks.Add(gen.biome == null ? null : biomeMask); indexesList.Add(gen.baseLayers[i].index); opacities.Add(gen.baseLayers[i].opacity); } } //optimizing matrices list if they are not used //CTS always use 16 channels, so this optimization is useless (and does not work when layer order changed) //for (int i = matrices.Count - 1; i >= 0; i--) // if (opacities[i] < 0.001f || matrices[i].IsEmpty() || (biomeMasks[i] != null && biomeMasks[i].IsEmpty())) // { indexesList.RemoveAt(i); opacities.RemoveAt(i); matrices.RemoveAt(i); biomeMasks.RemoveAt(i); } //creating array float[,,] splats3D = new float[terrainSize.resolution, terrainSize.resolution, 16]; //TODO: use max index if (matrices.Count == 0) { results.apply.CheckAdd(typeof(CTSOutput), splats3D, replace: true); return; } //filling array if (stop != null && stop(0)) { return; } int numLayers = matrices.Count; int numPrototypes = splats3D.GetLength(2); int maxX = splats3D.GetLength(0); int maxZ = splats3D.GetLength(1); //MapMagic.instance.resolution should not be used because of possible lods //CoordRect rect = matrices[0].rect; float[] values = new float[numPrototypes]; //row, to avoid reading/writing 3d array (it is too slow) for (int x = 0; x < maxX; x++) { for (int z = 0; z < maxZ; z++) { int pos = rect.GetPos(x + rect.offset.x, z + rect.offset.z); float sum = 0; //clearing values for (int i = 0; i < numPrototypes; i++) { values[i] = 0; } //getting values for (int i = 0; i < numLayers; i++) { float val = matrices[i].array[pos]; if (biomeMasks[i] != null) { val *= biomeMasks[i].array[pos]; //if mask is not assigned biome was ignored, so only main outs with mask==null left here } if (val < 0) { val = 0; } if (val > 1) { val = 1; } sum += val; //normalizing: calculating sum values[indexesList[i]] += val; } //setting color for (int i = 0; i < numLayers; i++) { splats3D[z, x, i] = values[i] / sum; } } } //pushing to apply if (stop != null && stop(0)) { return; } results.apply.CheckAdd(typeof(CTSOutput), splats3D, replace: true); #endif }