diff --git a/main.go b/main.go index abc4904..4c2c814 100644 --- a/main.go +++ b/main.go @@ -153,7 +153,7 @@ func main() { // Step 2: Generating Lakes var lakeImage image.Image - lakeImage, lakes = GenerateLakes(settings.Width, settings.Height, settings.Lakes, settings.LakeSizeLower, settings.LakeSizeUpper, noiseImg, seedProvider.Next()) + lakeImage, lakes = GenerateLakes(settings.Width, settings.Height, settings.Lakes, settings.LakeSizeLower, settings.LakeSizeUpper, seedProvider.Next(), settings.LakeEdgeRoughness) // Step 3: Generating Rivers var riverImage image.Image @@ -291,6 +291,19 @@ func main() { val, _ := lakeSizeUpperSlider.value.Get() settings.LakeSizeUpper = val })) + + lakeEdgeRoughnessSlider := newNumericInputSlider(0, 100, settings.LakeEdgeRoughness, "%.0f%%", "Lake Edge Roughness") + lakeEdgeRoughnessSlider.entry.OnChanged = func(s string) { + lakeEdgeRoughnessSlider.validate(s, func(hasError bool) { + errorStates["lakeEdgeRoughness"] = hasError + updateGenerateBtnState() + }) + } + lakeEdgeRoughnessSlider.value.AddListener(binding.NewDataListener(func() { + val, _ := lakeEdgeRoughnessSlider.value.Get() + settings.LakeEdgeRoughness = val + })) + riversSlider := newNumericInputSlider(0, 5, float64(settings.Rivers), "%.0f", "Rivers") riversSlider.entry.OnChanged = func(s string) { riversSlider.validate(s, func(hasError bool) { @@ -532,7 +545,7 @@ func main() { progressBar.SetValue(2.0 / 11.0) }) var lakeImage image.Image - lakeImage, lakes = GenerateLakes(settings.Width, settings.Height, settings.Lakes, settings.LakeSizeLower, settings.LakeSizeUpper, noiseImg, seedProvider.Next()) + lakeImage, lakes = GenerateLakes(settings.Width, settings.Height, settings.Lakes, settings.LakeSizeLower, settings.LakeSizeUpper, seedProvider.Next(), settings.LakeEdgeRoughness) // Step 3: Generating Rivers @@ -700,6 +713,7 @@ func main() { lakesSlider, lakeSizeLowerSlider, lakeSizeUpperSlider, + lakeEdgeRoughnessSlider, widget.NewLabel(""), // Spacer diff --git a/settings.go b/settings.go index 1a16f19..4d63e48 100644 --- a/settings.go +++ b/settings.go @@ -19,6 +19,7 @@ type Settings struct { Lakes int `json:"lakes"` LakeSizeLower float64 `json:"lake_size_lower"` LakeSizeUpper float64 `json:"lake_size_upper"` + LakeEdgeRoughness float64 `json:"lake_edge_roughness"` Rivers int `json:"rivers"` MinRiverWidth float64 `json:"min_river_width"` MaxRiverWidth float64 `json:"max_river_width"` @@ -115,6 +116,7 @@ func LoadSettings() (*Settings, error) { Lakes: 0, LakeSizeLower: 1, LakeSizeUpper: 5, + LakeEdgeRoughness: 50, MinTreeSize: 5, MaxTreeSize: 20, TreeCoverage: 20, diff --git a/water.go b/water.go index 41095c7..f6b77d3 100644 --- a/water.go +++ b/water.go @@ -47,7 +47,8 @@ func (pq *priorityQueue) Pop() any { } // GenerateLakes creates lakes on the map using a growth algorithm. -func GenerateLakes(width, height, numLakes int, lakeSizeLower, lakeSizeUpper float64, heightmap image.Image, seed int64) (image.Image, [][]image.Point) { +// When lakeEdgeRoughness is 0, lakes grow in perfect circles. Higher values add noise-based irregularity. +func GenerateLakes(width, height, numLakes int, lakeSizeLower, lakeSizeUpper float64, seed int64, lakeEdgeRoughness float64) (image.Image, [][]image.Point) { // Initialize a white canvas to draw the lakes on canvas := image.NewRGBA(image.Rect(0, 0, width, height)) draw.Draw(canvas, canvas.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src) @@ -125,7 +126,7 @@ func GenerateLakes(width, height, numLakes int, lakeSizeLower, lakeSizeUpper flo continue } - // Use noise to create a more natural lake shape. + // Use noise to create a more natural lake shape (only if roughness > 0). seedX := randSrc.Float64() * 10000.0 seedY := randSrc.Float64() * 10000.0 radius := math.Sqrt(float64(targetPixelsPerLake) / math.Pi) @@ -134,12 +135,18 @@ func GenerateLakes(width, height, numLakes int, lakeSizeLower, lakeSizeUpper flo getScore := func(pt image.Point) float64 { dx, dy := pt.X-startPt.X, pt.Y-startPt.Y dist := math.Sqrt(float64(dx*dx + dy*dy)) - noise := noiseGen.Eval2(seedX+float64(dx)*noiseFreq, seedY+float64(dy)*noiseFreq) distPenalty := math.Pow(dist/radius, 3.0) - luma, _, _, _ := heightmap.At(pt.X, pt.Y).RGBA() - heightmapVal := float64(luma) / 65535.0 - heightmapEffect := (0.5 - heightmapVal) * 1.5 // Encourage growth in lower areas - return noise - distPenalty + heightmapEffect + + // Only apply noise if edge roughness is requested + if lakeEdgeRoughness > 0 { + noise := noiseGen.Eval2(seedX+float64(dx)*noiseFreq, seedY+float64(dy)*noiseFreq) + // Scale noise contribution by roughness setting + noiseContribution := noise * (lakeEdgeRoughness / 100.0) + return noiseContribution - distPenalty + } + + // Pure circular growth when variability is 0 + return -distPenalty } heap.Push(pq, &lakePixel{point: startPt, score: getScore(startPt)})