901 lines
26 KiB
Go
901 lines
26 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/tar"
|
|
"archive/zip"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/jpeg"
|
|
"image/png"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/app"
|
|
"fyne.io/fyne/v2/canvas"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/dialog"
|
|
"fyne.io/fyne/v2/widget"
|
|
|
|
"github.com/chai2010/webp"
|
|
)
|
|
|
|
func main() {
|
|
a := app.NewWithID("com.example.rpgcitymaker")
|
|
a.Settings().SetTheme(&CustomTheme{a.Settings().Theme()})
|
|
w := a.NewWindow("RPG City Maker")
|
|
w.Resize(fyne.NewSize(800, 600))
|
|
|
|
settings, err := LoadSettings()
|
|
if err != nil {
|
|
log.Println("Error loading settings:", err)
|
|
// Use default settings if loading fails
|
|
settings = &Settings{Detail: 1, Roughness: 0, Width: 300, Height: 300, Lakes: 0, LakeSizeLower: 1, LakeSizeUpper: 5}
|
|
}
|
|
|
|
canvasImg := &canvas.Image{
|
|
FillMode: canvas.ImageFillContain,
|
|
}
|
|
|
|
heightmapImg := &canvas.Image{
|
|
FillMode: canvas.ImageFillContain,
|
|
}
|
|
|
|
var lakes [][]image.Point
|
|
var riverPixels []image.Point
|
|
var treePixels []image.Point
|
|
var roadPixels []image.Point
|
|
|
|
configDir, err := os.UserConfigDir()
|
|
if err != nil {
|
|
log.Fatal("Failed to get user config dir:", err)
|
|
}
|
|
appConfigDir := filepath.Join(configDir, "rpgcitymakerreborn")
|
|
canvasPath := filepath.Join(appConfigDir, "canvas.png")
|
|
heightmapPath := filepath.Join(appConfigDir, "heightmap.png")
|
|
w.SetOnClosed(func() {
|
|
if err := settings.Save(); err != nil {
|
|
log.Println("Error saving settings:", err)
|
|
}
|
|
|
|
if canvasImg.Image != nil {
|
|
canvasFile, err := os.Create(canvasPath)
|
|
if err != nil {
|
|
log.Println("Failed to create canvas file:", err)
|
|
} else {
|
|
defer canvasFile.Close()
|
|
if err := png.Encode(canvasFile, canvasImg.Image); err != nil {
|
|
log.Println("Failed to encode canvas image:", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if heightmapImg.Image != nil {
|
|
heightmapFile, err := os.Create(heightmapPath)
|
|
if err != nil {
|
|
log.Println("Failed to create heightmap file:", err)
|
|
} else {
|
|
defer heightmapFile.Close()
|
|
if err := png.Encode(heightmapFile, heightmapImg.Image); err != nil {
|
|
log.Println("Failed to encode heightmap image:", err)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
canvasFile, err := os.Open(canvasPath)
|
|
if err == nil {
|
|
defer canvasFile.Close()
|
|
img, err := png.Decode(canvasFile)
|
|
if err == nil {
|
|
canvasImg.Image = img
|
|
}
|
|
}
|
|
|
|
heightmapFile, err := os.Open(heightmapPath)
|
|
if err == nil {
|
|
defer heightmapFile.Close()
|
|
img, err := png.Decode(heightmapFile)
|
|
if err == nil {
|
|
heightmapImg.Image = img
|
|
}
|
|
}
|
|
|
|
if canvasImg.Image == nil || heightmapImg.Image == nil {
|
|
|
|
// Initial image generation
|
|
|
|
noiseImg := GenerateHeightmap(settings.Width, settings.Height, int(settings.Detail), 100.0, settings.Seed)
|
|
|
|
var lakeImage image.Image
|
|
|
|
lakeImage, lakes = GenerateLakes(settings.Width, settings.Height, settings.Lakes, settings.LakeSizeLower, settings.LakeSizeUpper, noiseImg, settings.Seed)
|
|
|
|
var riverImage image.Image
|
|
|
|
riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, settings.Seed, noiseImg)
|
|
|
|
finalImage := riverImage.(*image.RGBA)
|
|
|
|
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
|
|
|
|
for _, lake := range lakes {
|
|
|
|
flatLakePixels = append(flatLakePixels, lake...)
|
|
|
|
}
|
|
|
|
allWaterPixels := append(flatLakePixels, riverPixels...)
|
|
|
|
treePixels = GenerateTrees(finalImage, allWaterPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, settings.Seed)
|
|
|
|
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
|
|
|
compositeImg := ApplyRoughness(darkenedHeightmap, settings.Roughness)
|
|
|
|
heightmapImg.Image = compositeImg
|
|
|
|
canvasImg.Image = finalImage
|
|
|
|
}
|
|
|
|
detailLabel := widget.NewLabel(fmt.Sprintf("Detail: %.0f", settings.Detail))
|
|
detailSlider := widget.NewSlider(1, 16)
|
|
detailSlider.OnChanged = func(val float64) {
|
|
settings.Detail = val
|
|
detailLabel.SetText(fmt.Sprintf("Detail: %.0f", settings.Detail))
|
|
}
|
|
detailSlider.SetValue(settings.Detail)
|
|
|
|
roughnessLabel := widget.NewLabel(fmt.Sprintf("Roughness: %.0f%%", settings.Roughness))
|
|
roughnessSlider := widget.NewSlider(0, 100)
|
|
roughnessSlider.OnChanged = func(val float64) {
|
|
settings.Roughness = val
|
|
roughnessLabel.SetText(fmt.Sprintf("Roughness: %.0f%%", settings.Roughness))
|
|
}
|
|
roughnessSlider.SetValue(settings.Roughness)
|
|
|
|
lakesLabel := widget.NewLabel(fmt.Sprintf("Lakes: %d", settings.Lakes))
|
|
lakesSlider := widget.NewSlider(0, 15)
|
|
lakesSlider.OnChanged = func(val float64) {
|
|
settings.Lakes = int(val)
|
|
lakesLabel.SetText(fmt.Sprintf("Lakes: %d", settings.Lakes))
|
|
}
|
|
lakesSlider.SetValue(float64(settings.Lakes))
|
|
|
|
lakeSizeLowerLabel := widget.NewLabel(fmt.Sprintf("Min Lake Size: %.0f%%", settings.LakeSizeLower))
|
|
lakeSizeLowerSlider := widget.NewSlider(1, 100)
|
|
lakeSizeUpperLabel := widget.NewLabel(fmt.Sprintf("Max Lake Size: %.0f%%", settings.LakeSizeUpper))
|
|
lakeSizeUpperSlider := widget.NewSlider(1, 100)
|
|
|
|
lakeSizeLowerSlider.OnChanged = func(val float64) {
|
|
settings.LakeSizeLower = val
|
|
if settings.LakeSizeLower > settings.LakeSizeUpper {
|
|
settings.LakeSizeUpper = settings.LakeSizeLower
|
|
lakeSizeUpperSlider.SetValue(settings.LakeSizeUpper)
|
|
}
|
|
lakeSizeLowerLabel.SetText(fmt.Sprintf("Min Lake Size: %.0f%%", settings.LakeSizeLower))
|
|
}
|
|
lakeSizeLowerSlider.SetValue(settings.LakeSizeLower)
|
|
|
|
lakeSizeUpperSlider.OnChanged = func(val float64) {
|
|
settings.LakeSizeUpper = val
|
|
if settings.LakeSizeUpper < settings.LakeSizeLower {
|
|
settings.LakeSizeLower = settings.LakeSizeUpper
|
|
lakeSizeLowerSlider.SetValue(settings.LakeSizeLower)
|
|
}
|
|
lakeSizeUpperLabel.SetText(fmt.Sprintf("Max Lake Size: %.0f%%", settings.LakeSizeUpper))
|
|
}
|
|
lakeSizeUpperSlider.SetValue(settings.LakeSizeUpper)
|
|
|
|
riversLabel := widget.NewLabel(fmt.Sprintf("Rivers: %d", settings.Rivers))
|
|
riversSlider := widget.NewSlider(0, 5)
|
|
riversSlider.OnChanged = func(val float64) {
|
|
settings.Rivers = int(val)
|
|
riversLabel.SetText(fmt.Sprintf("Rivers: %d", settings.Rivers))
|
|
}
|
|
riversSlider.SetValue(float64(settings.Rivers))
|
|
|
|
minRiverWidthLabel := widget.NewLabel(fmt.Sprintf("Min River Width: %.0f%%", settings.MinRiverWidth))
|
|
minRiverWidthSlider := widget.NewSlider(1, 100)
|
|
maxRiverWidthLabel := widget.NewLabel(fmt.Sprintf("Max River Width: %.0f%%", settings.MaxRiverWidth))
|
|
maxRiverWidthSlider := widget.NewSlider(1, 100)
|
|
|
|
minRiverWidthSlider.OnChanged = func(val float64) {
|
|
settings.MinRiverWidth = val
|
|
if settings.MinRiverWidth > settings.MaxRiverWidth {
|
|
settings.MaxRiverWidth = settings.MinRiverWidth
|
|
maxRiverWidthSlider.SetValue(settings.MaxRiverWidth)
|
|
}
|
|
minRiverWidthLabel.SetText(fmt.Sprintf("Min River Width: %.0f%%", settings.MinRiverWidth))
|
|
}
|
|
minRiverWidthSlider.SetValue(settings.MinRiverWidth)
|
|
|
|
maxRiverWidthSlider.OnChanged = func(val float64) {
|
|
settings.MaxRiverWidth = val
|
|
if settings.MaxRiverWidth < settings.MinRiverWidth {
|
|
settings.MinRiverWidth = settings.MaxRiverWidth
|
|
minRiverWidthSlider.SetValue(settings.MinRiverWidth)
|
|
}
|
|
maxRiverWidthLabel.SetText(fmt.Sprintf("Max River Width: %.0f%%", settings.MaxRiverWidth))
|
|
}
|
|
maxRiverWidthSlider.SetValue(settings.MaxRiverWidth)
|
|
|
|
riverCurvynessLabel := widget.NewLabel(fmt.Sprintf("River Curvyness: %.0f%%", settings.RiverCurvyness))
|
|
riverCurvynessSlider := widget.NewSlider(0, 100)
|
|
riverCurvynessSlider.OnChanged = func(val float64) {
|
|
settings.RiverCurvyness = val
|
|
riverCurvynessLabel.SetText(fmt.Sprintf("River Curvyness: %.0f%%", settings.RiverCurvyness))
|
|
}
|
|
riverCurvynessSlider.SetValue(settings.RiverCurvyness)
|
|
|
|
minTreeSizeLabel := widget.NewLabel(fmt.Sprintf("Min Tree Size: %.0fpx", settings.MinTreeSize))
|
|
minTreeSizeSlider := widget.NewSlider(1, 150)
|
|
maxTreeSizeLabel := widget.NewLabel(fmt.Sprintf("Max Tree Size: %.0fpx", settings.MaxTreeSize))
|
|
maxTreeSizeSlider := widget.NewSlider(1, 150)
|
|
|
|
minTreeSizeSlider.OnChanged = func(val float64) {
|
|
settings.MinTreeSize = val
|
|
if settings.MinTreeSize > settings.MaxTreeSize {
|
|
settings.MaxTreeSize = settings.MinTreeSize
|
|
maxTreeSizeSlider.SetValue(settings.MaxTreeSize)
|
|
}
|
|
minTreeSizeLabel.SetText(fmt.Sprintf("Min Tree Size: %.0fpx", settings.MinTreeSize))
|
|
}
|
|
minTreeSizeSlider.SetValue(settings.MinTreeSize)
|
|
|
|
maxTreeSizeSlider.OnChanged = func(val float64) {
|
|
settings.MaxTreeSize = val
|
|
if settings.MaxTreeSize < settings.MinTreeSize {
|
|
settings.MinTreeSize = settings.MaxTreeSize
|
|
minTreeSizeSlider.SetValue(settings.MinTreeSize)
|
|
}
|
|
maxTreeSizeLabel.SetText(fmt.Sprintf("Max Tree Size: %.0fpx", settings.MaxTreeSize))
|
|
}
|
|
maxTreeSizeSlider.SetValue(settings.MaxTreeSize)
|
|
|
|
treeCoverageLabel := widget.NewLabel(fmt.Sprintf("Tree Coverage: %.0f%%", settings.TreeCoverage))
|
|
treeCoverageSlider := widget.NewSlider(1, 100)
|
|
treeCoverageSlider.OnChanged = func(val float64) {
|
|
settings.TreeCoverage = val
|
|
treeCoverageLabel.SetText(fmt.Sprintf("Tree Coverage: %.0f%%", settings.TreeCoverage))
|
|
}
|
|
treeCoverageSlider.SetValue(settings.TreeCoverage)
|
|
|
|
treeClumpinessLabel := widget.NewLabel(fmt.Sprintf("Tree Clumpiness: %.0f%%", settings.TreeClumpiness))
|
|
treeClumpinessSlider := widget.NewSlider(0, 100)
|
|
treeClumpinessSlider.OnChanged = func(val float64) {
|
|
settings.TreeClumpiness = val
|
|
treeClumpinessLabel.SetText(fmt.Sprintf("Tree Clumpiness: %.0f%%", 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.TextSize = 12
|
|
errorLabel.Hide()
|
|
|
|
var generateBtn *widget.Button
|
|
progressBar := NewTextOverlayProgressBar()
|
|
exportCanvasBtn := widget.NewButton("Export Canvas", func() {
|
|
showSaveDialog(w, canvasImg.Image, settings)
|
|
})
|
|
exportHeightmapBtn := widget.NewButton("Export Heightmap", func() {
|
|
showSaveDialog(w, heightmapImg.Image, settings)
|
|
})
|
|
exportMasksBtn := widget.NewButton("Export Masks", func() {
|
|
showMasksSaveDialog(w, canvasImg.Image, heightmapImg.Image, settings, lakes, riverPixels, treePixels, roadPixels)
|
|
})
|
|
|
|
generateBtn = widget.NewButton("Generate", func() {
|
|
go func() {
|
|
fyne.Do(func() {
|
|
generateBtn.Disable()
|
|
})
|
|
defer func() {
|
|
fyne.Do(func() {
|
|
generateBtn.Enable()
|
|
})
|
|
}()
|
|
|
|
steps := 8
|
|
currentStep := 0
|
|
|
|
// Step 1: Generating Heightmap
|
|
currentStep++
|
|
fyne.Do(func() {
|
|
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Heightmap", currentStep, steps))
|
|
progressBar.SetValue(float64(currentStep) / float64(steps))
|
|
})
|
|
noiseImg := GenerateHeightmap(settings.Width, settings.Height, int(settings.Detail), 100.0, settings.Seed)
|
|
|
|
// Step 2: Generating Lakes
|
|
currentStep++
|
|
fyne.Do(func() {
|
|
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Lakes", currentStep, steps))
|
|
progressBar.SetValue(float64(currentStep) / float64(steps))
|
|
})
|
|
var lakeImage image.Image
|
|
lakeImage, lakes = GenerateLakes(settings.Width, settings.Height, settings.Lakes, settings.LakeSizeLower, settings.LakeSizeUpper, noiseImg, settings.Seed)
|
|
|
|
// Step 3: Generating Rivers
|
|
currentStep++
|
|
fyne.Do(func() {
|
|
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Rivers", currentStep, steps))
|
|
progressBar.SetValue(float64(currentStep) / float64(steps))
|
|
})
|
|
var riverImage image.Image
|
|
riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, settings.Seed, noiseImg)
|
|
|
|
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
|
|
for _, lake := range lakes {
|
|
flatLakePixels = append(flatLakePixels, lake...)
|
|
}
|
|
allWaterPixels := append(flatLakePixels, riverPixels...)
|
|
|
|
// Step 5: Darkening Water Areas
|
|
currentStep++
|
|
fyne.Do(func() {
|
|
progressBar.SetText(fmt.Sprintf("Step %d/%d: Darkening Water Areas", currentStep, steps))
|
|
progressBar.SetValue(float64(currentStep) / float64(steps))
|
|
})
|
|
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
|
|
|
// Step 6: Applying Roughness
|
|
currentStep++
|
|
fyne.Do(func() {
|
|
progressBar.SetText(fmt.Sprintf("Step %d/%d: Applying Roughness", currentStep, steps))
|
|
progressBar.SetValue(float64(currentStep) / float64(steps))
|
|
})
|
|
compositeImg := ApplyRoughness(darkenedHeightmap, settings.Roughness)
|
|
|
|
// Step 7: Generating Trees
|
|
currentStep++
|
|
fyne.Do(func() {
|
|
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Trees", currentStep, steps))
|
|
progressBar.SetValue(float64(currentStep) / float64(steps))
|
|
})
|
|
treePixels = GenerateTrees(finalImage, allWaterPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, settings.Seed)
|
|
|
|
// Step 8: Finalizing Images
|
|
currentStep++
|
|
fyne.Do(func() {
|
|
progressBar.SetText(fmt.Sprintf("Step %d/%d: Finalizing Images", currentStep, steps))
|
|
progressBar.SetValue(float64(currentStep) / float64(steps))
|
|
heightmapImg.Image = compositeImg
|
|
heightmapImg.Refresh()
|
|
canvasImg.Image = finalImage
|
|
canvasImg.Refresh()
|
|
|
|
progressBar.SetText("Generation Complete!")
|
|
})
|
|
}()
|
|
})
|
|
|
|
widthEntry := widget.NewEntry()
|
|
widthEntry.SetText(strconv.Itoa(settings.Width))
|
|
widthEntry.OnChanged = func(s string) {
|
|
val, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
errorLabel.Text = "Width must be a number"
|
|
errorLabel.Show()
|
|
generateBtn.Disable()
|
|
return
|
|
}
|
|
errorLabel.Hide()
|
|
generateBtn.Enable()
|
|
settings.Width = val
|
|
}
|
|
heightEntry := widget.NewEntry()
|
|
heightEntry.SetText(strconv.Itoa(settings.Height))
|
|
heightEntry.OnChanged = func(s string) {
|
|
val, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
errorLabel.Text = "Height must be a number"
|
|
errorLabel.Show()
|
|
generateBtn.Disable()
|
|
return
|
|
}
|
|
errorLabel.Hide()
|
|
generateBtn.Enable()
|
|
settings.Height = val
|
|
}
|
|
|
|
seedEntry := widget.NewEntry()
|
|
seedEntry.SetText(strconv.FormatInt(settings.Seed, 10))
|
|
seedEntry.OnChanged = func(s string) {
|
|
val, err := strconv.ParseInt(s, 10, 64)
|
|
if err != nil {
|
|
errorLabel.Text = "Seed must be a number"
|
|
errorLabel.Show()
|
|
generateBtn.Disable()
|
|
return
|
|
}
|
|
errorLabel.Hide()
|
|
generateBtn.Enable()
|
|
settings.Seed = val
|
|
}
|
|
|
|
randomizeBtn := widget.NewButton("Randomize", func() {
|
|
settings.Seed = time.Now().UnixNano()
|
|
seedEntry.SetText(strconv.FormatInt(settings.Seed, 10))
|
|
})
|
|
|
|
terrainTab := container.NewTabItem("Terrain", container.NewVBox(
|
|
detailLabel,
|
|
detailSlider,
|
|
roughnessLabel,
|
|
roughnessSlider,
|
|
|
|
widget.NewLabel(""), // Spacer
|
|
|
|
minTreeSizeLabel,
|
|
minTreeSizeSlider,
|
|
maxTreeSizeLabel,
|
|
maxTreeSizeSlider,
|
|
treeCoverageLabel,
|
|
treeCoverageSlider,
|
|
treeClumpinessLabel,
|
|
treeClumpinessSlider,
|
|
))
|
|
|
|
waterTab := container.NewTabItem("Water", container.NewVBox(
|
|
lakesLabel,
|
|
lakesSlider,
|
|
lakeSizeLowerLabel,
|
|
lakeSizeLowerSlider,
|
|
lakeSizeUpperLabel,
|
|
lakeSizeUpperSlider,
|
|
|
|
widget.NewLabel(""), // Spacer
|
|
|
|
riversLabel,
|
|
riversSlider,
|
|
minRiverWidthLabel,
|
|
minRiverWidthSlider,
|
|
maxRiverWidthLabel,
|
|
maxRiverWidthSlider,
|
|
riverCurvynessLabel,
|
|
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(
|
|
widget.NewLabel("Width:"),
|
|
widthEntry, widget.NewLabel("Height:"),
|
|
heightEntry,
|
|
widget.NewLabel("Seed:"),
|
|
seedEntry,
|
|
randomizeBtn,
|
|
generateBtn,
|
|
errorLabel,
|
|
progressBar,
|
|
widget.NewSeparator(),
|
|
exportCanvasBtn,
|
|
exportHeightmapBtn,
|
|
exportMasksBtn,
|
|
))
|
|
|
|
tabs := container.NewAppTabs(
|
|
imageTab,
|
|
terrainTab,
|
|
waterTab,
|
|
roadsTab,
|
|
)
|
|
|
|
left := container.NewVBox(
|
|
widget.NewLabel("RPG City Maker Reborn"),
|
|
tabs,
|
|
)
|
|
|
|
right := container.NewGridWithRows(2,
|
|
canvasImg,
|
|
heightmapImg,
|
|
)
|
|
|
|
split := container.NewHSplit(
|
|
left,
|
|
right,
|
|
)
|
|
split.SetOffset(0.3)
|
|
|
|
w.SetContent(split)
|
|
w.ShowAndRun()
|
|
}
|
|
func getImageData(img image.Image, format string) (*bytes.Buffer, error) {
|
|
buf := new(bytes.Buffer)
|
|
var err error
|
|
switch format {
|
|
case "PNG":
|
|
err = png.Encode(buf, img)
|
|
case "JPG":
|
|
err = jpeg.Encode(buf, img, nil)
|
|
case "WEBP":
|
|
err = webp.Encode(buf, img, &webp.Options{Lossless: true})
|
|
}
|
|
return buf, err
|
|
}
|
|
|
|
func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg image.Image, settings *Settings, lakes [][]image.Point, riverPixels, treePixels, roadPixels []image.Point) {
|
|
fileNameEntry := widget.NewEntry()
|
|
fileNameEntry.SetPlaceHolder("masks_folder")
|
|
|
|
formatSelect := widget.NewSelect([]string{"PNG", "JPG", "WEBP"}, nil)
|
|
formatSelect.SetSelected("PNG")
|
|
|
|
packageSelect := widget.NewSelect([]string{"Folder", "tar.gz", "zip"}, nil)
|
|
packageSelect.SetSelected("Folder")
|
|
|
|
pathLabel := widget.NewLabel(settings.LastExportPath)
|
|
|
|
browseBtn := widget.NewButton("Browse", func() {
|
|
dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) {
|
|
if err != nil {
|
|
log.Println("Error opening folder dialog:", err)
|
|
return
|
|
}
|
|
if uri == nil {
|
|
return
|
|
}
|
|
settings.LastExportPath = uri.Path()
|
|
pathLabel.SetText(settings.LastExportPath)
|
|
}, win)
|
|
})
|
|
|
|
content := container.NewVBox(
|
|
widget.NewLabel("Save to:"),
|
|
container.NewBorder(nil, nil, nil, browseBtn, pathLabel),
|
|
widget.NewLabel("Folder Name:"),
|
|
fileNameEntry,
|
|
widget.NewLabel("Format:"),
|
|
formatSelect,
|
|
widget.NewLabel("Packaging:"),
|
|
packageSelect,
|
|
)
|
|
|
|
saveDialog := dialog.NewCustom("Export Masks", "Cancel", content, win)
|
|
|
|
saveBtn := widget.NewButton("Save", func() {
|
|
folderName := fileNameEntry.Text
|
|
if folderName == "" {
|
|
return
|
|
}
|
|
imgFormat := strings.ToLower(formatSelect.Selected)
|
|
bounds := canvasImg.Bounds()
|
|
|
|
// Create mask images
|
|
lakeMask := image.NewGray(bounds)
|
|
for _, lake := range lakes {
|
|
for _, p := range lake {
|
|
lakeMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
|
|
}
|
|
}
|
|
riverMask := image.NewGray(bounds)
|
|
for _, p := range riverPixels {
|
|
riverMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
|
|
}
|
|
treeMask := image.NewGray(bounds)
|
|
for _, p := range treePixels {
|
|
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{
|
|
"canvas." + imgFormat: canvasImg,
|
|
"heightmap." + imgFormat: heightmapImg,
|
|
"lakes_mask." + imgFormat: lakeMask,
|
|
"rivers_mask." + imgFormat: riverMask,
|
|
"trees_mask." + imgFormat: treeMask,
|
|
"roads_mask." + imgFormat: roadMask,
|
|
}
|
|
|
|
switch packageSelect.Selected {
|
|
case "Folder":
|
|
exportPath := filepath.Join(pathLabel.Text, folderName)
|
|
if err := os.MkdirAll(exportPath, 0755); err != nil {
|
|
log.Println("Error creating directory:", err)
|
|
return
|
|
}
|
|
settings.LastExportPath = exportPath
|
|
for name, img := range imagesToSave {
|
|
saveImage(img, filepath.Join(exportPath, name))
|
|
}
|
|
case "tar.gz":
|
|
filePath := filepath.Join(pathLabel.Text, folderName+".tar.gz")
|
|
settings.LastExportPath = filepath.Dir(filePath)
|
|
file, err := os.Create(filePath)
|
|
if err != nil {
|
|
log.Println("Error creating archive:", err)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
gw := gzip.NewWriter(file)
|
|
defer gw.Close()
|
|
tw := tar.NewWriter(gw)
|
|
defer tw.Close()
|
|
|
|
for name, img := range imagesToSave {
|
|
buf, err := getImageData(img, formatSelect.Selected)
|
|
if err != nil {
|
|
log.Printf("Error encoding image %s: %v\n", name, err)
|
|
continue
|
|
}
|
|
hdr := &tar.Header{
|
|
Name: name,
|
|
Mode: 0644,
|
|
Size: int64(buf.Len()),
|
|
}
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
log.Printf("Error writing tar header for %s: %v\n", name, err)
|
|
continue
|
|
}
|
|
if _, err := tw.Write(buf.Bytes()); err != nil {
|
|
log.Printf("Error writing tar data for %s: %v\n", name, err)
|
|
}
|
|
}
|
|
case "zip":
|
|
filePath := filepath.Join(pathLabel.Text, folderName+".zip")
|
|
settings.LastExportPath = filepath.Dir(filePath)
|
|
file, err := os.Create(filePath)
|
|
if err != nil {
|
|
log.Println("Error creating archive:", err)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
zw := zip.NewWriter(file)
|
|
defer zw.Close()
|
|
|
|
for name, img := range imagesToSave {
|
|
buf, err := getImageData(img, formatSelect.Selected)
|
|
if err != nil {
|
|
log.Printf("Error encoding image %s: %v\n", name, err)
|
|
continue
|
|
}
|
|
f, err := zw.Create(name)
|
|
if err != nil {
|
|
log.Printf("Error creating zip entry for %s: %v\n", name, err)
|
|
continue
|
|
}
|
|
if _, err := f.Write(buf.Bytes()); err != nil {
|
|
log.Printf("Error writing zip data for %s: %v\n", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
saveDialog.Hide()
|
|
})
|
|
saveBtn.Disable()
|
|
|
|
fileNameEntry.OnChanged = func(text string) {
|
|
if text == "" {
|
|
saveBtn.Disable()
|
|
} else {
|
|
saveBtn.Enable()
|
|
}
|
|
}
|
|
|
|
saveDialog.SetButtons([]fyne.CanvasObject{
|
|
widget.NewButton("Cancel", func() {
|
|
saveDialog.Hide()
|
|
}),
|
|
saveBtn,
|
|
})
|
|
|
|
saveDialog.Show()
|
|
}
|
|
|
|
func saveImage(img image.Image, path string) {
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
log.Println("Error creating file:", err)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
ext := strings.ToLower(filepath.Ext(path))
|
|
switch ext {
|
|
case ".png":
|
|
err = png.Encode(file, img)
|
|
case ".jpg":
|
|
err = jpeg.Encode(file, img, nil)
|
|
case ".webp":
|
|
err = webp.Encode(file, img, &webp.Options{Lossless: true})
|
|
}
|
|
if err != nil {
|
|
log.Println("Failed to encode image:", err)
|
|
}
|
|
}
|
|
|
|
func showSaveDialog(win fyne.Window, img image.Image, settings *Settings) {
|
|
fileNameEntry := widget.NewEntry()
|
|
fileNameEntry.SetPlaceHolder("image")
|
|
|
|
formatSelect := widget.NewSelect([]string{"PNG", "JPG", "WEBP"}, nil)
|
|
formatSelect.SetSelected("PNG")
|
|
|
|
pathLabel := widget.NewLabel(settings.LastExportPath)
|
|
|
|
browseBtn := widget.NewButton("Browse", func() {
|
|
dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) {
|
|
if err != nil {
|
|
log.Println("Error opening folder dialog:", err)
|
|
return
|
|
}
|
|
if uri == nil {
|
|
return
|
|
}
|
|
settings.LastExportPath = uri.Path()
|
|
pathLabel.SetText(settings.LastExportPath)
|
|
}, win)
|
|
})
|
|
|
|
content := container.NewVBox(
|
|
widget.NewLabel("Save to:"),
|
|
container.NewBorder(nil, nil, nil, browseBtn, pathLabel),
|
|
widget.NewLabel("Filename:"),
|
|
fileNameEntry,
|
|
widget.NewLabel("Format:"),
|
|
formatSelect,
|
|
)
|
|
|
|
saveDialog := dialog.NewCustom("Export Image", "Cancel", content, win)
|
|
|
|
saveBtn := widget.NewButton("Save", func() {
|
|
fileName := fileNameEntry.Text
|
|
if fileName == "" {
|
|
return
|
|
}
|
|
|
|
filePath := filepath.Join(pathLabel.Text, fileName+"."+strings.ToLower(formatSelect.Selected))
|
|
settings.LastExportPath = filepath.Dir(filePath)
|
|
saveImage(img, filePath)
|
|
saveDialog.Hide()
|
|
})
|
|
saveBtn.Disable()
|
|
|
|
fileNameEntry.OnChanged = func(text string) {
|
|
if text == "" {
|
|
saveBtn.Disable()
|
|
} else {
|
|
saveBtn.Enable()
|
|
}
|
|
}
|
|
|
|
saveDialog.SetButtons([]fyne.CanvasObject{
|
|
widget.NewButton("Cancel", func() {
|
|
saveDialog.Hide()
|
|
}),
|
|
saveBtn,
|
|
})
|
|
|
|
saveDialog.Show()
|
|
}
|