flattened building plots on the heightmap, replaced from previous commit, fixed and optimized

This commit is contained in:
Grimsace
2026-02-09 09:41:35 -06:00
parent 230ed5aa13
commit 49751d51d3
2 changed files with 124 additions and 20 deletions
+95 -6
View File
@@ -6,13 +6,14 @@ import (
"math" "math"
"math/rand" "math/rand"
"sort" "sort"
"sync"
) )
// GenerateBuildings creates and places buildings on the map. // 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 // Early exit if no buildings are to be generated
if settings.NumBuildings == 0 { if settings.NumBuildings == 0 {
return nil return nil, nil
} }
// Initialize random number generator // Initialize random number generator
@@ -32,7 +33,8 @@ func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, r
// Initialize building data structures // Initialize building data structures
isBuilding := make(map[image.Point]bool) isBuilding := make(map[image.Point]bool)
var buildingPixels []image.Point var buildings [][]image.Point
var allBuildingPixels []image.Point
var anchorPoints []image.Point var anchorPoints []image.Point
// Determine anchor points for building placement // 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 // Early exit if no anchor points are available
if len(anchorPoints) == 0 { if len(anchorPoints) == 0 {
return nil return nil, nil
} }
// Sort anchor points for deterministic placement // Sort anchor points for deterministic placement
sort.Slice(anchorPoints, func(i, j int) bool { 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 { for _, p := range pixels {
img.Set(p.X, p.Y, buildingColor) img.Set(p.X, p.Y, buildingColor)
isBuilding[p] = true isBuilding[p] = true
buildingPixels = append(buildingPixels, p) allBuildingPixels = append(allBuildingPixels, p)
} }
buildings = append(buildings, pixels)
buildingsPlaced++ buildingsPlaced++
break // Move to the next building break // Move to the next building
} }
} }
} }
return buildingPixels return buildings, allBuildingPixels
} }
// getProceduralBuildingPixels generates a complex building by connecting multiple shapes. // 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 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
}
+28 -13
View File
@@ -55,6 +55,7 @@ func main() {
var riverPixels []image.Point var riverPixels []image.Point
var treePixels []image.Point var treePixels []image.Point
var buildingPixels []image.Point var buildingPixels []image.Point
var buildings [][]image.Point
var roadPixels []image.Point var roadPixels []image.Point
var bridgePixels []image.Point var bridgePixels []image.Point
@@ -159,7 +160,7 @@ func main() {
} }
// Step 5: Generating Buildings // 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 // Step 6: Generating Trees
treePixels = GenerateTrees(finalImage, allWaterPixels, roadPixels, buildingPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next()) 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 // Step 7: Darkening Water Areas
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels) darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
// Step 8: Flattening Road Areas // Step 8: Flattening Building Areas
flattenedHeightmap := FlattenRoadAreas(darkenedHeightmap, roadPixels) 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) compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness)
heightmapImg.Image = compositeImg heightmapImg.Image = compositeImg
@@ -385,7 +389,7 @@ func main() {
showSaveDialog(w, heightmapImg.Image, settings) showSaveDialog(w, heightmapImg.Image, settings)
}) })
exportMasksBtn := widget.NewButton("Export Masks", func() { 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 // Main generation button and logic
generateBtn = widget.NewButton("Generate", func() { generateBtn = widget.NewButton("Generate", func() {
@@ -401,7 +405,7 @@ func main() {
}) })
}() }()
// Set up progress bar // Set up progress bar
steps := 9 steps := 10
currentStep := 0 currentStep := 0
seedProvider := NewSeedProvider(settings.Seed) seedProvider := NewSeedProvider(settings.Seed)
@@ -462,7 +466,7 @@ func main() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Buildings", currentStep, steps)) progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Buildings", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(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 // Step 6: Darkening Water Areas
currentStep++ currentStep++
@@ -471,9 +475,18 @@ func main() {
progressBar.SetValue(float64(currentStep) / float64(steps)) progressBar.SetValue(float64(currentStep) / float64(steps))
}) })
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels) 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++ currentStep++
fyne.Do(func() { fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Applying Roughness", currentStep, steps)) progressBar.SetText(fmt.Sprintf("Step %d/%d: Applying Roughness", currentStep, steps))
@@ -481,7 +494,7 @@ func main() {
}) })
compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness) compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness)
// Step 8: Generating Trees // Step 9: Generating Trees
currentStep++ currentStep++
fyne.Do(func() { fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Trees", currentStep, steps)) 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()) 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++ currentStep++
fyne.Do(func() { fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Finalizing Images", currentStep, steps)) 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. // 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 // Create UI elements for the save dialog
fileNameEntry := widget.NewEntry() fileNameEntry := widget.NewEntry()
fileNameEntry.SetPlaceHolder("masks_folder") fileNameEntry.SetPlaceHolder("masks_folder")
@@ -990,9 +1003,11 @@ func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg image.Image, s
bridgeMask.SetGray(p.X, p.Y, color.Gray{Y: 255}) bridgeMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
} }
buildingMask := image.NewGray(bounds) buildingMask := image.NewGray(bounds)
for _, p := range buildingPixels { for _, building := range buildings {
for _, p := range building {
buildingMask.SetGray(p.X, p.Y, color.Gray{Y: 255}) buildingMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
} }
}
imagesToSave := map[string]image.Image{ imagesToSave := map[string]image.Image{
"canvas." + imgFormat: canvasImg, "canvas." + imgFormat: canvasImg,