From 6227ecb3fdf235e542f29a17615294b90967f7b4 Mon Sep 17 00:00:00 2001 From: Grimsace Date: Wed, 18 Feb 2026 14:33:09 -0600 Subject: [PATCH] added user settings to control river width variability and bank detail --- main.go | 31 +++++++++++++++++++++-- settings.go | 72 ++++++++++++++++++++++++++++------------------------- water.go | 19 ++++++++------ 3 files changed, 79 insertions(+), 43 deletions(-) diff --git a/main.go b/main.go index 875502b..abc4904 100644 --- a/main.go +++ b/main.go @@ -158,7 +158,7 @@ func main() { // Step 3: Generating Rivers var riverImage image.Image - riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, seedProvider.Next(), noiseImg) + riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, seedProvider.Next(), noiseImg, settings.RiverWidthVariability, settings.RiverEdgeRoughness) finalImage := riverImage.(*image.RGBA) @@ -338,6 +338,31 @@ func main() { val, _ := riverCurvynessSlider.value.Get() settings.RiverCurvyness = val })) + + riverWidthVariabilitySlider := newNumericInputSlider(0, 100, settings.RiverWidthVariability, "%.0f%%", "River Width Variability") + riverWidthVariabilitySlider.entry.OnChanged = func(s string) { + riverWidthVariabilitySlider.validate(s, func(hasError bool) { + errorStates["riverWidthVariability"] = hasError + updateGenerateBtnState() + }) + } + riverWidthVariabilitySlider.value.AddListener(binding.NewDataListener(func() { + val, _ := riverWidthVariabilitySlider.value.Get() + settings.RiverWidthVariability = val + })) + + riverEdgeRoughnessSlider := newNumericInputSlider(0, 100, settings.RiverEdgeRoughness, "%.0f%%", "River Edge Roughness") + riverEdgeRoughnessSlider.entry.OnChanged = func(s string) { + riverEdgeRoughnessSlider.validate(s, func(hasError bool) { + errorStates["riverEdgeRoughness"] = hasError + updateGenerateBtnState() + }) + } + riverEdgeRoughnessSlider.value.AddListener(binding.NewDataListener(func() { + val, _ := riverEdgeRoughnessSlider.value.Get() + settings.RiverEdgeRoughness = val + })) + minTreeSizeSlider := newNumericInputSlider(1, 150, settings.MinTreeSize, "%.0fpx", "Min Tree Size") minTreeSizeSlider.entry.OnChanged = func(s string) { minTreeSizeSlider.validate(s, func(hasError bool) { @@ -516,7 +541,7 @@ func main() { progressBar.SetValue(3.0 / 11.0) }) var riverImage image.Image - riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, seedProvider.Next(), noiseImg) + riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, seedProvider.Next(), noiseImg, settings.RiverWidthVariability, settings.RiverEdgeRoughness) finalImage := riverImage.(*image.RGBA) @@ -682,6 +707,8 @@ func main() { minRiverWidthSlider, maxRiverWidthSlider, riverCurvynessSlider, + riverWidthVariabilitySlider, + riverEdgeRoughnessSlider, )) roadsTab := container.NewTabItem("Roads", container.NewVBox( diff --git a/settings.go b/settings.go index 8171270..1a16f19 100644 --- a/settings.go +++ b/settings.go @@ -16,13 +16,15 @@ type Settings struct { Height int `json:"height"` // Water settings - Lakes int `json:"lakes"` - LakeSizeLower float64 `json:"lake_size_lower"` - LakeSizeUpper float64 `json:"lake_size_upper"` - Rivers int `json:"rivers"` - MinRiverWidth float64 `json:"min_river_width"` - MaxRiverWidth float64 `json:"max_river_width"` - RiverCurvyness float64 `json:"river_curvyness"` + Lakes int `json:"lakes"` + LakeSizeLower float64 `json:"lake_size_lower"` + LakeSizeUpper float64 `json:"lake_size_upper"` + Rivers int `json:"rivers"` + MinRiverWidth float64 `json:"min_river_width"` + MaxRiverWidth float64 `json:"max_river_width"` + RiverCurvyness float64 `json:"river_curvyness"` + RiverWidthVariability float64 `json:"river_width_variability"` + RiverEdgeRoughness float64 `json:"river_edge_roughness"` // Tree settings MinTreeSize float64 `json:"min_tree_size"` @@ -106,33 +108,35 @@ func LoadSettings() (*Settings, error) { homeDir = "." } return &Settings{ - Detail: 1, - Roughness: 0, - Width: 300, - Height: 300, - Lakes: 0, - LakeSizeLower: 1, - LakeSizeUpper: 5, - MinTreeSize: 5, - MaxTreeSize: 20, - TreeCoverage: 20, - TreeClumpiness: 50, - Seed: time.Now().UnixNano(), - Rivers: 0, - MinRiverWidth: 1, - MaxRiverWidth: 5, - RiverCurvyness: 50, - NumRoads: 100, - MinRoadWidth: 2, - MaxRoadWidth: 8, - RoadExits: 5, - RoadCurvyness: 50, - RoadDistribution: 50, - NumBuildings: 200, - MinBuildingSize: 10, - MaxBuildingSize: 30, - BuildingDistribution: 20, - BuildingShape: "mixed", + Detail: 1, + Roughness: 0, + Width: 300, + Height: 300, + Lakes: 0, + LakeSizeLower: 1, + LakeSizeUpper: 5, + MinTreeSize: 5, + MaxTreeSize: 20, + TreeCoverage: 20, + TreeClumpiness: 50, + Seed: time.Now().UnixNano(), + Rivers: 0, + MinRiverWidth: 1, + MaxRiverWidth: 5, + RiverCurvyness: 50, + RiverWidthVariability: 50, + RiverEdgeRoughness: 50, + NumRoads: 100, + MinRoadWidth: 2, + MaxRoadWidth: 8, + RoadExits: 5, + RoadCurvyness: 50, + RoadDistribution: 50, + NumBuildings: 200, + MinBuildingSize: 10, + MaxBuildingSize: 30, + BuildingDistribution: 20, + BuildingShape: "mixed", BuildingShapeRatios: map[string]float64{ "squares": 40, "circles": 30, diff --git a/water.go b/water.go index 7935777..41095c7 100644 --- a/water.go +++ b/water.go @@ -190,7 +190,7 @@ type River struct { } // GenerateRivers creates rivers on the map. -func GenerateRivers(width, height, numRivers int, minWidth, maxWidth, curvyness float64, inputImage image.Image, lakes [][]image.Point, seed int64, heightmap image.Image) (image.Image, []image.Point) { +func GenerateRivers(width, height, numRivers int, minWidth, maxWidth, curvyness float64, inputImage image.Image, lakes [][]image.Point, seed int64, heightmap image.Image, riverWidthVariability, riverEdgeRoughness float64) (image.Image, []image.Point) { if numRivers == 0 { return inputImage, nil } @@ -267,7 +267,7 @@ func GenerateRivers(width, height, numRivers int, minWidth, maxWidth, curvyness radius := riverWidthPx / 2.0 for _, p := range path { - drawCircle(canvas, p, radius, color.RGBA{R: 0, G: 0, B: 255, A: 255}, &allRiverPixels, isWater, heightmap) + drawCircle(canvas, p, radius, color.RGBA{R: 0, G: 0, B: 255, A: 255}, &allRiverPixels, isWater, heightmap, riverWidthVariability, riverEdgeRoughness) } r.Points = path } @@ -410,14 +410,19 @@ func getPointOnEdge(width, height, edge int, randSrc *rand.Rand) image.Point { // drawCircle draws a circle on the image and adds its pixels to the given slice. // The outer edges are roughened using dual sin waves for natural-looking banks. -func drawCircle(img *image.RGBA, center image.Point, radius float64, c color.Color, pixels *[]image.Point, isWater map[image.Point]bool, heightmap image.Image) { +// riverWidthVariability controls the amplitude of width changes (0-100%). +// riverEdgeRoughness controls the detail level of the edge roughness (0-100%). +func drawCircle(img *image.RGBA, center image.Point, radius float64, c color.Color, pixels *[]image.Point, isWater map[image.Point]bool, heightmap image.Image, riverWidthVariability, riverEdgeRoughness float64) { bounds := img.Bounds() // Calculate dual sin wave amplitudes for outer edge roughening - // Large amplitude represents major variations in river width - // Small amplitude is 1/4 of large for subtle details - largeAmplitude := radius * 0.25 - smallAmplitude := largeAmplitude / 4.0 + // Large amplitude represents major variations in river width (controlled by riverWidthVariability) + // At 0%, no width variation; at 100%, amplitude is 50% of radius + largeAmplitude := (radius * 0.5) * (riverWidthVariability / 100.0) + + // Small amplitude is controlled by riverEdgeRoughness + // At 0%, no detail; at 100%, detail amplitude equals large amplitude + smallAmplitude := largeAmplitude * (riverEdgeRoughness / 100.0) for y := int(math.Floor(float64(center.Y) - radius)); y <= int(math.Ceil(float64(center.Y)+radius)); y++ { for x := int(math.Floor(float64(center.X) - radius)); x <= int(math.Ceil(float64(center.X)+radius)); x++ {