basic building generation
This commit is contained in:
+172
@@ -0,0 +1,172 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, roadPixels, allWaterPixels []image.Point, seed int64) []image.Point {
|
||||||
|
if settings.NumBuildings == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
randSrc := rand.New(rand.NewSource(seed))
|
||||||
|
buildingColor := color.RGBA{R: 128, G: 128, B: 128, A: 255} // Gray color for buildings
|
||||||
|
|
||||||
|
isWater := make(map[image.Point]bool)
|
||||||
|
for _, p := range allWaterPixels {
|
||||||
|
isWater[p] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
isRoad := make(map[image.Point]bool)
|
||||||
|
for _, p := range roadPixels {
|
||||||
|
isRoad[p] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
isBuilding := make(map[image.Point]bool)
|
||||||
|
var buildingPixels []image.Point
|
||||||
|
var anchorPoints []image.Point
|
||||||
|
if len(roadPixels) > 0 {
|
||||||
|
anchorPoints = roadPixels
|
||||||
|
} else {
|
||||||
|
// If no roads, use all land pixels as anchors
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
p := image.Point{X: x, Y: y}
|
||||||
|
if !isWater[p] {
|
||||||
|
anchorPoints = append(anchorPoints, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(anchorPoints) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Sort anchor points to have a deterministic order if needed, although we are selecting randomly
|
||||||
|
sort.Slice(anchorPoints, func(i, j int) bool {
|
||||||
|
if anchorPoints[i].Y != anchorPoints[j].Y {
|
||||||
|
return anchorPoints[i].Y < anchorPoints[j].Y
|
||||||
|
}
|
||||||
|
return anchorPoints[i].X < anchorPoints[j].X
|
||||||
|
})
|
||||||
|
landPoints := make([]image.Point, 0, width*height)
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
p := image.Point{X: x, Y: y}
|
||||||
|
if !isWater[p] && !isRoad[p] {
|
||||||
|
landPoints = append(landPoints, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildingsPlaced := 0
|
||||||
|
searchTries := 100 // Number of attempts to find a spot for a building
|
||||||
|
maxPlacementAttempts := settings.NumBuildings * 5 // To prevent infinite loops
|
||||||
|
|
||||||
|
for buildingsPlaced < settings.NumBuildings && maxPlacementAttempts > 0 {
|
||||||
|
maxPlacementAttempts--
|
||||||
|
|
||||||
|
var anchor image.Point
|
||||||
|
if randSrc.Float64() > settings.BuildingDistribution/100.0 {
|
||||||
|
// Place near roads
|
||||||
|
anchor = anchorPoints[randSrc.Intn(len(anchorPoints))]
|
||||||
|
} else {
|
||||||
|
// Place randomly on land
|
||||||
|
if len(landPoints) == 0 {
|
||||||
|
continue // No land to place buildings on
|
||||||
|
}
|
||||||
|
anchor = landPoints[randSrc.Intn(len(landPoints))]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < searchTries; i++ {
|
||||||
|
searchRadius := float64(i) * 2.0 // Search in expanding circles
|
||||||
|
angle := randSrc.Float64() * 2 * math.Pi
|
||||||
|
dist := searchRadius * randSrc.Float64()
|
||||||
|
center := image.Point{
|
||||||
|
X: anchor.X + int(dist*math.Cos(angle)),
|
||||||
|
Y: anchor.Y + int(dist*math.Sin(angle)),
|
||||||
|
}
|
||||||
|
if settings.BuildingDistribution == 100 {
|
||||||
|
center = image.Point{
|
||||||
|
X: randSrc.Intn(width),
|
||||||
|
Y: randSrc.Intn(height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if center.X < 0 || center.Y < 0 || center.X >= width || center.Y >= height {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
size := settings.MinBuildingSize + randSrc.Float64()*(settings.MaxBuildingSize-settings.MinBuildingSize)
|
||||||
|
pixels, ok := getBuildingPixels(center, size, settings.BuildingShape, isWater, isRoad, isBuilding, width, height, randSrc)
|
||||||
|
if ok {
|
||||||
|
for _, p := range pixels {
|
||||||
|
img.Set(p.X, p.Y, buildingColor)
|
||||||
|
isBuilding[p] = true
|
||||||
|
buildingPixels = append(buildingPixels, p)
|
||||||
|
}
|
||||||
|
buildingsPlaced++
|
||||||
|
break // Found a spot, move to next building
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buildingPixels
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBuildingPixels(center image.Point, size float64, shape string, isWater, isRoad, isBuilding map[image.Point]bool, width, height int, randSrc *rand.Rand) ([]image.Point, bool) {
|
||||||
|
var pixels []image.Point
|
||||||
|
var halfSize = int(size / 2)
|
||||||
|
|
||||||
|
switch shape {
|
||||||
|
case "squares":
|
||||||
|
for y := center.Y - halfSize; y <= center.Y+halfSize; y++ {
|
||||||
|
for x := center.X - halfSize; x <= center.X+halfSize; x++ {
|
||||||
|
p := image.Point{X: x, Y: y}
|
||||||
|
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || isWater[p] || isRoad[p] || isBuilding[p] {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
pixels = append(pixels, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "circles":
|
||||||
|
r2 := (size / 2) * (size / 2)
|
||||||
|
for y := center.Y - halfSize; y <= center.Y+halfSize; y++ {
|
||||||
|
for x := center.X - halfSize; x <= center.X+halfSize; x++ {
|
||||||
|
dx, dy := float64(x-center.X), float64(y-center.Y)
|
||||||
|
if dx*dx+dy*dy <= r2 {
|
||||||
|
p := image.Point{X: x, Y: y}
|
||||||
|
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || isWater[p] || isRoad[p] || isBuilding[p] {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
pixels = append(pixels, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "rectangles":
|
||||||
|
longSide := size
|
||||||
|
shortSide := randSrc.Float64()*(size-float64(halfSize)) + float64(halfSize)
|
||||||
|
var w, h int
|
||||||
|
if randSrc.Intn(2) == 0 {
|
||||||
|
w, h = int(longSide), int(shortSide)
|
||||||
|
} else {
|
||||||
|
w, h = int(shortSide), int(longSide)
|
||||||
|
}
|
||||||
|
halfW, halfH := w/2, h/2
|
||||||
|
|
||||||
|
for y := center.Y - halfH; y <= center.Y+halfH; y++ {
|
||||||
|
for x := center.X - halfW; x <= center.X+halfW; x++ {
|
||||||
|
p := image.Point{X: x, Y: y}
|
||||||
|
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || isWater[p] || isRoad[p] || isBuilding[p] {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
pixels = append(pixels, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pixels) == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return pixels, true
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
///okie dokie now let's add a major feature: building generation. We should create a new tab for buildings in the program and create a new file buildings.go for the generation logic. I'd like there to be several settings for the building generation. First is the number of buildings, which should number from 0 to 10000. Secondly is the minimum and maximum building size, which should exactly mirror how tree size is determined, with the exception that it's not the diameter of a circle, but instead the crictical dimension of a shape (see below). Next is distribution, ranging from 0% to 100%. At 0% distribution buildings will be placed directly next to roads and each other. At 100% distribution they will be scattered randomly around the map. We need a setting for building shape represented as a dropdown. The first setting will be "squares" (where each building is just a square, with the critical dimension being side length) then "circles" (critical dimension diameter) then "rectangles" (critical dimension longest side length, side lengths are determined randomly from min and max with the longest side length being equal to or shorter then the max and the shortest stide length being above or equal to the minimum size). No part of a building should be placed on water. No part of a building should be placed on a road. We should generate trees after buildings and not allow the center of a tree to be placed on a building.
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
@@ -51,6 +52,7 @@ func main() {
|
|||||||
var lakes [][]image.Point
|
var lakes [][]image.Point
|
||||||
var riverPixels []image.Point
|
var riverPixels []image.Point
|
||||||
var treePixels []image.Point
|
var treePixels []image.Point
|
||||||
|
var buildingPixels []image.Point
|
||||||
var roadPixels []image.Point
|
var roadPixels []image.Point
|
||||||
var bridgePixels []image.Point
|
var bridgePixels []image.Point
|
||||||
|
|
||||||
@@ -145,7 +147,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
treePixels = GenerateTrees(finalImage, allWaterPixels, roadPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next())
|
buildingPixels = GenerateBuildings(finalImage, settings.Width, settings.Height, settings, roadPixels, allWaterPixels, seedProvider.Next())
|
||||||
|
|
||||||
|
treePixels = GenerateTrees(finalImage, allWaterPixels, roadPixels, buildingPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next())
|
||||||
|
|
||||||
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
||||||
|
|
||||||
@@ -364,7 +368,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)
|
showMasksSaveDialog(w, canvasImg.Image, heightmapImg.Image, settings, lakes, riverPixels, treePixels, roadPixels, bridgePixels, buildingPixels)
|
||||||
})
|
})
|
||||||
|
|
||||||
generateBtn = widget.NewButton("Generate", func() {
|
generateBtn = widget.NewButton("Generate", func() {
|
||||||
@@ -378,7 +382,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
steps := 8
|
steps := 9
|
||||||
currentStep := 0
|
currentStep := 0
|
||||||
|
|
||||||
seedProvider := NewSeedProvider(settings.Seed)
|
seedProvider := NewSeedProvider(settings.Seed)
|
||||||
@@ -433,7 +437,15 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Darkening Water Areas
|
// Step 5: Generating Buildings
|
||||||
|
currentStep++
|
||||||
|
fyne.Do(func() {
|
||||||
|
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())
|
||||||
|
|
||||||
|
// Step 6: Darkening Water Areas
|
||||||
currentStep++
|
currentStep++
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressBar.SetText(fmt.Sprintf("Step %d/%d: Darkening Water Areas", currentStep, steps))
|
progressBar.SetText(fmt.Sprintf("Step %d/%d: Darkening Water Areas", currentStep, steps))
|
||||||
@@ -442,7 +454,7 @@ func main() {
|
|||||||
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
||||||
flattenedHeightmap := FlattenRoadAreas(darkenedHeightmap, roadPixels)
|
flattenedHeightmap := FlattenRoadAreas(darkenedHeightmap, roadPixels)
|
||||||
|
|
||||||
// Step 6: Applying Roughness
|
// Step 7: 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))
|
||||||
@@ -450,15 +462,15 @@ func main() {
|
|||||||
})
|
})
|
||||||
compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness)
|
compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness)
|
||||||
|
|
||||||
// Step 7: Generating Trees
|
// Step 8: 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))
|
||||||
progressBar.SetValue(float64(currentStep) / float64(steps))
|
progressBar.SetValue(float64(currentStep) / float64(steps))
|
||||||
})
|
})
|
||||||
treePixels = GenerateTrees(finalImage, allWaterPixels, roadPixels, 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 8: Finalizing Images
|
// Step 9: 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))
|
||||||
@@ -577,6 +589,66 @@ func main() {
|
|||||||
roadDistributionSlider,
|
roadDistributionSlider,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
numBuildingsLabel := widget.NewLabel(fmt.Sprintf("Number of Buildings: %d", settings.NumBuildings))
|
||||||
|
numBuildingsSlider := widget.NewSlider(0, 10000)
|
||||||
|
numBuildingsSlider.OnChanged = func(val float64) {
|
||||||
|
settings.NumBuildings = int(val)
|
||||||
|
numBuildingsLabel.SetText(fmt.Sprintf("Number of Buildings: %d", settings.NumBuildings))
|
||||||
|
}
|
||||||
|
numBuildingsSlider.SetValue(float64(settings.NumBuildings))
|
||||||
|
|
||||||
|
minBuildingSizeLabel := widget.NewLabel(fmt.Sprintf("Min Building Size: %.0fpx", settings.MinBuildingSize))
|
||||||
|
minBuildingSizeSlider := widget.NewSlider(1, 150)
|
||||||
|
maxBuildingSizeLabel := widget.NewLabel(fmt.Sprintf("Max Building Size: %.0fpx", settings.MaxBuildingSize))
|
||||||
|
maxBuildingSizeSlider := widget.NewSlider(1, 150)
|
||||||
|
|
||||||
|
minBuildingSizeSlider.OnChanged = func(val float64) {
|
||||||
|
settings.MinBuildingSize = val
|
||||||
|
if settings.MinBuildingSize > settings.MaxBuildingSize {
|
||||||
|
settings.MaxBuildingSize = settings.MinBuildingSize
|
||||||
|
maxBuildingSizeSlider.SetValue(settings.MaxBuildingSize)
|
||||||
|
}
|
||||||
|
minBuildingSizeLabel.SetText(fmt.Sprintf("Min Building Size: %.0fpx", settings.MinBuildingSize))
|
||||||
|
}
|
||||||
|
minBuildingSizeSlider.SetValue(settings.MinBuildingSize)
|
||||||
|
|
||||||
|
maxBuildingSizeSlider.OnChanged = func(val float64) {
|
||||||
|
settings.MaxBuildingSize = val
|
||||||
|
if settings.MaxBuildingSize < settings.MinBuildingSize {
|
||||||
|
settings.MinBuildingSize = settings.MaxBuildingSize
|
||||||
|
minBuildingSizeSlider.SetValue(settings.MinBuildingSize)
|
||||||
|
}
|
||||||
|
maxBuildingSizeLabel.SetText(fmt.Sprintf("Max Building Size: %.0fpx", settings.MaxBuildingSize))
|
||||||
|
}
|
||||||
|
maxBuildingSizeSlider.SetValue(settings.MaxBuildingSize)
|
||||||
|
|
||||||
|
buildingDistributionLabel := widget.NewLabel(fmt.Sprintf("Building Distribution: %.0f%%", settings.BuildingDistribution))
|
||||||
|
buildingDistributionSlider := widget.NewSlider(0, 100)
|
||||||
|
buildingDistributionSlider.OnChanged = func(val float64) {
|
||||||
|
settings.BuildingDistribution = val
|
||||||
|
buildingDistributionLabel.SetText(fmt.Sprintf("Building Distribution: %.0f%%", settings.BuildingDistribution))
|
||||||
|
}
|
||||||
|
buildingDistributionSlider.SetValue(settings.BuildingDistribution)
|
||||||
|
|
||||||
|
buildingShapeLabel := widget.NewLabel("Building Shape:")
|
||||||
|
buildingShapeSelect := widget.NewSelect([]string{"squares", "circles", "rectangles"}, func(s string) {
|
||||||
|
settings.BuildingShape = s
|
||||||
|
})
|
||||||
|
buildingShapeSelect.SetSelected(settings.BuildingShape)
|
||||||
|
|
||||||
|
buildingsTab := container.NewTabItem("Buildings", container.NewVBox(
|
||||||
|
numBuildingsLabel,
|
||||||
|
numBuildingsSlider,
|
||||||
|
minBuildingSizeLabel,
|
||||||
|
minBuildingSizeSlider,
|
||||||
|
maxBuildingSizeLabel,
|
||||||
|
maxBuildingSizeSlider,
|
||||||
|
buildingDistributionLabel,
|
||||||
|
buildingDistributionSlider,
|
||||||
|
buildingShapeLabel,
|
||||||
|
buildingShapeSelect,
|
||||||
|
))
|
||||||
|
|
||||||
imageTab := container.NewTabItem("Image", container.NewVBox(
|
imageTab := container.NewTabItem("Image", container.NewVBox(
|
||||||
widget.NewLabel("Width:"),
|
widget.NewLabel("Width:"),
|
||||||
widthEntry, widget.NewLabel("Height:"),
|
widthEntry, widget.NewLabel("Height:"),
|
||||||
@@ -598,6 +670,7 @@ func main() {
|
|||||||
terrainTab,
|
terrainTab,
|
||||||
waterTab,
|
waterTab,
|
||||||
roadsTab,
|
roadsTab,
|
||||||
|
buildingsTab,
|
||||||
)
|
)
|
||||||
|
|
||||||
left := container.NewVBox(
|
left := container.NewVBox(
|
||||||
@@ -633,7 +706,7 @@ func getImageData(img image.Image, format string) (*bytes.Buffer, error) {
|
|||||||
return buf, err
|
return buf, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg image.Image, settings *Settings, lakes [][]image.Point, riverPixels, treePixels, roadPixels, bridgePixels []image.Point) {
|
func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg image.Image, settings *Settings, lakes [][]image.Point, riverPixels, treePixels, roadPixels, bridgePixels, buildingPixels []image.Point) {
|
||||||
fileNameEntry := widget.NewEntry()
|
fileNameEntry := widget.NewEntry()
|
||||||
fileNameEntry.SetPlaceHolder("masks_folder")
|
fileNameEntry.SetPlaceHolder("masks_folder")
|
||||||
|
|
||||||
@@ -703,15 +776,20 @@ func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg image.Image, s
|
|||||||
for _, p := range bridgePixels {
|
for _, p := range bridgePixels {
|
||||||
bridgeMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
|
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})
|
||||||
|
}
|
||||||
|
|
||||||
imagesToSave := map[string]image.Image{
|
imagesToSave := map[string]image.Image{
|
||||||
"canvas." + imgFormat: canvasImg,
|
"canvas." + imgFormat: canvasImg,
|
||||||
"heightmap." + imgFormat: heightmapImg,
|
"heightmap." + imgFormat: heightmapImg,
|
||||||
"lakes_mask." + imgFormat: lakeMask,
|
"lakes_mask." + imgFormat: lakeMask,
|
||||||
"rivers_mask." + imgFormat: riverMask,
|
"rivers_mask." + imgFormat: riverMask,
|
||||||
"trees_mask." + imgFormat: treeMask,
|
"trees_mask." + imgFormat: treeMask,
|
||||||
"roads_mask." + imgFormat: roadMask,
|
"roads_mask." + imgFormat: roadMask,
|
||||||
"bridges_mask." + imgFormat: bridgeMask,
|
"bridges_mask." + imgFormat: bridgeMask,
|
||||||
|
"buildings_mask." + imgFormat: buildingMask,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch packageSelect.Selected {
|
switch packageSelect.Selected {
|
||||||
|
|||||||
+56
-46
@@ -8,29 +8,34 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
Detail float64 `json:"detail"`
|
Detail float64 `json:"detail"`
|
||||||
Roughness float64 `json:"roughness"`
|
Roughness float64 `json:"roughness"`
|
||||||
Width int `json:"width"`
|
Width int `json:"width"`
|
||||||
Height int `json:"height"`
|
Height int `json:"height"`
|
||||||
Lakes int `json:"lakes"`
|
Lakes int `json:"lakes"`
|
||||||
LakeSizeLower float64 `json:"lake_size_lower"`
|
LakeSizeLower float64 `json:"lake_size_lower"`
|
||||||
LakeSizeUpper float64 `json:"lake_size_upper"`
|
LakeSizeUpper float64 `json:"lake_size_upper"`
|
||||||
MinTreeSize float64 `json:"min_tree_size"`
|
MinTreeSize float64 `json:"min_tree_size"`
|
||||||
MaxTreeSize float64 `json:"max_tree_size"`
|
MaxTreeSize float64 `json:"max_tree_size"`
|
||||||
TreeCoverage float64 `json:"tree_coverage"`
|
TreeCoverage float64 `json:"tree_coverage"`
|
||||||
TreeClumpiness float64 `json:"tree_clumpiness"`
|
TreeClumpiness float64 `json:"tree_clumpiness"`
|
||||||
Seed int64 `json:"seed"`
|
Seed int64 `json:"seed"`
|
||||||
Rivers int `json:"rivers"`
|
Rivers int `json:"rivers"`
|
||||||
MinRiverWidth float64 `json:"min_river_width"`
|
MinRiverWidth float64 `json:"min_river_width"`
|
||||||
MaxRiverWidth float64 `json:"max_river_width"`
|
MaxRiverWidth float64 `json:"max_river_width"`
|
||||||
RiverCurvyness float64 `json:"river_curvyness"`
|
RiverCurvyness float64 `json:"river_curvyness"`
|
||||||
NumRoads int `json:"num_roads"`
|
NumRoads int `json:"num_roads"`
|
||||||
MinRoadWidth float64 `json:"min_road_width"`
|
MinRoadWidth float64 `json:"min_road_width"`
|
||||||
MaxRoadWidth float64 `json:"max_road_width"`
|
MaxRoadWidth float64 `json:"max_road_width"`
|
||||||
RoadExits int `json:"road_exits"`
|
RoadExits int `json:"road_exits"`
|
||||||
RoadCurvyness float64 `json:"road_curvyness"`
|
RoadCurvyness float64 `json:"road_curvyness"`
|
||||||
RoadDistribution float64 `json:"road_distribution"`
|
RoadDistribution float64 `json:"road_distribution"`
|
||||||
LastExportPath string `json:"last_export_path"`
|
NumBuildings int `json:"num_buildings"`
|
||||||
|
MinBuildingSize float64 `json:"min_building_size"`
|
||||||
|
MaxBuildingSize float64 `json:"max_building_size"`
|
||||||
|
BuildingDistribution float64 `json:"building_distribution"`
|
||||||
|
BuildingShape string `json:"building_shape"`
|
||||||
|
LastExportPath string `json:"last_export_path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Settings) Save() error {
|
func (s *Settings) Save() error {
|
||||||
@@ -70,29 +75,34 @@ func LoadSettings() (*Settings, error) {
|
|||||||
homeDir = "."
|
homeDir = "."
|
||||||
}
|
}
|
||||||
return &Settings{
|
return &Settings{
|
||||||
Detail: 1,
|
Detail: 1,
|
||||||
Roughness: 0,
|
Roughness: 0,
|
||||||
Width: 300,
|
Width: 300,
|
||||||
Height: 300,
|
Height: 300,
|
||||||
Lakes: 0,
|
Lakes: 0,
|
||||||
LakeSizeLower: 1,
|
LakeSizeLower: 1,
|
||||||
LakeSizeUpper: 5,
|
LakeSizeUpper: 5,
|
||||||
MinTreeSize: 5,
|
MinTreeSize: 5,
|
||||||
MaxTreeSize: 20,
|
MaxTreeSize: 20,
|
||||||
TreeCoverage: 20,
|
TreeCoverage: 20,
|
||||||
TreeClumpiness: 50,
|
TreeClumpiness: 50,
|
||||||
Seed: time.Now().UnixNano(),
|
Seed: time.Now().UnixNano(),
|
||||||
Rivers: 0,
|
Rivers: 0,
|
||||||
MinRiverWidth: 1,
|
MinRiverWidth: 1,
|
||||||
MaxRiverWidth: 5,
|
MaxRiverWidth: 5,
|
||||||
RiverCurvyness: 50,
|
RiverCurvyness: 50,
|
||||||
NumRoads: 100,
|
NumRoads: 100,
|
||||||
MinRoadWidth: 2,
|
MinRoadWidth: 2,
|
||||||
MaxRoadWidth: 8,
|
MaxRoadWidth: 8,
|
||||||
RoadExits: 5,
|
RoadExits: 5,
|
||||||
RoadCurvyness: 50,
|
RoadCurvyness: 50,
|
||||||
RoadDistribution: 50,
|
RoadDistribution: 50,
|
||||||
LastExportPath: homeDir,
|
NumBuildings: 200,
|
||||||
|
MinBuildingSize: 10,
|
||||||
|
MaxBuildingSize: 30,
|
||||||
|
BuildingDistribution: 20,
|
||||||
|
BuildingShape: "squares",
|
||||||
|
LastExportPath: homeDir,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+9
-4
@@ -151,7 +151,7 @@ func FlattenRoadAreas(heightmap image.Image, roadPixels []image.Point) image.Ima
|
|||||||
return composite
|
return composite
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateTrees(img *image.RGBA, lakePixels, roadPixels []image.Point, minTreeSize, maxTreeSize, treeCoverage, treeClumpiness float64, seed int64) []image.Point {
|
func GenerateTrees(img *image.RGBA, lakePixels, roadPixels, buildingPixels []image.Point, minTreeSize, maxTreeSize, treeCoverage, treeClumpiness float64, seed int64) []image.Point {
|
||||||
width := img.Bounds().Dx()
|
width := img.Bounds().Dx()
|
||||||
height := img.Bounds().Dy()
|
height := img.Bounds().Dy()
|
||||||
|
|
||||||
@@ -195,6 +195,11 @@ func GenerateTrees(img *image.RGBA, lakePixels, roadPixels []image.Point, minTre
|
|||||||
isRoad[p] = true
|
isRoad[p] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isBuilding := make(map[image.Point]bool)
|
||||||
|
for _, p := range buildingPixels {
|
||||||
|
isBuilding[p] = true
|
||||||
|
}
|
||||||
|
|
||||||
randSrc := rand.New(rand.NewSource(seed))
|
randSrc := rand.New(rand.NewSource(seed))
|
||||||
|
|
||||||
// 3. Determine initial clump trees
|
// 3. Determine initial clump trees
|
||||||
@@ -204,7 +209,7 @@ func GenerateTrees(img *image.RGBA, lakePixels, roadPixels []image.Point, minTre
|
|||||||
for range numClumpTrees {
|
for range numClumpTrees {
|
||||||
for range 100 { // try 100 times to find a valid spot
|
for range 100 { // try 100 times to find a valid spot
|
||||||
p := image.Point{X: randSrc.Intn(width), Y: randSrc.Intn(height)}
|
p := image.Point{X: randSrc.Intn(width), Y: randSrc.Intn(height)}
|
||||||
if treeNoiseMap.GrayAt(p.X, p.Y).Y >= threshold && !isLake[p] && !isRoad[p] {
|
if treeNoiseMap.GrayAt(p.X, p.Y).Y >= threshold && !isLake[p] && !isRoad[p] && !isBuilding[p] {
|
||||||
initialPoints = append(initialPoints, p)
|
initialPoints = append(initialPoints, p)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -214,7 +219,7 @@ func GenerateTrees(img *image.RGBA, lakePixels, roadPixels []image.Point, minTre
|
|||||||
// 4. Place remaining trees using Bridson's Algorithm
|
// 4. Place remaining trees using Bridson's Algorithm
|
||||||
minRadius := minTreeSize
|
minRadius := minTreeSize
|
||||||
allPoints := poissonDiscSampling(width, height, minRadius, 30, initialPoints, func(p image.Point) bool {
|
allPoints := poissonDiscSampling(width, height, minRadius, 30, initialPoints, func(p image.Point) bool {
|
||||||
return treeNoiseMap.GrayAt(p.X, p.Y).Y >= threshold && !isLake[p] && !isRoad[p]
|
return treeNoiseMap.GrayAt(p.X, p.Y).Y >= threshold && !isLake[p] && !isRoad[p] && !isBuilding[p]
|
||||||
}, seed)
|
}, seed)
|
||||||
|
|
||||||
var treePixels []image.Point
|
var treePixels []image.Point
|
||||||
@@ -252,7 +257,7 @@ func GenerateTrees(img *image.RGBA, lakePixels, roadPixels []image.Point, minTre
|
|||||||
for y := p.Y - int(r); y <= p.Y+int(r); y++ {
|
for y := p.Y - int(r); y <= p.Y+int(r); y++ {
|
||||||
for x := p.X - int(r); x <= p.X+int(r); x++ {
|
for x := p.X - int(r); x <= p.X+int(r); x++ {
|
||||||
pt := image.Point{X: x, Y: y}
|
pt := image.Point{X: x, Y: y}
|
||||||
if !pt.In(img.Bounds()) || isLake[pt] || isRoad[pt] {
|
if !pt.In(img.Bounds()) || isLake[pt] || isRoad[pt] || isBuilding[pt] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user