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/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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user