basic road generation added
This commit is contained in:
@@ -51,6 +51,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 roadPixels []image.Point
|
||||||
|
|
||||||
configDir, err := os.UserConfigDir()
|
configDir, err := os.UserConfigDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -107,25 +108,63 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if canvasImg.Image == nil || heightmapImg.Image == nil {
|
if canvasImg.Image == nil || heightmapImg.Image == nil {
|
||||||
|
|
||||||
// Initial image generation
|
// Initial image generation
|
||||||
|
|
||||||
noiseImg := GenerateHeightmap(settings.Width, settings.Height, int(settings.Detail), 100.0, settings.Seed)
|
noiseImg := GenerateHeightmap(settings.Width, settings.Height, int(settings.Detail), 100.0, settings.Seed)
|
||||||
|
|
||||||
var lakeImage image.Image
|
var lakeImage image.Image
|
||||||
|
|
||||||
lakeImage, lakes = GenerateLakes(settings.Width, settings.Height, settings.Lakes, settings.LakeSizeLower, settings.LakeSizeUpper, noiseImg, settings.Seed)
|
lakeImage, lakes = GenerateLakes(settings.Width, settings.Height, settings.Lakes, settings.LakeSizeLower, settings.LakeSizeUpper, noiseImg, settings.Seed)
|
||||||
|
|
||||||
var riverImage image.Image
|
var riverImage image.Image
|
||||||
|
|
||||||
riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, settings.Seed, noiseImg)
|
riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, settings.Seed, noiseImg)
|
||||||
|
|
||||||
finalImage := riverImage.(*image.RGBA)
|
finalImage := riverImage.(*image.RGBA)
|
||||||
|
|
||||||
var flatLakePixels []image.Point
|
var roadImage *image.RGBA
|
||||||
for _, lake := range lakes {
|
|
||||||
flatLakePixels = append(flatLakePixels, lake...)
|
roadPixels, roadImage = GenerateRoads(settings.Width, settings.Height, settings, noiseImg)
|
||||||
|
|
||||||
|
// Draw roadImage over finalImage
|
||||||
|
|
||||||
|
for y := 0; y < roadImage.Bounds().Max.Y; y++ {
|
||||||
|
|
||||||
|
for x := 0; x < roadImage.Bounds().Max.X; x++ {
|
||||||
|
|
||||||
|
r, g, b, a := roadImage.At(x, y).RGBA()
|
||||||
|
|
||||||
|
if a > 0 {
|
||||||
|
|
||||||
|
finalImage.Set(x, y, color.RGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var flatLakePixels []image.Point
|
||||||
|
|
||||||
|
for _, lake := range lakes {
|
||||||
|
|
||||||
|
flatLakePixels = append(flatLakePixels, lake...)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
allWaterPixels := append(flatLakePixels, riverPixels...)
|
allWaterPixels := append(flatLakePixels, riverPixels...)
|
||||||
|
|
||||||
treePixels = GenerateTrees(finalImage, allWaterPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, settings.Seed)
|
treePixels = GenerateTrees(finalImage, allWaterPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, settings.Seed)
|
||||||
|
|
||||||
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
||||||
|
|
||||||
compositeImg := ApplyRoughness(darkenedHeightmap, settings.Roughness)
|
compositeImg := ApplyRoughness(darkenedHeightmap, settings.Roughness)
|
||||||
|
|
||||||
heightmapImg.Image = compositeImg
|
heightmapImg.Image = compositeImg
|
||||||
|
|
||||||
canvasImg.Image = finalImage
|
canvasImg.Image = finalImage
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
detailLabel := widget.NewLabel(fmt.Sprintf("Detail: %.0f", settings.Detail))
|
detailLabel := widget.NewLabel(fmt.Sprintf("Detail: %.0f", settings.Detail))
|
||||||
@@ -259,6 +298,67 @@ func main() {
|
|||||||
}
|
}
|
||||||
treeClumpinessSlider.SetValue(settings.TreeClumpiness)
|
treeClumpinessSlider.SetValue(settings.TreeClumpiness)
|
||||||
|
|
||||||
|
numRoadsLabel := widget.NewLabel(fmt.Sprintf("Number of Roads: %d", settings.NumRoads))
|
||||||
|
numRoadsSlider := widget.NewSlider(1, 1000)
|
||||||
|
numRoadsSlider.OnChanged = func(val float64) {
|
||||||
|
settings.NumRoads = int(val)
|
||||||
|
numRoadsLabel.SetText(fmt.Sprintf("Number of Roads: %d", settings.NumRoads))
|
||||||
|
}
|
||||||
|
numRoadsSlider.SetValue(float64(settings.NumRoads))
|
||||||
|
|
||||||
|
minRoadWidthLabel := widget.NewLabel(fmt.Sprintf("Min Road Width: %.0fpx", settings.MinRoadWidth))
|
||||||
|
minRoadWidthSlider := widget.NewSlider(1, 150)
|
||||||
|
maxRoadWidthLabel := widget.NewLabel(fmt.Sprintf("Max Road Width: %.0fpx", settings.MaxRoadWidth))
|
||||||
|
maxRoadWidthSlider := widget.NewSlider(1, 150)
|
||||||
|
|
||||||
|
minRoadWidthSlider.OnChanged = func(val float64) {
|
||||||
|
settings.MinRoadWidth = val
|
||||||
|
if settings.MinRoadWidth > settings.MaxRoadWidth {
|
||||||
|
settings.MaxRoadWidth = settings.MinRoadWidth
|
||||||
|
maxRoadWidthSlider.SetValue(settings.MaxRoadWidth)
|
||||||
|
}
|
||||||
|
minRoadWidthLabel.SetText(fmt.Sprintf("Min Road Width: %.0fpx", settings.MinRoadWidth))
|
||||||
|
}
|
||||||
|
minRoadWidthSlider.SetValue(settings.MinRoadWidth)
|
||||||
|
|
||||||
|
maxRoadWidthSlider.OnChanged = func(val float64) {
|
||||||
|
settings.MaxRoadWidth = val
|
||||||
|
if settings.MaxRoadWidth < settings.MinRoadWidth {
|
||||||
|
settings.MinRoadWidth = settings.MaxRoadWidth
|
||||||
|
minRoadWidthSlider.SetValue(settings.MinRoadWidth)
|
||||||
|
}
|
||||||
|
maxRoadWidthLabel.SetText(fmt.Sprintf("Max Road Width: %.0fpx", settings.MaxRoadWidth))
|
||||||
|
}
|
||||||
|
maxRoadWidthSlider.SetValue(settings.MaxRoadWidth)
|
||||||
|
|
||||||
|
roadExitsLabel := widget.NewLabel(fmt.Sprintf("Road Exits: %d", settings.RoadExits))
|
||||||
|
roadExitsSlider := widget.NewSlider(1, 100)
|
||||||
|
roadExitsSlider.OnChanged = func(val float64) {
|
||||||
|
if val > float64(settings.NumRoads) {
|
||||||
|
val = float64(settings.NumRoads)
|
||||||
|
roadExitsSlider.SetValue(val)
|
||||||
|
}
|
||||||
|
settings.RoadExits = int(val)
|
||||||
|
roadExitsLabel.SetText(fmt.Sprintf("Road Exits: %d", settings.RoadExits))
|
||||||
|
}
|
||||||
|
roadExitsSlider.SetValue(float64(settings.RoadExits))
|
||||||
|
|
||||||
|
roadCurvynessLabel := widget.NewLabel(fmt.Sprintf("Road Curvyness: %.0f%%", settings.RoadCurvyness))
|
||||||
|
roadCurvynessSlider := widget.NewSlider(0, 100)
|
||||||
|
roadCurvynessSlider.OnChanged = func(val float64) {
|
||||||
|
settings.RoadCurvyness = val
|
||||||
|
roadCurvynessLabel.SetText(fmt.Sprintf("Road Curvyness: %.0f%%", settings.RoadCurvyness))
|
||||||
|
}
|
||||||
|
roadCurvynessSlider.SetValue(settings.RoadCurvyness)
|
||||||
|
|
||||||
|
roadDistributionLabel := widget.NewLabel(fmt.Sprintf("Distribution: %.0f%%", settings.RoadDistribution))
|
||||||
|
roadDistributionSlider := widget.NewSlider(0, 100)
|
||||||
|
roadDistributionSlider.OnChanged = func(val float64) {
|
||||||
|
settings.RoadDistribution = val
|
||||||
|
roadDistributionLabel.SetText(fmt.Sprintf("Distribution: %.0f%%", settings.RoadDistribution))
|
||||||
|
}
|
||||||
|
roadDistributionSlider.SetValue(settings.RoadDistribution)
|
||||||
|
|
||||||
errorLabel := canvas.NewText("", color.RGBA{R: 255, A: 255})
|
errorLabel := canvas.NewText("", color.RGBA{R: 255, A: 255})
|
||||||
errorLabel.TextSize = 12
|
errorLabel.TextSize = 12
|
||||||
errorLabel.Hide()
|
errorLabel.Hide()
|
||||||
@@ -272,7 +372,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)
|
showMasksSaveDialog(w, canvasImg.Image, heightmapImg.Image, settings, lakes, riverPixels, treePixels, roadPixels)
|
||||||
})
|
})
|
||||||
|
|
||||||
generateBtn = widget.NewButton("Generate", func() {
|
generateBtn = widget.NewButton("Generate", func() {
|
||||||
@@ -286,7 +386,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
steps := 7
|
steps := 8
|
||||||
currentStep := 0
|
currentStep := 0
|
||||||
|
|
||||||
// Step 1: Generating Heightmap
|
// Step 1: Generating Heightmap
|
||||||
@@ -316,13 +416,31 @@ func main() {
|
|||||||
riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, settings.Seed, noiseImg)
|
riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, settings.Seed, noiseImg)
|
||||||
|
|
||||||
finalImage := riverImage.(*image.RGBA)
|
finalImage := riverImage.(*image.RGBA)
|
||||||
|
|
||||||
|
// Step 4: Generating Roads
|
||||||
|
currentStep++
|
||||||
|
fyne.Do(func() {
|
||||||
|
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Roads", currentStep, steps))
|
||||||
|
progressBar.SetValue(float64(currentStep) / float64(steps))
|
||||||
|
})
|
||||||
|
var roadImage *image.RGBA
|
||||||
|
roadPixels, roadImage = GenerateRoads(settings.Width, settings.Height, settings, noiseImg) // Draw roadImage over finalImage
|
||||||
|
for y := 0; y < roadImage.Bounds().Max.Y; y++ {
|
||||||
|
for x := 0; x < roadImage.Bounds().Max.X; x++ {
|
||||||
|
r, g, b, a := roadImage.At(x, y).RGBA()
|
||||||
|
if a > 0 {
|
||||||
|
finalImage.Set(x, y, color.RGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var flatLakePixels []image.Point
|
var flatLakePixels []image.Point
|
||||||
for _, lake := range lakes {
|
for _, lake := range lakes {
|
||||||
flatLakePixels = append(flatLakePixels, lake...)
|
flatLakePixels = append(flatLakePixels, lake...)
|
||||||
}
|
}
|
||||||
allWaterPixels := append(flatLakePixels, riverPixels...)
|
allWaterPixels := append(flatLakePixels, riverPixels...)
|
||||||
|
|
||||||
// Step 4: Darkening Water Areas
|
// Step 5: 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))
|
||||||
@@ -330,7 +448,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
||||||
|
|
||||||
// Step 5: Applying Roughness
|
// Step 6: 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))
|
||||||
@@ -338,7 +456,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
compositeImg := ApplyRoughness(darkenedHeightmap, settings.Roughness)
|
compositeImg := ApplyRoughness(darkenedHeightmap, settings.Roughness)
|
||||||
|
|
||||||
// Step 6: Generating Trees
|
// Step 7: 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))
|
||||||
@@ -346,7 +464,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
treePixels = GenerateTrees(finalImage, allWaterPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, settings.Seed)
|
treePixels = GenerateTrees(finalImage, allWaterPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, settings.Seed)
|
||||||
|
|
||||||
// Step 7: Finalizing Images
|
// Step 8: 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))
|
||||||
@@ -448,10 +566,26 @@ func main() {
|
|||||||
riverCurvynessSlider,
|
riverCurvynessSlider,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
roadDistributionSlider.SetValue(settings.RoadDistribution)
|
||||||
|
|
||||||
|
roadsTab := container.NewTabItem("Roads", container.NewVBox(
|
||||||
|
numRoadsLabel,
|
||||||
|
numRoadsSlider,
|
||||||
|
minRoadWidthLabel,
|
||||||
|
minRoadWidthSlider,
|
||||||
|
maxRoadWidthLabel,
|
||||||
|
maxRoadWidthSlider,
|
||||||
|
roadExitsLabel,
|
||||||
|
roadExitsSlider,
|
||||||
|
roadCurvynessLabel,
|
||||||
|
roadCurvynessSlider,
|
||||||
|
roadDistributionLabel,
|
||||||
|
roadDistributionSlider,
|
||||||
|
))
|
||||||
|
|
||||||
imageTab := container.NewTabItem("Image", container.NewVBox(
|
imageTab := container.NewTabItem("Image", container.NewVBox(
|
||||||
widget.NewLabel("Width:"),
|
widget.NewLabel("Width:"),
|
||||||
widthEntry,
|
widthEntry, widget.NewLabel("Height:"),
|
||||||
widget.NewLabel("Height:"),
|
|
||||||
heightEntry,
|
heightEntry,
|
||||||
widget.NewLabel("Seed:"),
|
widget.NewLabel("Seed:"),
|
||||||
seedEntry,
|
seedEntry,
|
||||||
@@ -469,6 +603,7 @@ func main() {
|
|||||||
imageTab,
|
imageTab,
|
||||||
terrainTab,
|
terrainTab,
|
||||||
waterTab,
|
waterTab,
|
||||||
|
roadsTab,
|
||||||
)
|
)
|
||||||
|
|
||||||
left := container.NewVBox(
|
left := container.NewVBox(
|
||||||
@@ -504,7 +639,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 []image.Point) {
|
func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg image.Image, settings *Settings, lakes [][]image.Point, riverPixels, treePixels, roadPixels []image.Point) {
|
||||||
fileNameEntry := widget.NewEntry()
|
fileNameEntry := widget.NewEntry()
|
||||||
fileNameEntry.SetPlaceHolder("masks_folder")
|
fileNameEntry.SetPlaceHolder("masks_folder")
|
||||||
|
|
||||||
@@ -566,6 +701,10 @@ func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg image.Image, s
|
|||||||
for _, p := range treePixels {
|
for _, p := range treePixels {
|
||||||
treeMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
|
treeMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
|
||||||
}
|
}
|
||||||
|
roadMask := image.NewGray(bounds)
|
||||||
|
for _, p := range roadPixels {
|
||||||
|
roadMask.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,
|
||||||
@@ -573,6 +712,7 @@ func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg image.Image, s
|
|||||||
"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,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch packageSelect.Selected {
|
switch packageSelect.Selected {
|
||||||
|
|||||||
@@ -0,0 +1,280 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"sort"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PointOfInterest struct {
|
||||||
|
X, Y int
|
||||||
|
Connections int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Road struct {
|
||||||
|
Start, End, Control *PointOfInterest
|
||||||
|
Width int
|
||||||
|
Points []image.Point
|
||||||
|
Importance int
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateRoads(width, height int, settings *Settings, noiseImg image.Image) ([]image.Point, *image.RGBA) {
|
||||||
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
// Transparent background
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
img.Set(x, y, color.Transparent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rand.Seed(settings.Seed)
|
||||||
|
roadColor := color.RGBA{R: 139, G: 69, B: 19, A: 255}
|
||||||
|
|
||||||
|
pois := generatePOIs(width, height, settings)
|
||||||
|
if len(pois) == 0 {
|
||||||
|
return nil, img
|
||||||
|
}
|
||||||
|
|
||||||
|
roads := connectPOIs(pois, width, height, settings)
|
||||||
|
assignRoadWidths(roads, settings)
|
||||||
|
|
||||||
|
var allRoadPixels []image.Point
|
||||||
|
for _, road := range roads {
|
||||||
|
roadPixels := drawCurve(img, road.Start, road.End, road.Control, roadColor, road.Width)
|
||||||
|
allRoadPixels = append(allRoadPixels, roadPixels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return allRoadPixels, img
|
||||||
|
}
|
||||||
|
|
||||||
|
func generatePOIs(width, height int, settings *Settings) []*PointOfInterest {
|
||||||
|
numPOIs := settings.NumRoads / 2
|
||||||
|
if numPOIs == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
numExits := settings.RoadExits
|
||||||
|
if numExits > settings.NumRoads {
|
||||||
|
numExits = settings.NumRoads
|
||||||
|
}
|
||||||
|
|
||||||
|
pois := make([]*PointOfInterest, numPOIs)
|
||||||
|
centerX := width / 2
|
||||||
|
centerY := height / 2
|
||||||
|
|
||||||
|
// Distribution affects the radius
|
||||||
|
maxRadius := math.Min(float64(width)/2, float64(height)/2)
|
||||||
|
radius := maxRadius * (settings.RoadDistribution / 100.0)
|
||||||
|
|
||||||
|
for i := 0; i < numPOIs; i++ {
|
||||||
|
// Handle exits
|
||||||
|
if i < numExits {
|
||||||
|
side := rand.Intn(4)
|
||||||
|
var x, y int
|
||||||
|
switch side {
|
||||||
|
case 0: // Top
|
||||||
|
x = rand.Intn(width)
|
||||||
|
y = 0
|
||||||
|
case 1: // Bottom
|
||||||
|
x = rand.Intn(width)
|
||||||
|
y = height - 1
|
||||||
|
case 2: // Left
|
||||||
|
x = 0
|
||||||
|
y = rand.Intn(height)
|
||||||
|
case 3: // Right
|
||||||
|
x = width - 1
|
||||||
|
y = rand.Intn(height)
|
||||||
|
}
|
||||||
|
pois[i] = &PointOfInterest{X: x, Y: y}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
angle := rand.Float64() * 2 * math.Pi
|
||||||
|
r := rand.Float64() * radius
|
||||||
|
x := int(float64(centerX) + r*math.Cos(angle))
|
||||||
|
y := int(float64(centerY) + r*math.Sin(angle))
|
||||||
|
pois[i] = &PointOfInterest{X: x, Y: y}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pois
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings) []*Road {
|
||||||
|
if len(pois) < 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var roads []*Road
|
||||||
|
visited := make(map[*PointOfInterest]bool)
|
||||||
|
existingRoads := make(map[string]bool)
|
||||||
|
|
||||||
|
// Find the center-most POI
|
||||||
|
centerX := width / 2
|
||||||
|
centerY := height / 2
|
||||||
|
var startNode *PointOfInterest
|
||||||
|
minDist := -1.0
|
||||||
|
|
||||||
|
for _, poi := range pois {
|
||||||
|
dist := math.Sqrt(math.Pow(float64(poi.X-centerX), 2) + math.Pow(float64(poi.Y-centerY), 2))
|
||||||
|
if startNode == nil || dist < minDist {
|
||||||
|
minDist = dist
|
||||||
|
startNode = poi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visited[startNode] = true
|
||||||
|
|
||||||
|
for len(visited) < len(pois) {
|
||||||
|
var closest *PointOfInterest
|
||||||
|
var fromNode *PointOfInterest
|
||||||
|
minDist := -1.0
|
||||||
|
|
||||||
|
for poi := range visited {
|
||||||
|
for _, other := range pois {
|
||||||
|
if !visited[other] {
|
||||||
|
dist := math.Sqrt(math.Pow(float64(poi.X-other.X), 2) + math.Pow(float64(poi.Y-other.Y), 2))
|
||||||
|
|
||||||
|
// Check if road exists
|
||||||
|
key := fmt.Sprintf("%p-%p", poi, other)
|
||||||
|
if uintptr(unsafe.Pointer(poi)) > uintptr(unsafe.Pointer(other)) {
|
||||||
|
key = fmt.Sprintf("%p-%p", other, poi)
|
||||||
|
}
|
||||||
|
if existingRoads[key] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if closest == nil || dist < minDist {
|
||||||
|
minDist = dist
|
||||||
|
closest = other
|
||||||
|
fromNode = poi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if closest != nil {
|
||||||
|
visited[closest] = true
|
||||||
|
fromNode.Connections++
|
||||||
|
closest.Connections++
|
||||||
|
|
||||||
|
// Add road to existing roads map
|
||||||
|
key := fmt.Sprintf("%p-%p", fromNode, closest)
|
||||||
|
if uintptr(unsafe.Pointer(fromNode)) > uintptr(unsafe.Pointer(closest)) {
|
||||||
|
key = fmt.Sprintf("%p-%p", closest, fromNode)
|
||||||
|
}
|
||||||
|
existingRoads[key] = true
|
||||||
|
|
||||||
|
// Create control point for curve
|
||||||
|
midX := (fromNode.X + closest.X) / 2
|
||||||
|
midY := (fromNode.Y + closest.Y) / 2
|
||||||
|
dist := math.Sqrt(math.Pow(float64(fromNode.X-closest.X), 2) + math.Pow(float64(fromNode.Y-closest.Y), 2))
|
||||||
|
offset := dist * (settings.RoadCurvyness / 100.0) * (rand.Float64() - 0.5)
|
||||||
|
|
||||||
|
controlX := int(float64(midX) + offset)
|
||||||
|
controlY := int(float64(midY) + offset)
|
||||||
|
|
||||||
|
roads = append(roads, &Road{
|
||||||
|
Start: fromNode,
|
||||||
|
End: closest,
|
||||||
|
Control: &PointOfInterest{X: controlX, Y: controlY},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, road := range roads {
|
||||||
|
road.Importance = road.Start.Connections + road.End.Connections
|
||||||
|
}
|
||||||
|
|
||||||
|
return roads
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignRoadWidths(roads []*Road, settings *Settings) {
|
||||||
|
if len(roads) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(roads, func(i, j int) bool {
|
||||||
|
return roads[i].Importance > roads[j].Importance
|
||||||
|
})
|
||||||
|
|
||||||
|
minWidth := settings.MinRoadWidth
|
||||||
|
maxWidth := settings.MaxRoadWidth
|
||||||
|
widthStep := 0.0
|
||||||
|
if len(roads) > 1 {
|
||||||
|
widthStep = (maxWidth - minWidth) / float64(len(roads)-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, road := range roads {
|
||||||
|
road.Width = int(maxWidth - float64(i)*widthStep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawCurve(img *image.RGBA, p0, p1, p2 *PointOfInterest, col color.Color, width int) []image.Point {
|
||||||
|
var points []image.Point
|
||||||
|
var lastX, lastY int = -1, -1
|
||||||
|
for t := 0.0; t <= 1.0; t += 0.01 {
|
||||||
|
x := (1-t)*(1-t)*float64(p0.X) + 2*(1-t)*t*float64(p2.X) + t*t*float64(p1.X)
|
||||||
|
y := (1-t)*(1-t)*float64(p0.Y) + 2*(1-t)*t*float64(p2.Y) + t*t*float64(p1.Y)
|
||||||
|
|
||||||
|
if lastX != -1 {
|
||||||
|
linePoints := drawLine(img, lastX, lastY, int(x), int(y), col, width)
|
||||||
|
points = append(points, linePoints...)
|
||||||
|
}
|
||||||
|
lastX, lastY = int(x), int(y)
|
||||||
|
}
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bresenham's line algorithm for drawing segments of the curve
|
||||||
|
func drawLine(img *image.RGBA, x0, y0, x1, y1 int, col color.Color, width int) []image.Point {
|
||||||
|
var points []image.Point
|
||||||
|
dx := abs(x1 - x0)
|
||||||
|
dy := -abs(y1 - y0)
|
||||||
|
sx := -1
|
||||||
|
if x0 < x1 {
|
||||||
|
sx = 1
|
||||||
|
}
|
||||||
|
sy := -1
|
||||||
|
if y0 < y1 {
|
||||||
|
sy = 1
|
||||||
|
}
|
||||||
|
err := dx + dy
|
||||||
|
|
||||||
|
for {
|
||||||
|
for i := -width / 2; i <= width/2; i++ {
|
||||||
|
for j := -width / 2; j <= width/2; j++ {
|
||||||
|
px := x0 + i
|
||||||
|
py := y0 + j
|
||||||
|
if img.Bounds().Min.X <= px && px < img.Bounds().Max.X && img.Bounds().Min.Y <= py && py < img.Bounds().Max.Y {
|
||||||
|
img.Set(px, py, col)
|
||||||
|
points = append(points, image.Point{X: px, Y: py})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if x0 == x1 && y0 == y1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e2 := 2 * err
|
||||||
|
if e2 >= dy {
|
||||||
|
err += dy
|
||||||
|
x0 += sx
|
||||||
|
}
|
||||||
|
if e2 <= dx {
|
||||||
|
err += dx
|
||||||
|
y0 += sy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
|
func abs(x int) int {
|
||||||
|
if x < 0 {
|
||||||
|
return -x
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
+46
-34
@@ -8,23 +8,29 @@ 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"`
|
||||||
LastExportPath string `json:"last_export_path"`
|
NumRoads int `json:"num_roads"`
|
||||||
|
MinRoadWidth float64 `json:"min_road_width"`
|
||||||
|
MaxRoadWidth float64 `json:"max_road_width"`
|
||||||
|
RoadExits int `json:"road_exits"`
|
||||||
|
RoadCurvyness float64 `json:"road_curvyness"`
|
||||||
|
RoadDistribution float64 `json:"road_distribution"`
|
||||||
|
LastExportPath string `json:"last_export_path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Settings) Save() error {
|
func (s *Settings) Save() error {
|
||||||
@@ -64,23 +70,29 @@ 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,
|
||||||
LastExportPath: homeDir,
|
NumRoads: 100,
|
||||||
|
MinRoadWidth: 2,
|
||||||
|
MaxRoadWidth: 8,
|
||||||
|
RoadExits: 5,
|
||||||
|
RoadCurvyness: 50,
|
||||||
|
RoadDistribution: 50,
|
||||||
|
LastExportPath: homeDir,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Reference in New Issue
Block a user