From 49751d51d3e43830e0ab795f4f8bea27fb08c040 Mon Sep 17 00:00:00 2001 From: Grimsace Date: Mon, 9 Feb 2026 09:41:35 -0600 Subject: [PATCH] flattened building plots on the heightmap, replaced from previous commit, fixed and optimized --- buildings.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++++--- main.go | 43 +++++++++++++++------- 2 files changed, 124 insertions(+), 20 deletions(-) diff --git a/buildings.go b/buildings.go index 347fd86..24fc471 100644 --- a/buildings.go +++ b/buildings.go @@ -6,13 +6,14 @@ import ( "math" "math/rand" "sort" + "sync" ) // GenerateBuildings creates and places buildings on the map. -func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, roadPixels, allWaterPixels []image.Point, seed int64) []image.Point { +func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, roadPixels, allWaterPixels []image.Point, seed int64) ([][]image.Point, []image.Point) { // Early exit if no buildings are to be generated if settings.NumBuildings == 0 { - return nil + return nil, nil } // Initialize random number generator @@ -32,7 +33,8 @@ func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, r // Initialize building data structures isBuilding := make(map[image.Point]bool) - var buildingPixels []image.Point + var buildings [][]image.Point + var allBuildingPixels []image.Point var anchorPoints []image.Point // Determine anchor points for building placement @@ -52,7 +54,7 @@ func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, r // Early exit if no anchor points are available if len(anchorPoints) == 0 { - return nil + return nil, nil } // Sort anchor points for deterministic placement sort.Slice(anchorPoints, func(i, j int) bool { @@ -137,14 +139,15 @@ func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, r for _, p := range pixels { img.Set(p.X, p.Y, buildingColor) isBuilding[p] = true - buildingPixels = append(buildingPixels, p) + allBuildingPixels = append(allBuildingPixels, p) } + buildings = append(buildings, pixels) buildingsPlaced++ break // Move to the next building } } } - return buildingPixels + return buildings, allBuildingPixels } // getProceduralBuildingPixels generates a complex building by connecting multiple shapes. @@ -433,3 +436,89 @@ func getBuildingPixels(center image.Point, size float64, shape string, isWater, } return pixels, true } + +// isPixelInSlice checks if a pixel is already in a slice of pixels. +func isPixelInSlice(pixel image.Point, pixelSlice []image.Point) bool { + for _, p := range pixelSlice { + if p == pixel { + return true + } + } + return false +} + +// FlattenBuildingAreas flattens the terrain under buildings and blends the surrounding area. +func FlattenBuildingAreas(heightMap *image.RGBA, buildings [][]image.Point, width, height int) *image.RGBA { + if len(buildings) == 0 { + return heightMap + } + + // Create a copy of the heightmap to avoid modifying the original during processing. + newHeightMap := image.NewRGBA(heightMap.Bounds()) + copy(newHeightMap.Pix, heightMap.Pix) + + // Process each building in parallel. + var wg sync.WaitGroup + for _, building := range buildings { + wg.Add(1) + go func(building []image.Point) { + defer wg.Done() + + // Calculate the average height of the building area. + var totalGray uint32 + for _, p := range building { + gray, _, _, _ := newHeightMap.At(p.X, p.Y).RGBA() + totalGray += gray + } + avgGray := uint8(totalGray / uint32(len(building)) >> 8) + avgColor := color.RGBA{R: avgGray, G: avgGray, B: avgGray, A: 255} + + // Flatten the building area. + for _, p := range building { + newHeightMap.Set(p.X, p.Y, avgColor) + } + + // Create a buffer around the building. + buffer := make([]image.Point, 0) + for _, p := range building { + for y := p.Y - 5; y <= p.Y+5; y++ { + for x := p.X - 5; x <= p.X+5; x++ { + if x >= 0 && x < width && y >= 0 && y < height { + candidate := image.Point{X: x, Y: y} + if !isPixelInSlice(candidate, building) && !isPixelInSlice(candidate, buffer) { + buffer = append(buffer, candidate) + } + } + } + } + } + + // Blend the buffer. + for _, p := range buffer { + originalColor := heightMap.At(p.X, p.Y) + _, g, _, _ := originalColor.RGBA() + + minDist := math.MaxFloat64 + for _, bp := range building { + dist := math.Sqrt(math.Pow(float64(p.X-bp.X), 2) + math.Pow(float64(p.Y-bp.Y), 2)) + if dist < minDist { + minDist = dist + } + } + + // Blend based on distance. + blendFactor := minDist / 5.0 + if blendFactor > 1.0 { + blendFactor = 1.0 + } + + newGray := uint8(float64(avgGray)*(1.0-blendFactor) + float64(g>>8)*blendFactor) + newColor := color.RGBA{R: newGray, G: newGray, B: newGray, A: 255} + newHeightMap.Set(p.X, p.Y, newColor) + } + }(building) + } + wg.Wait() + + return newHeightMap +} diff --git a/main.go b/main.go index 7473d4b..071982c 100644 --- a/main.go +++ b/main.go @@ -55,6 +55,7 @@ func main() { var riverPixels []image.Point var treePixels []image.Point var buildingPixels []image.Point + var buildings [][]image.Point var roadPixels []image.Point var bridgePixels []image.Point @@ -159,7 +160,7 @@ func main() { } // Step 5: Generating Buildings - buildingPixels = GenerateBuildings(finalImage, settings.Width, settings.Height, settings, roadPixels, allWaterPixels, seedProvider.Next()) + buildings, buildingPixels = GenerateBuildings(finalImage, settings.Width, settings.Height, settings, roadPixels, allWaterPixels, seedProvider.Next()) // Step 6: Generating Trees treePixels = GenerateTrees(finalImage, allWaterPixels, roadPixels, buildingPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next()) @@ -167,10 +168,13 @@ func main() { // Step 7: Darkening Water Areas darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels) - // Step 8: Flattening Road Areas - flattenedHeightmap := FlattenRoadAreas(darkenedHeightmap, roadPixels) + // Step 8: Flattening Building Areas + flattenedBuildingHeightmap := FlattenBuildingAreas(darkenedHeightmap.(*image.RGBA), buildings, settings.Width, settings.Height) - // Step 9: Applying Roughness + // Step 9: Flattening Road Areas + flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, roadPixels) + + // Step 10: Applying Roughness compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness) heightmapImg.Image = compositeImg @@ -385,7 +389,7 @@ func main() { showSaveDialog(w, heightmapImg.Image, settings) }) exportMasksBtn := widget.NewButton("Export Masks", func() { - showMasksSaveDialog(w, canvasImg.Image, heightmapImg.Image, settings, lakes, riverPixels, treePixels, roadPixels, bridgePixels, buildingPixels) + showMasksSaveDialog(w, canvasImg.Image, heightmapImg.Image, settings, lakes, riverPixels, treePixels, roadPixels, bridgePixels, buildingPixels, buildings) }) // Main generation button and logic generateBtn = widget.NewButton("Generate", func() { @@ -401,7 +405,7 @@ func main() { }) }() // Set up progress bar - steps := 9 + steps := 10 currentStep := 0 seedProvider := NewSeedProvider(settings.Seed) @@ -462,7 +466,7 @@ func main() { progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Buildings", currentStep, steps)) progressBar.SetValue(float64(currentStep) / float64(steps)) }) - buildingPixels = GenerateBuildings(finalImage, settings.Width, settings.Height, settings, roadPixels, allWaterPixels, seedProvider.Next()) + buildings, buildingPixels = GenerateBuildings(finalImage, settings.Width, settings.Height, settings, roadPixels, allWaterPixels, seedProvider.Next()) // Step 6: Darkening Water Areas currentStep++ @@ -471,9 +475,18 @@ func main() { progressBar.SetValue(float64(currentStep) / float64(steps)) }) darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels) - flattenedHeightmap := FlattenRoadAreas(darkenedHeightmap, roadPixels) - // Step 7: Applying Roughness + // Step 7: Flattening Building Areas + currentStep++ + fyne.Do(func() { + progressBar.SetText(fmt.Sprintf("Step %d/%d: Flattening Building Areas", currentStep, steps)) + progressBar.SetValue(float64(currentStep) / float64(steps)) + }) + flattenedBuildingHeightmap := FlattenBuildingAreas(darkenedHeightmap.(*image.RGBA), buildings, settings.Width, settings.Height) + + flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, roadPixels) + + // Step 8: Applying Roughness currentStep++ fyne.Do(func() { progressBar.SetText(fmt.Sprintf("Step %d/%d: Applying Roughness", currentStep, steps)) @@ -481,7 +494,7 @@ func main() { }) compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness) - // Step 8: Generating Trees + // Step 9: Generating Trees currentStep++ fyne.Do(func() { progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Trees", currentStep, steps)) @@ -489,7 +502,7 @@ func main() { }) treePixels = GenerateTrees(finalImage, allWaterPixels, roadPixels, buildingPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next()) - // Step 9: Finalizing Images + // Step 10: Finalizing Images currentStep++ fyne.Do(func() { progressBar.SetText(fmt.Sprintf("Step %d/%d: Finalizing Images", currentStep, steps)) @@ -918,7 +931,7 @@ func getImageData(img image.Image, format string) (*bytes.Buffer, error) { } // showMasksSaveDialog displays a dialog for saving the generated masks. -func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg image.Image, settings *Settings, lakes [][]image.Point, riverPixels, treePixels, roadPixels, bridgePixels, buildingPixels []image.Point) { +func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg image.Image, settings *Settings, lakes [][]image.Point, riverPixels, treePixels, roadPixels, bridgePixels, buildingPixels []image.Point, buildings [][]image.Point) { // Create UI elements for the save dialog fileNameEntry := widget.NewEntry() fileNameEntry.SetPlaceHolder("masks_folder") @@ -990,8 +1003,10 @@ func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg image.Image, s bridgeMask.SetGray(p.X, p.Y, color.Gray{Y: 255}) } buildingMask := image.NewGray(bounds) - for _, p := range buildingPixels { - buildingMask.SetGray(p.X, p.Y, color.Gray{Y: 255}) + for _, building := range buildings { + for _, p := range building { + buildingMask.SetGray(p.X, p.Y, color.Gray{Y: 255}) + } } imagesToSave := map[string]image.Image{