flattened building plots on the heightmap, replaced from previous commit, fixed and optimized
This commit is contained in:
+95
-6
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user