Merge pull request 'Added Walls to Main' (#1) from walls into main
Reviewed-on: https://codeberg.org/Grimsace/RPG_City_Maker_Reborn/pulls/1
This commit is contained in:
+1077
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
module rpg_city_maker_reborn
|
module rpg_city_maker_reborn
|
||||||
|
|
||||||
go 1.25.6
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
fyne.io/fyne/v2 v2.7.2
|
fyne.io/fyne/v2 v2.7.2
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ func main() {
|
|||||||
var roadMask *PixelMask
|
var roadMask *PixelMask
|
||||||
var bridgeMask *PixelMask
|
var bridgeMask *PixelMask
|
||||||
var exitRoadMask *PixelMask
|
var exitRoadMask *PixelMask
|
||||||
|
var wallMask *PixelMask
|
||||||
|
var turretMask *PixelMask
|
||||||
|
|
||||||
// Set up application configuration directory
|
// Set up application configuration directory
|
||||||
configDir, err := os.UserConfigDir()
|
configDir, err := os.UserConfigDir()
|
||||||
@@ -231,40 +233,105 @@ func main() {
|
|||||||
if riverMask != nil {
|
if riverMask != nil {
|
||||||
waterMask.Merge(riverMask)
|
waterMask.Merge(riverMask)
|
||||||
}
|
}
|
||||||
// Step 4: Generating Roads
|
// Step 4: Preparing Road Nodes
|
||||||
|
var roadNodes []*PointOfInterest
|
||||||
|
var roadTarget int
|
||||||
|
var edgeToEdgeOnly bool
|
||||||
|
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
||||||
|
pois []*PointOfInterest
|
||||||
|
target int
|
||||||
|
edgeToEdge bool
|
||||||
|
} {
|
||||||
|
pois, target, edgeToEdge := PrepareRoadNodes(settings.Width, settings.Height, settings, waterMask, seedProvider.Next())
|
||||||
|
return struct {
|
||||||
|
pois []*PointOfInterest
|
||||||
|
target int
|
||||||
|
edgeToEdge bool
|
||||||
|
}{pois: pois, target: target, edgeToEdge: edgeToEdge}
|
||||||
|
}); ok {
|
||||||
|
roadNodes, roadTarget, edgeToEdgeOnly = out.pois, out.target, out.edgeToEdge
|
||||||
|
} else {
|
||||||
|
log.Println("PrepareRoadNodes timed out after 1 minute; continuing.")
|
||||||
|
roadNodes = nil
|
||||||
|
roadTarget = 0
|
||||||
|
edgeToEdgeOnly = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Generating Fortifications
|
||||||
|
var wallLayout *FortificationLayout
|
||||||
|
fortBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
||||||
|
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
||||||
|
layout *FortificationLayout
|
||||||
|
} {
|
||||||
|
layout, _ := GenerateFortifications(fortBase, settings.Width, settings.Height, settings, waterMask, roadNodes, seedProvider.Next())
|
||||||
|
return struct {
|
||||||
|
layout *FortificationLayout
|
||||||
|
}{layout: layout}
|
||||||
|
}); ok {
|
||||||
|
wallLayout = out.layout
|
||||||
|
if wallLayout != nil {
|
||||||
|
wallMask = wallLayout.Mask
|
||||||
|
} else {
|
||||||
|
wallMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
|
}
|
||||||
|
finalImage = fortBase
|
||||||
|
} else {
|
||||||
|
log.Println("GenerateFortifications timed out after 1 minute; continuing.")
|
||||||
|
wallLayout = &FortificationLayout{Mask: NewPixelMask(settings.Width, settings.Height)}
|
||||||
|
wallMask = wallLayout.Mask
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6: Generating Roads
|
||||||
var roadAnchors []image.Point
|
var roadAnchors []image.Point
|
||||||
|
var roadList []*Road
|
||||||
roadBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
roadBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
||||||
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
||||||
rd *PixelMask
|
rd *PixelMask
|
||||||
br *PixelMask
|
br *PixelMask
|
||||||
ex *PixelMask
|
ex *PixelMask
|
||||||
anc []image.Point
|
anc []image.Point
|
||||||
|
rl []*Road
|
||||||
} {
|
} {
|
||||||
rd, br, ex, anc := GenerateRoads(roadBase, settings.Width, settings.Height, settings, waterMask, seedProvider.Next())
|
rd, br, ex, anc, rl := GenerateRoadsWithPOIs(roadBase, settings.Width, settings.Height, settings, waterMask, wallLayout, roadNodes, roadTarget, edgeToEdgeOnly, seedProvider.Next())
|
||||||
return struct {
|
return struct {
|
||||||
rd *PixelMask
|
rd *PixelMask
|
||||||
br *PixelMask
|
br *PixelMask
|
||||||
ex *PixelMask
|
ex *PixelMask
|
||||||
anc []image.Point
|
anc []image.Point
|
||||||
}{rd: rd, br: br, ex: ex, anc: anc}
|
rl []*Road
|
||||||
|
}{rd: rd, br: br, ex: ex, anc: anc, rl: rl}
|
||||||
}); ok {
|
}); ok {
|
||||||
roadMask, bridgeMask, exitRoadMask, roadAnchors = out.rd, out.br, out.ex, out.anc
|
roadMask, bridgeMask, exitRoadMask, roadAnchors, roadList = out.rd, out.br, out.ex, out.anc, out.rl
|
||||||
|
drawWallMask(roadBase, wallMask)
|
||||||
|
turretMask = GenerateTurrets(roadBase, settings.Width, settings.Height, settings, wallLayout, waterMask, roadMask, roadList)
|
||||||
finalImage = roadBase
|
finalImage = roadBase
|
||||||
} else {
|
} else {
|
||||||
log.Println("GenerateRoads timed out after 1 minute; continuing.")
|
log.Println("GenerateRoads timed out after 1 minute; continuing.")
|
||||||
roadMask = NewPixelMask(settings.Width, settings.Height)
|
roadMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
bridgeMask = NewPixelMask(settings.Width, settings.Height)
|
bridgeMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
exitRoadMask = NewPixelMask(settings.Width, settings.Height)
|
exitRoadMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
|
turretMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
roadAnchors = nil
|
roadAnchors = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Generating Buildings
|
placementMask := cloneMask(roadMask)
|
||||||
|
if placementMask == nil {
|
||||||
|
placementMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
|
}
|
||||||
|
if wallMask != nil {
|
||||||
|
placementMask.Merge(wallMask)
|
||||||
|
}
|
||||||
|
if turretMask != nil {
|
||||||
|
placementMask.Merge(turretMask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 7: Generating Buildings
|
||||||
buildingBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
buildingBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
||||||
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
||||||
blds [][]image.Point
|
blds [][]image.Point
|
||||||
bmsk *PixelMask
|
bmsk *PixelMask
|
||||||
} {
|
} {
|
||||||
blds, bmsk := GenerateBuildings(buildingBase, settings.Width, settings.Height, settings, roadAnchors, waterMask, roadMask, exitRoadMask, seedProvider.Next())
|
blds, bmsk := GenerateBuildings(buildingBase, settings.Width, settings.Height, settings, roadAnchors, waterMask, placementMask, exitRoadMask, seedProvider.Next())
|
||||||
return struct {
|
return struct {
|
||||||
blds [][]image.Point
|
blds [][]image.Point
|
||||||
bmsk *PixelMask
|
bmsk *PixelMask
|
||||||
@@ -278,10 +345,10 @@ func main() {
|
|||||||
buildingMask = NewPixelMask(settings.Width, settings.Height)
|
buildingMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6: Generating Trees
|
// Step 8: Generating Trees
|
||||||
treeBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
treeBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
||||||
if out, ok := runWithTimeout(generationStepTimeout, func() *PixelMask {
|
if out, ok := runWithTimeout(generationStepTimeout, func() *PixelMask {
|
||||||
return GenerateTrees(treeBase, waterMask, roadMask, buildingMask, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next())
|
return GenerateTrees(treeBase, waterMask, placementMask, buildingMask, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next())
|
||||||
}); ok {
|
}); ok {
|
||||||
treeMask = out
|
treeMask = out
|
||||||
finalImage = treeBase
|
finalImage = treeBase
|
||||||
@@ -296,8 +363,8 @@ func main() {
|
|||||||
// Step 8: Flattening Building Areas
|
// Step 8: Flattening Building Areas
|
||||||
flattenedBuildingHeightmap := FlattenBuildingAreas(darkenedHeightmap.(*image.RGBA), buildings, settings.Width, settings.Height)
|
flattenedBuildingHeightmap := FlattenBuildingAreas(darkenedHeightmap.(*image.RGBA), buildings, settings.Width, settings.Height)
|
||||||
|
|
||||||
// Step 9: Flattening Road Areas
|
// Step 9: Flattening Road and Wall Areas
|
||||||
flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, roadMask)
|
flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, placementMask)
|
||||||
|
|
||||||
// Step 10: Applying Roughness
|
// Step 10: Applying Roughness
|
||||||
compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness)
|
compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness)
|
||||||
@@ -613,6 +680,131 @@ func main() {
|
|||||||
settings.MinRoadAngle = val
|
settings.MinRoadAngle = val
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
minWallWidthSlider := newNumericInputSliderWithStep(minWallWidthPercent, maxWallWidthPercent, settings.MinWallWidth, wallWidthPercentStep, "%.1f%%", "Min Wall Width")
|
||||||
|
minWallWidthSlider.entry.OnChanged = func(s string) {
|
||||||
|
minWallWidthSlider.validate(s, func(hasError bool) {
|
||||||
|
errorStates["minWallWidth"] = hasError
|
||||||
|
updateGenerateBtnState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
minWallWidthSlider.value.AddListener(binding.NewDataListener(func() {
|
||||||
|
val, _ := minWallWidthSlider.value.Get()
|
||||||
|
settings.MinWallWidth = val
|
||||||
|
}))
|
||||||
|
|
||||||
|
maxWallWidthSlider := newNumericInputSliderWithStep(minWallWidthPercent, maxWallWidthPercent, settings.MaxWallWidth, wallWidthPercentStep, "%.1f%%", "Max Wall Width")
|
||||||
|
maxWallWidthSlider.entry.OnChanged = func(s string) {
|
||||||
|
maxWallWidthSlider.validate(s, func(hasError bool) {
|
||||||
|
errorStates["maxWallWidth"] = hasError
|
||||||
|
updateGenerateBtnState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
maxWallWidthSlider.value.AddListener(binding.NewDataListener(func() {
|
||||||
|
val, _ := maxWallWidthSlider.value.Get()
|
||||||
|
settings.MaxWallWidth = val
|
||||||
|
}))
|
||||||
|
|
||||||
|
numWallsSlider := newNumericInputSlider(0, 5, float64(settings.NumWalls), "%.0f", "Number of Walls")
|
||||||
|
numWallsSlider.entry.OnChanged = func(s string) {
|
||||||
|
numWallsSlider.validate(s, func(hasError bool) {
|
||||||
|
errorStates["numWalls"] = hasError
|
||||||
|
updateGenerateBtnState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
numWallsSlider.value.AddListener(binding.NewDataListener(func() {
|
||||||
|
val, _ := numWallsSlider.value.Get()
|
||||||
|
settings.NumWalls = int(val)
|
||||||
|
}))
|
||||||
|
|
||||||
|
cityCoverageSlider := newNumericInputSlider(1, 100, settings.CityCoverage, "%.0f%%", "City Coverage")
|
||||||
|
cityCoverageSlider.entry.OnChanged = func(s string) {
|
||||||
|
cityCoverageSlider.validate(s, func(hasError bool) {
|
||||||
|
errorStates["cityCoverage"] = hasError
|
||||||
|
updateGenerateBtnState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
cityCoverageSlider.value.AddListener(binding.NewDataListener(func() {
|
||||||
|
val, _ := cityCoverageSlider.value.Get()
|
||||||
|
settings.CityCoverage = val
|
||||||
|
}))
|
||||||
|
|
||||||
|
wallCurvynessSlider := newNumericInputSlider(0, 100, settings.WallCurvyness, "%.0f%%", "Wall Curvyness")
|
||||||
|
wallCurvynessSlider.entry.OnChanged = func(s string) {
|
||||||
|
wallCurvynessSlider.validate(s, func(hasError bool) {
|
||||||
|
errorStates["wallCurvyness"] = hasError
|
||||||
|
updateGenerateBtnState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
wallCurvynessSlider.value.AddListener(binding.NewDataListener(func() {
|
||||||
|
val, _ := wallCurvynessSlider.value.Get()
|
||||||
|
settings.WallCurvyness = val
|
||||||
|
}))
|
||||||
|
|
||||||
|
showTurretsCheck := widget.NewCheck("Show Turrets", func(v bool) {
|
||||||
|
settings.ShowTurrets = v
|
||||||
|
})
|
||||||
|
showTurretsCheck.SetChecked(settings.ShowTurrets)
|
||||||
|
|
||||||
|
turretSizeSlider := newNumericInputSliderWithStep(minTurretSizePercent, maxTurretSizePercent, settings.TurretSize, turretSizePercentStep, "%.1f%%", "Turret Size")
|
||||||
|
turretSizeSlider.entry.OnChanged = func(s string) {
|
||||||
|
turretSizeSlider.validate(s, func(hasError bool) {
|
||||||
|
errorStates["turretSize"] = hasError
|
||||||
|
updateGenerateBtnState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
turretSizeSlider.value.AddListener(binding.NewDataListener(func() {
|
||||||
|
val, _ := turretSizeSlider.value.Get()
|
||||||
|
settings.TurretSize = val
|
||||||
|
}))
|
||||||
|
|
||||||
|
turretShapeLabel := widget.NewLabel("Turret Shape:")
|
||||||
|
turretShapeSelect := widget.NewSelect([]string{"circular", "square"}, func(s string) {
|
||||||
|
settings.TurretShape = s
|
||||||
|
})
|
||||||
|
turretShapeSelect.SetSelected(settings.TurretShape)
|
||||||
|
|
||||||
|
turretSpacingSlider := newNumericInputSlider(0, 100, settings.TurretSpacing, "%.0f%%", "Turret Spacing")
|
||||||
|
turretSpacingSlider.entry.OnChanged = func(s string) {
|
||||||
|
turretSpacingSlider.validate(s, func(hasError bool) {
|
||||||
|
errorStates["turretSpacing"] = hasError
|
||||||
|
updateGenerateBtnState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
turretSpacingSlider.value.AddListener(binding.NewDataListener(func() {
|
||||||
|
val, _ := turretSpacingSlider.value.Get()
|
||||||
|
settings.TurretSpacing = val
|
||||||
|
}))
|
||||||
|
|
||||||
|
gateSpacingSlider := newNumericInputSlider(0, 100, settings.GateSpacing, "%.0f%%", "Gate Spacing")
|
||||||
|
gateSpacingSlider.entry.OnChanged = func(s string) {
|
||||||
|
gateSpacingSlider.validate(s, func(hasError bool) {
|
||||||
|
errorStates["gateSpacing"] = hasError
|
||||||
|
updateGenerateBtnState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
gateSpacingSlider.value.AddListener(binding.NewDataListener(func() {
|
||||||
|
val, _ := gateSpacingSlider.value.Get()
|
||||||
|
settings.GateSpacing = val
|
||||||
|
}))
|
||||||
|
|
||||||
|
turretControls := container.NewVBox(
|
||||||
|
turretSizeSlider,
|
||||||
|
turretShapeLabel,
|
||||||
|
turretShapeSelect,
|
||||||
|
turretSpacingSlider,
|
||||||
|
)
|
||||||
|
if !settings.ShowTurrets {
|
||||||
|
turretControls.Hide()
|
||||||
|
}
|
||||||
|
showTurretsCheck.OnChanged = func(v bool) {
|
||||||
|
settings.ShowTurrets = v
|
||||||
|
if v {
|
||||||
|
turretControls.Show()
|
||||||
|
} else {
|
||||||
|
turretControls.Hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create UI elements for error display and action buttons
|
// Create UI elements for error display and action buttons
|
||||||
errorLabel := widget.NewLabel("")
|
errorLabel := widget.NewLabel("")
|
||||||
errorLabel.Wrapping = fyne.TextWrapWord
|
errorLabel.Wrapping = fyne.TextWrapWord
|
||||||
@@ -631,7 +823,7 @@ func main() {
|
|||||||
showSaveDialog(w, bumpmapImg.Image, settings)
|
showSaveDialog(w, bumpmapImg.Image, settings)
|
||||||
})
|
})
|
||||||
exportMasksBtn := widget.NewButton("Export Masks", func() {
|
exportMasksBtn := widget.NewButton("Export Masks", func() {
|
||||||
showMasksSaveDialog(w, canvasImg.Image, heightmapImg.Image, bumpmapImg.Image, settings, lakes, riverMask, treeMask, roadMask, bridgeMask, buildingMask)
|
showMasksSaveDialog(w, canvasImg.Image, heightmapImg.Image, bumpmapImg.Image, settings, lakes, riverMask, treeMask, roadMask, bridgeMask, wallMask, turretMask, buildingMask)
|
||||||
})
|
})
|
||||||
// Main generation button and logic
|
// Main generation button and logic
|
||||||
generateBtn = widget.NewButton("Generate", func() {
|
generateBtn = widget.NewButton("Generate", func() {
|
||||||
@@ -660,16 +852,16 @@ func main() {
|
|||||||
}
|
}
|
||||||
// Step 1: Generating Heightmap
|
// Step 1: Generating Heightmap
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressLabel.SetText("Step 1 of 11: Generating Heightmap")
|
progressLabel.SetText("Step 1 of 13: Generating Heightmap")
|
||||||
progressBar.SetValue(1.0 / 11.0)
|
progressBar.SetValue(1.0 / 13.0)
|
||||||
})
|
})
|
||||||
noiseImg := GenerateHeightmap(settings.Width, settings.Height, int(settings.Detail), 100.0, seedProvider.Next())
|
noiseImg := GenerateHeightmap(settings.Width, settings.Height, int(settings.Detail), 100.0, seedProvider.Next())
|
||||||
|
|
||||||
// Step 2: Generating Lakes
|
// Step 2: Generating Lakes
|
||||||
|
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressLabel.SetText("Step 2 of 11: Generating Lakes")
|
progressLabel.SetText("Step 2 of 13: Generating Lakes")
|
||||||
progressBar.SetValue(2.0 / 11.0)
|
progressBar.SetValue(2.0 / 13.0)
|
||||||
})
|
})
|
||||||
var lakeImage image.Image = image.NewRGBA(image.Rect(0, 0, settings.Width, settings.Height))
|
var lakeImage image.Image = image.NewRGBA(image.Rect(0, 0, settings.Width, settings.Height))
|
||||||
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
||||||
@@ -693,8 +885,8 @@ func main() {
|
|||||||
// Step 3: Generating Rivers
|
// Step 3: Generating Rivers
|
||||||
|
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressLabel.SetText("Step 3 of 11: Generating Rivers")
|
progressLabel.SetText("Step 3 of 13: Generating Rivers")
|
||||||
progressBar.SetValue(3.0 / 11.0)
|
progressBar.SetValue(3.0 / 13.0)
|
||||||
})
|
})
|
||||||
riverBase := cloneToRGBA(lakeImage, settings.Width, settings.Height)
|
riverBase := cloneToRGBA(lakeImage, settings.Width, settings.Height)
|
||||||
finalImage := riverBase
|
finalImage := riverBase
|
||||||
@@ -720,29 +912,94 @@ func main() {
|
|||||||
waterMask.Merge(riverMask)
|
waterMask.Merge(riverMask)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Generating Roads
|
// Step 4: Preparing Road Nodes
|
||||||
|
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressLabel.SetText("Step 4 of 11: Generating Roads")
|
progressLabel.SetText("Step 4 of 13: Preparing Road Nodes")
|
||||||
progressBar.SetValue(4.0 / 11.0)
|
progressBar.SetValue(4.0 / 13.0)
|
||||||
|
})
|
||||||
|
var roadNodes []*PointOfInterest
|
||||||
|
var roadTarget int
|
||||||
|
var edgeToEdgeOnly bool
|
||||||
|
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
||||||
|
pois []*PointOfInterest
|
||||||
|
target int
|
||||||
|
edgeToEdge bool
|
||||||
|
} {
|
||||||
|
pois, target, edgeToEdge := PrepareRoadNodes(settings.Width, settings.Height, settings, waterMask, seedProvider.Next())
|
||||||
|
return struct {
|
||||||
|
pois []*PointOfInterest
|
||||||
|
target int
|
||||||
|
edgeToEdge bool
|
||||||
|
}{pois: pois, target: target, edgeToEdge: edgeToEdge}
|
||||||
|
}); ok {
|
||||||
|
roadNodes, roadTarget, edgeToEdgeOnly = out.pois, out.target, out.edgeToEdge
|
||||||
|
} else {
|
||||||
|
log.Println("PrepareRoadNodes timed out after 1 minute; continuing.")
|
||||||
|
addTimeout("Road Nodes")
|
||||||
|
roadNodes = nil
|
||||||
|
roadTarget = 0
|
||||||
|
edgeToEdgeOnly = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Generating Fortifications
|
||||||
|
|
||||||
|
fyne.Do(func() {
|
||||||
|
progressLabel.SetText("Step 5 of 13: Generating Fortifications")
|
||||||
|
progressBar.SetValue(5.0 / 13.0)
|
||||||
|
})
|
||||||
|
var wallLayout *FortificationLayout
|
||||||
|
fortBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
||||||
|
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
||||||
|
layout *FortificationLayout
|
||||||
|
} {
|
||||||
|
layout, _ := GenerateFortifications(fortBase, settings.Width, settings.Height, settings, waterMask, roadNodes, seedProvider.Next())
|
||||||
|
return struct {
|
||||||
|
layout *FortificationLayout
|
||||||
|
}{layout: layout}
|
||||||
|
}); ok {
|
||||||
|
wallLayout = out.layout
|
||||||
|
if wallLayout != nil {
|
||||||
|
wallMask = wallLayout.Mask
|
||||||
|
} else {
|
||||||
|
wallMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
|
}
|
||||||
|
finalImage = fortBase
|
||||||
|
} else {
|
||||||
|
log.Println("GenerateFortifications timed out after 1 minute; continuing.")
|
||||||
|
addTimeout("Fortifications")
|
||||||
|
wallLayout = &FortificationLayout{Mask: NewPixelMask(settings.Width, settings.Height)}
|
||||||
|
wallMask = wallLayout.Mask
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6: Generating Roads
|
||||||
|
|
||||||
|
fyne.Do(func() {
|
||||||
|
progressLabel.SetText("Step 6 of 13: Generating Roads")
|
||||||
|
progressBar.SetValue(6.0 / 13.0)
|
||||||
})
|
})
|
||||||
var roadAnchors []image.Point
|
var roadAnchors []image.Point
|
||||||
|
var roadList []*Road
|
||||||
roadBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
roadBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
||||||
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
||||||
rd *PixelMask
|
rd *PixelMask
|
||||||
br *PixelMask
|
br *PixelMask
|
||||||
ex *PixelMask
|
ex *PixelMask
|
||||||
anc []image.Point
|
anc []image.Point
|
||||||
|
rl []*Road
|
||||||
} {
|
} {
|
||||||
rd, br, ex, anc := GenerateRoads(roadBase, settings.Width, settings.Height, settings, waterMask, seedProvider.Next())
|
rd, br, ex, anc, rl := GenerateRoadsWithPOIs(roadBase, settings.Width, settings.Height, settings, waterMask, wallLayout, roadNodes, roadTarget, edgeToEdgeOnly, seedProvider.Next())
|
||||||
return struct {
|
return struct {
|
||||||
rd *PixelMask
|
rd *PixelMask
|
||||||
br *PixelMask
|
br *PixelMask
|
||||||
ex *PixelMask
|
ex *PixelMask
|
||||||
anc []image.Point
|
anc []image.Point
|
||||||
}{rd: rd, br: br, ex: ex, anc: anc}
|
rl []*Road
|
||||||
|
}{rd: rd, br: br, ex: ex, anc: anc, rl: rl}
|
||||||
}); ok {
|
}); ok {
|
||||||
roadMask, bridgeMask, exitRoadMask, roadAnchors = out.rd, out.br, out.ex, out.anc
|
roadMask, bridgeMask, exitRoadMask, roadAnchors, roadList = out.rd, out.br, out.ex, out.anc, out.rl
|
||||||
|
drawWallMask(roadBase, wallMask)
|
||||||
|
turretMask = GenerateTurrets(roadBase, settings.Width, settings.Height, settings, wallLayout, waterMask, roadMask, roadList)
|
||||||
finalImage = roadBase
|
finalImage = roadBase
|
||||||
} else {
|
} else {
|
||||||
log.Println("GenerateRoads timed out after 1 minute; continuing.")
|
log.Println("GenerateRoads timed out after 1 minute; continuing.")
|
||||||
@@ -750,21 +1007,32 @@ func main() {
|
|||||||
roadMask = NewPixelMask(settings.Width, settings.Height)
|
roadMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
bridgeMask = NewPixelMask(settings.Width, settings.Height)
|
bridgeMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
exitRoadMask = NewPixelMask(settings.Width, settings.Height)
|
exitRoadMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
|
turretMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
roadAnchors = nil
|
roadAnchors = nil
|
||||||
}
|
}
|
||||||
|
placementMask := cloneMask(roadMask)
|
||||||
|
if placementMask == nil {
|
||||||
|
placementMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
|
}
|
||||||
|
if wallMask != nil {
|
||||||
|
placementMask.Merge(wallMask)
|
||||||
|
}
|
||||||
|
if turretMask != nil {
|
||||||
|
placementMask.Merge(turretMask)
|
||||||
|
}
|
||||||
|
|
||||||
// Step 5: Generating Buildings
|
// Step 7: Generating Buildings
|
||||||
|
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressLabel.SetText("Step 5 of 11: Generating Buildings")
|
progressLabel.SetText("Step 7 of 13: Generating Buildings")
|
||||||
progressBar.SetValue(5.0 / 11.0)
|
progressBar.SetValue(7.0 / 13.0)
|
||||||
})
|
})
|
||||||
buildingBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
buildingBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
||||||
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
if out, ok := runWithTimeout(generationStepTimeout, func() struct {
|
||||||
blds [][]image.Point
|
blds [][]image.Point
|
||||||
bmsk *PixelMask
|
bmsk *PixelMask
|
||||||
} {
|
} {
|
||||||
blds, bmsk := GenerateBuildings(buildingBase, settings.Width, settings.Height, settings, roadAnchors, waterMask, roadMask, exitRoadMask, seedProvider.Next())
|
blds, bmsk := GenerateBuildings(buildingBase, settings.Width, settings.Height, settings, roadAnchors, waterMask, placementMask, exitRoadMask, seedProvider.Next())
|
||||||
return struct {
|
return struct {
|
||||||
blds [][]image.Point
|
blds [][]image.Point
|
||||||
bmsk *PixelMask
|
bmsk *PixelMask
|
||||||
@@ -779,45 +1047,45 @@ func main() {
|
|||||||
buildingMask = NewPixelMask(settings.Width, settings.Height)
|
buildingMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6: Darkening Water Areas
|
// Step 8: Darkening Water Areas
|
||||||
|
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressLabel.SetText("Step 6 of 11: Darkening Water Areas")
|
progressLabel.SetText("Step 8 of 13: Darkening Water Areas")
|
||||||
progressBar.SetValue(6.0 / 11.0)
|
progressBar.SetValue(8.0 / 13.0)
|
||||||
})
|
})
|
||||||
darkenedHeightmap := DarkenLakeAreas(noiseImg, waterMask)
|
darkenedHeightmap := DarkenLakeAreas(noiseImg, waterMask)
|
||||||
|
|
||||||
// Step 7: Flattening Building Areas
|
// Step 9: Flattening Building Areas
|
||||||
|
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressLabel.SetText("Step 7 of 11: Flattening Building Areas")
|
progressLabel.SetText("Step 9 of 13: Flattening Building Areas")
|
||||||
progressBar.SetValue(7.0 / 11.0)
|
progressBar.SetValue(9.0 / 13.0)
|
||||||
})
|
})
|
||||||
flattenedBuildingHeightmap := FlattenBuildingAreas(darkenedHeightmap.(*image.RGBA), buildings, settings.Width, settings.Height)
|
flattenedBuildingHeightmap := FlattenBuildingAreas(darkenedHeightmap.(*image.RGBA), buildings, settings.Width, settings.Height)
|
||||||
|
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressLabel.SetText("Step 8 of 11: Flattening Road Areas")
|
progressLabel.SetText("Step 10 of 13: Flattening Road and Wall Areas")
|
||||||
progressBar.SetValue(8.0 / 11.0)
|
progressBar.SetValue(10.0 / 13.0)
|
||||||
})
|
})
|
||||||
flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, roadMask)
|
flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, placementMask)
|
||||||
|
|
||||||
// Step 8: Applying Roughness
|
// Step 11: Applying Roughness
|
||||||
|
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressLabel.SetText("Step 9 of 11: Applying Roughness")
|
progressLabel.SetText("Step 11 of 13: Applying Roughness")
|
||||||
progressBar.SetValue(9.0 / 11.0)
|
progressBar.SetValue(11.0 / 13.0)
|
||||||
})
|
})
|
||||||
compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness)
|
compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness)
|
||||||
|
|
||||||
// Step 9: Generating Trees
|
// Step 12: Generating Trees
|
||||||
|
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressLabel.SetText("Step 10 of 11: Generating Trees")
|
progressLabel.SetText("Step 12 of 13: Generating Trees")
|
||||||
progressBar.SetValue(10.0 / 11.0)
|
progressBar.SetValue(12.0 / 13.0)
|
||||||
})
|
})
|
||||||
treeBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
treeBase := cloneToRGBA(finalImage, settings.Width, settings.Height)
|
||||||
if out, ok := runWithTimeout(generationStepTimeout, func() *PixelMask {
|
if out, ok := runWithTimeout(generationStepTimeout, func() *PixelMask {
|
||||||
return GenerateTrees(treeBase, waterMask, roadMask, buildingMask, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next())
|
return GenerateTrees(treeBase, waterMask, placementMask, buildingMask, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next())
|
||||||
}); ok {
|
}); ok {
|
||||||
treeMask = out
|
treeMask = out
|
||||||
finalImage = treeBase
|
finalImage = treeBase
|
||||||
@@ -827,10 +1095,10 @@ func main() {
|
|||||||
treeMask = NewPixelMask(settings.Width, settings.Height)
|
treeMask = NewPixelMask(settings.Width, settings.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 10: Generating Bump Map
|
// Step 13: Generating Bump Map
|
||||||
|
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
progressLabel.SetText("Step 11 of 11: Generating Bump Map")
|
progressLabel.SetText("Step 13 of 13: Generating Bump Map")
|
||||||
progressBar.SetValue(1.0)
|
progressBar.SetValue(1.0)
|
||||||
})
|
})
|
||||||
bumpMap := GenerateBumpMap(compositeImg.(*image.RGBA), settings.Width, settings.Height, 0.10)
|
bumpMap := GenerateBumpMap(compositeImg.(*image.RGBA), settings.Width, settings.Height, 0.10)
|
||||||
@@ -942,6 +1210,17 @@ func main() {
|
|||||||
roadCurvynessSlider,
|
roadCurvynessSlider,
|
||||||
roadDistributionSlider,
|
roadDistributionSlider,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
fortificationsTab := container.NewTabItem("Fortifications", container.NewVBox(
|
||||||
|
minWallWidthSlider,
|
||||||
|
maxWallWidthSlider,
|
||||||
|
numWallsSlider,
|
||||||
|
cityCoverageSlider,
|
||||||
|
wallCurvynessSlider,
|
||||||
|
gateSpacingSlider,
|
||||||
|
showTurretsCheck,
|
||||||
|
turretControls,
|
||||||
|
))
|
||||||
numBuildingsSlider := newNumericInputSlider(0, 10000, float64(settings.NumBuildings), "%.0f", "Number of Buildings")
|
numBuildingsSlider := newNumericInputSlider(0, 10000, float64(settings.NumBuildings), "%.0f", "Number of Buildings")
|
||||||
numBuildingsSlider.entry.OnChanged = func(s string) {
|
numBuildingsSlider.entry.OnChanged = func(s string) {
|
||||||
numBuildingsSlider.validate(s, func(hasError bool) {
|
numBuildingsSlider.validate(s, func(hasError bool) {
|
||||||
@@ -1219,6 +1498,7 @@ func main() {
|
|||||||
terrainTab,
|
terrainTab,
|
||||||
waterTab,
|
waterTab,
|
||||||
roadsTab,
|
roadsTab,
|
||||||
|
fortificationsTab,
|
||||||
buildingsTab,
|
buildingsTab,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1303,7 +1583,7 @@ func encodeImageToWriter(w io.Writer, img image.Image, format string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// showMasksSaveDialog displays a dialog for saving the generated masks.
|
// showMasksSaveDialog displays a dialog for saving the generated masks.
|
||||||
func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg, bumpmapImg image.Image, settings *Settings, lakes [][]image.Point, riverMask, treeMask, roadMask, bridgeMask, buildingMask *PixelMask) {
|
func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg, bumpmapImg image.Image, settings *Settings, lakes [][]image.Point, riverMask, treeMask, roadMask, bridgeMask, wallMask, turretMask, buildingMask *PixelMask) {
|
||||||
// Create UI elements for the save dialog
|
// Create UI elements for the save dialog
|
||||||
fileNameEntry := widget.NewEntry()
|
fileNameEntry := widget.NewEntry()
|
||||||
fileNameEntry.SetPlaceHolder("masks_folder")
|
fileNameEntry.SetPlaceHolder("masks_folder")
|
||||||
@@ -1387,6 +1667,8 @@ func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg, bumpmapImg im
|
|||||||
{name: "trees_mask." + imgFormat, make: func() image.Image { return maskToGray(treeMask) }},
|
{name: "trees_mask." + imgFormat, make: func() image.Image { return maskToGray(treeMask) }},
|
||||||
{name: "roads_mask." + imgFormat, make: func() image.Image { return maskToGray(roadMask) }},
|
{name: "roads_mask." + imgFormat, make: func() image.Image { return maskToGray(roadMask) }},
|
||||||
{name: "bridges_mask." + imgFormat, make: func() image.Image { return maskToGray(bridgeMask) }},
|
{name: "bridges_mask." + imgFormat, make: func() image.Image { return maskToGray(bridgeMask) }},
|
||||||
|
{name: "walls_mask." + imgFormat, make: func() image.Image { return maskToGray(wallMask) }},
|
||||||
|
{name: "turrets_mask." + imgFormat, make: func() image.Image { return maskToGray(turretMask) }},
|
||||||
{name: "buildings_mask." + imgFormat, make: func() image.Image { return maskToGray(buildingMask) }},
|
{name: "buildings_mask." + imgFormat, make: func() image.Image { return maskToGray(buildingMask) }},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ func (m *PixelMask) SetXY(x, y int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *PixelMask) ClearXY(x, y int) {
|
||||||
|
if m.InBounds(x, y) {
|
||||||
|
m.Data[m.index(x, y)] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *PixelMask) GetPoint(p image.Point) bool {
|
func (m *PixelMask) GetPoint(p image.Point) bool {
|
||||||
return m.GetXY(p.X, p.Y)
|
return m.GetXY(p.X, p.Y)
|
||||||
}
|
}
|
||||||
|
|||||||
+74
@@ -44,6 +44,18 @@ type Settings struct {
|
|||||||
RoadDistribution float64 `json:"road_distribution"`
|
RoadDistribution float64 `json:"road_distribution"`
|
||||||
MinRoadAngle float64 `json:"min_road_angle"`
|
MinRoadAngle float64 `json:"min_road_angle"`
|
||||||
|
|
||||||
|
// Fortification settings
|
||||||
|
MinWallWidth float64 `json:"min_wall_width"`
|
||||||
|
MaxWallWidth float64 `json:"max_wall_width"`
|
||||||
|
NumWalls int `json:"num_walls"`
|
||||||
|
CityCoverage float64 `json:"city_coverage"`
|
||||||
|
WallCurvyness float64 `json:"wall_curvyness"`
|
||||||
|
ShowTurrets bool `json:"show_turrets"`
|
||||||
|
TurretSize float64 `json:"turret_size"`
|
||||||
|
TurretShape string `json:"turret_shape"`
|
||||||
|
TurretSpacing float64 `json:"turret_spacing"`
|
||||||
|
GateSpacing float64 `json:"gate_spacing"` // percent of wall circumference between gates (0=no gates)
|
||||||
|
|
||||||
// Building settings
|
// Building settings
|
||||||
NumBuildings int `json:"num_buildings"`
|
NumBuildings int `json:"num_buildings"`
|
||||||
MinBuildingSize float64 `json:"min_building_size"`
|
MinBuildingSize float64 `json:"min_building_size"`
|
||||||
@@ -139,6 +151,16 @@ func LoadSettings() (*Settings, error) {
|
|||||||
RoadCurvyness: 50,
|
RoadCurvyness: 50,
|
||||||
RoadDistribution: 50,
|
RoadDistribution: 50,
|
||||||
MinRoadAngle: 18,
|
MinRoadAngle: 18,
|
||||||
|
MinWallWidth: 1.5,
|
||||||
|
MaxWallWidth: 4.0,
|
||||||
|
NumWalls: 1,
|
||||||
|
CityCoverage: 70,
|
||||||
|
WallCurvyness: 35,
|
||||||
|
ShowTurrets: true,
|
||||||
|
TurretSize: 0.6,
|
||||||
|
TurretShape: "circular",
|
||||||
|
TurretSpacing: 55,
|
||||||
|
GateSpacing: 25,
|
||||||
NumBuildings: 200,
|
NumBuildings: 200,
|
||||||
MinBuildingSize: 3.5,
|
MinBuildingSize: 3.5,
|
||||||
MaxBuildingSize: 10.0,
|
MaxBuildingSize: 10.0,
|
||||||
@@ -200,9 +222,61 @@ func LoadSettings() (*Settings, error) {
|
|||||||
if settings.BuildingsPerRoad == 0 {
|
if settings.BuildingsPerRoad == 0 {
|
||||||
settings.BuildingsPerRoad = 6
|
settings.BuildingsPerRoad = 6
|
||||||
}
|
}
|
||||||
|
if _, ok := rawKeys["min_wall_width"]; !ok {
|
||||||
|
settings.MinWallWidth = 1.5
|
||||||
|
}
|
||||||
|
if _, ok := rawKeys["max_wall_width"]; !ok {
|
||||||
|
settings.MaxWallWidth = 4.0
|
||||||
|
}
|
||||||
|
if _, ok := rawKeys["num_walls"]; !ok {
|
||||||
|
settings.NumWalls = 1
|
||||||
|
}
|
||||||
|
if settings.CityCoverage == 0 {
|
||||||
|
settings.CityCoverage = 70
|
||||||
|
}
|
||||||
if _, ok := rawKeys["min_road_angle"]; !ok {
|
if _, ok := rawKeys["min_road_angle"]; !ok {
|
||||||
settings.MinRoadAngle = 18
|
settings.MinRoadAngle = 18
|
||||||
}
|
}
|
||||||
|
if _, ok := rawKeys["wall_curvyness"]; !ok {
|
||||||
|
settings.WallCurvyness = 35
|
||||||
|
}
|
||||||
|
if _, ok := rawKeys["show_turrets"]; !ok {
|
||||||
|
settings.ShowTurrets = true
|
||||||
|
}
|
||||||
|
if _, ok := rawKeys["turret_size"]; !ok {
|
||||||
|
settings.TurretSize = 0.6
|
||||||
|
}
|
||||||
|
if _, ok := rawKeys["turret_shape"]; !ok || (settings.TurretShape != "square" && settings.TurretShape != "circular") {
|
||||||
|
settings.TurretShape = "circular"
|
||||||
|
}
|
||||||
|
if _, ok := rawKeys["turret_spacing"]; !ok {
|
||||||
|
settings.TurretSpacing = 55
|
||||||
|
}
|
||||||
|
if _, ok := rawKeys["gate_spacing"]; !ok {
|
||||||
|
settings.GateSpacing = 25
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wall widths are percentages of average image dimension.
|
||||||
|
// Migrate older pixel-based values when they exceed the valid percentage range.
|
||||||
|
if settings.MinWallWidth > maxWallWidthPercent || settings.MaxWallWidth > maxWallWidthPercent {
|
||||||
|
avgDim := averageImageDimension(settings.Width, settings.Height)
|
||||||
|
if avgDim < 1 {
|
||||||
|
avgDim = 1
|
||||||
|
}
|
||||||
|
settings.MinWallWidth = (settings.MinWallWidth / avgDim) * 100.0
|
||||||
|
settings.MaxWallWidth = (settings.MaxWallWidth / avgDim) * 100.0
|
||||||
|
}
|
||||||
|
settings.MinWallWidth, settings.MaxWallWidth = normalizeWallWidthPercentRange(settings.MinWallWidth, settings.MaxWallWidth)
|
||||||
|
if settings.NumWalls < 0 {
|
||||||
|
settings.NumWalls = 0
|
||||||
|
}
|
||||||
|
if settings.NumWalls > 5 {
|
||||||
|
settings.NumWalls = 5
|
||||||
|
}
|
||||||
|
settings.CityCoverage = clamp(settings.CityCoverage, 1, 100)
|
||||||
|
settings.WallCurvyness = clamp(settings.WallCurvyness, 0, 100)
|
||||||
|
settings.TurretSize = snapTurretSizePercent(settings.TurretSize)
|
||||||
|
settings.TurretSpacing = clamp(settings.TurretSpacing, 0, 100)
|
||||||
|
|
||||||
// Tree sizes are percentages of average image dimension.
|
// Tree sizes are percentages of average image dimension.
|
||||||
// Migrate older pixel-based values when they exceed the valid percentage range.
|
// Migrate older pixel-based values when they exceed the valid percentage range.
|
||||||
|
|||||||
Reference in New Issue
Block a user