changed how masks are stored for massive performance improvment and way less ram usage
This commit is contained in:
+33
-31
@@ -62,7 +62,17 @@ func getBuildingSizeRangePixels(settings *Settings, width, height int) (float64,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateBuildings creates and places buildings on the map.
|
// GenerateBuildings creates and places buildings on the map.
|
||||||
func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, roadPixels, allWaterPixels []image.Point, seed int64) ([][]image.Point, []image.Point) {
|
func GenerateBuildings(
|
||||||
|
img *image.RGBA,
|
||||||
|
width,
|
||||||
|
height int,
|
||||||
|
settings *Settings,
|
||||||
|
roadAnchors []image.Point,
|
||||||
|
waterMask,
|
||||||
|
roadMask,
|
||||||
|
exitRoadMask *PixelMask,
|
||||||
|
seed int64,
|
||||||
|
) ([][]image.Point, *PixelMask) {
|
||||||
// Early exit if no buildings are to be generated
|
// Early exit if no buildings are to be generated
|
||||||
if settings.NumBuildings == 0 {
|
if settings.NumBuildings == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -70,32 +80,25 @@ func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, r
|
|||||||
|
|
||||||
randSrc := rand.New(rand.NewSource(seed))
|
randSrc := rand.New(rand.NewSource(seed))
|
||||||
buildingColor := color.RGBA{R: 128, G: 128, B: 128, A: 255} // Gray color for buildings
|
buildingColor := color.RGBA{R: 128, G: 128, B: 128, A: 255} // Gray color for buildings
|
||||||
|
if waterMask == nil {
|
||||||
isWater := make(map[image.Point]bool)
|
waterMask = NewPixelMask(width, height)
|
||||||
for _, p := range allWaterPixels {
|
|
||||||
isWater[p] = true
|
|
||||||
}
|
}
|
||||||
|
if roadMask == nil {
|
||||||
isRoad := make(map[image.Point]bool)
|
roadMask = NewPixelMask(width, height)
|
||||||
for _, p := range roadPixels {
|
|
||||||
isRoad[p] = true
|
|
||||||
}
|
}
|
||||||
isExitRoad := make(map[image.Point]bool)
|
if exitRoadMask == nil {
|
||||||
for _, p := range getExitRoadPixels() {
|
exitRoadMask = NewPixelMask(width, height)
|
||||||
isExitRoad[p] = true
|
|
||||||
}
|
}
|
||||||
|
buildingMask := NewPixelMask(width, height)
|
||||||
isBuilding := make(map[image.Point]bool)
|
|
||||||
var buildings [][]image.Point
|
var buildings [][]image.Point
|
||||||
var allBuildingPixels []image.Point
|
|
||||||
var anchorPoints []image.Point
|
var anchorPoints []image.Point
|
||||||
var normalRoadAnchors []image.Point
|
var normalRoadAnchors []image.Point
|
||||||
var exitRoadAnchors []image.Point
|
var exitRoadAnchors []image.Point
|
||||||
|
|
||||||
if len(roadPixels) > 0 {
|
if len(roadAnchors) > 0 {
|
||||||
anchorPoints = roadPixels
|
anchorPoints = roadAnchors
|
||||||
for _, p := range anchorPoints {
|
for _, p := range anchorPoints {
|
||||||
if isExitRoad[p] {
|
if exitRoadMask.GetPoint(p) {
|
||||||
exitRoadAnchors = append(exitRoadAnchors, p)
|
exitRoadAnchors = append(exitRoadAnchors, p)
|
||||||
} else {
|
} else {
|
||||||
normalRoadAnchors = append(normalRoadAnchors, p)
|
normalRoadAnchors = append(normalRoadAnchors, p)
|
||||||
@@ -106,7 +109,7 @@ func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, r
|
|||||||
for y := 0; y < height; y++ {
|
for y := 0; y < height; y++ {
|
||||||
for x := 0; x < width; x++ {
|
for x := 0; x < width; x++ {
|
||||||
p := image.Point{X: x, Y: y}
|
p := image.Point{X: x, Y: y}
|
||||||
if !isWater[p] {
|
if !waterMask.GetPoint(p) {
|
||||||
anchorPoints = append(anchorPoints, p)
|
anchorPoints = append(anchorPoints, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +132,7 @@ func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, r
|
|||||||
for y := 0; y < height; y++ {
|
for y := 0; y < height; y++ {
|
||||||
for x := 0; x < width; x++ {
|
for x := 0; x < width; x++ {
|
||||||
p := image.Point{X: x, Y: y}
|
p := image.Point{X: x, Y: y}
|
||||||
if !isWater[p] && !isRoad[p] {
|
if !waterMask.GetPoint(p) && !roadMask.GetPoint(p) {
|
||||||
landPoints = append(landPoints, p)
|
landPoints = append(landPoints, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,17 +199,16 @@ func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, r
|
|||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
if shape == "procedural" {
|
if shape == "procedural" {
|
||||||
pixels, ok = getProceduralBuildingPixels(center, size, settings, isWater, isRoad, isBuilding, width, height, randSrc)
|
pixels, ok = getProceduralBuildingPixels(center, size, settings, waterMask, roadMask, buildingMask, width, height, randSrc)
|
||||||
} else {
|
} else {
|
||||||
pixels, ok = getBuildingPixels(center, size, shape, isWater, isRoad, isBuilding, width, height, randSrc)
|
pixels, ok = getBuildingPixels(center, size, shape, waterMask, roadMask, buildingMask, width, height, randSrc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
// If successful, draw the building and update data structures
|
// If successful, draw the building and update data structures
|
||||||
for _, p := range pixels {
|
for _, p := range pixels {
|
||||||
img.Set(p.X, p.Y, buildingColor)
|
img.Set(p.X, p.Y, buildingColor)
|
||||||
isBuilding[p] = true
|
buildingMask.SetPoint(p)
|
||||||
allBuildingPixels = append(allBuildingPixels, p)
|
|
||||||
}
|
}
|
||||||
buildings = append(buildings, pixels)
|
buildings = append(buildings, pixels)
|
||||||
buildingsPlaced++
|
buildingsPlaced++
|
||||||
@@ -214,11 +216,11 @@ func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buildings, allBuildingPixels
|
return buildings, buildingMask
|
||||||
}
|
}
|
||||||
|
|
||||||
// getProceduralBuildingPixels generates a complex building by connecting multiple shapes.
|
// getProceduralBuildingPixels generates a complex building by connecting multiple shapes.
|
||||||
func getProceduralBuildingPixels(center image.Point, size float64, settings *Settings, isWater, isRoad, isBuilding map[image.Point]bool, width, height int, randSrc *rand.Rand) ([]image.Point, bool) {
|
func getProceduralBuildingPixels(center image.Point, size float64, settings *Settings, waterMask, roadMask, buildingMask *PixelMask, width, height int, randSrc *rand.Rand) ([]image.Point, bool) {
|
||||||
complexity := settings.MinBuildingComplexity
|
complexity := settings.MinBuildingComplexity
|
||||||
if settings.BuildingComplexityRatio > randSrc.Float64()*100 {
|
if settings.BuildingComplexityRatio > randSrc.Float64()*100 {
|
||||||
complexity = settings.MinBuildingComplexity + randSrc.Intn(settings.MaxBuildingComplexity-settings.MinBuildingComplexity+1)
|
complexity = settings.MinBuildingComplexity + randSrc.Intn(settings.MaxBuildingComplexity-settings.MinBuildingComplexity+1)
|
||||||
@@ -293,7 +295,7 @@ func getProceduralBuildingPixels(center image.Point, size float64, settings *Set
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, p := range pixels {
|
for _, p := range pixels {
|
||||||
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || isWater[p] || isRoad[p] || isBuilding[p] {
|
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || waterMask.GetPoint(p) || roadMask.GetPoint(p) || buildingMask.GetPoint(p) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
if !pixelMap[p] {
|
if !pixelMap[p] {
|
||||||
@@ -436,7 +438,7 @@ func chooseShape(randSrc *rand.Rand, ratios map[string]float64) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getBuildingPixels determines the pixels for a single building based on its shape and checks for collisions.
|
// getBuildingPixels determines the pixels for a single building based on its shape and checks for collisions.
|
||||||
func getBuildingPixels(center image.Point, size float64, shape string, isWater, isRoad, isBuilding map[image.Point]bool, width, height int, randSrc *rand.Rand) ([]image.Point, bool) {
|
func getBuildingPixels(center image.Point, size float64, shape string, waterMask, roadMask, buildingMask *PixelMask, width, height int, randSrc *rand.Rand) ([]image.Point, bool) {
|
||||||
var pixels []image.Point
|
var pixels []image.Point
|
||||||
var halfSize = int(size / 2)
|
var halfSize = int(size / 2)
|
||||||
|
|
||||||
@@ -446,7 +448,7 @@ func getBuildingPixels(center image.Point, size float64, shape string, isWater,
|
|||||||
for y := center.Y - halfSize; y <= center.Y+halfSize; y++ {
|
for y := center.Y - halfSize; y <= center.Y+halfSize; y++ {
|
||||||
for x := center.X - halfSize; x <= center.X+halfSize; x++ {
|
for x := center.X - halfSize; x <= center.X+halfSize; x++ {
|
||||||
p := image.Point{X: x, Y: y}
|
p := image.Point{X: x, Y: y}
|
||||||
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || isWater[p] || isRoad[p] || isBuilding[p] {
|
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || waterMask.GetPoint(p) || roadMask.GetPoint(p) || buildingMask.GetPoint(p) {
|
||||||
return nil, false // Collision detected
|
return nil, false // Collision detected
|
||||||
}
|
}
|
||||||
pixels = append(pixels, p)
|
pixels = append(pixels, p)
|
||||||
@@ -459,7 +461,7 @@ func getBuildingPixels(center image.Point, size float64, shape string, isWater,
|
|||||||
dx, dy := float64(x-center.X), float64(y-center.Y)
|
dx, dy := float64(x-center.X), float64(y-center.Y)
|
||||||
if dx*dx+dy*dy <= r2 {
|
if dx*dx+dy*dy <= r2 {
|
||||||
p := image.Point{X: x, Y: y}
|
p := image.Point{X: x, Y: y}
|
||||||
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || isWater[p] || isRoad[p] || isBuilding[p] {
|
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || waterMask.GetPoint(p) || roadMask.GetPoint(p) || buildingMask.GetPoint(p) {
|
||||||
return nil, false // Collision detected
|
return nil, false // Collision detected
|
||||||
}
|
}
|
||||||
pixels = append(pixels, p)
|
pixels = append(pixels, p)
|
||||||
@@ -480,7 +482,7 @@ func getBuildingPixels(center image.Point, size float64, shape string, isWater,
|
|||||||
for y := center.Y - halfH; y <= center.Y+halfH; y++ {
|
for y := center.Y - halfH; y <= center.Y+halfH; y++ {
|
||||||
for x := center.X - halfW; x <= center.X+halfW; x++ {
|
for x := center.X - halfW; x <= center.X+halfW; x++ {
|
||||||
p := image.Point{X: x, Y: y}
|
p := image.Point{X: x, Y: y}
|
||||||
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || isWater[p] || isRoad[p] || isBuilding[p] {
|
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || waterMask.GetPoint(p) || roadMask.GetPoint(p) || buildingMask.GetPoint(p) {
|
||||||
return nil, false // Collision detected
|
return nil, false // Collision detected
|
||||||
}
|
}
|
||||||
pixels = append(pixels, p)
|
pixels = append(pixels, p)
|
||||||
|
|||||||
@@ -55,12 +55,14 @@ func main() {
|
|||||||
|
|
||||||
// Initialize slices to store generated map features
|
// Initialize slices to store generated map features
|
||||||
var lakes [][]image.Point
|
var lakes [][]image.Point
|
||||||
var riverPixels []image.Point
|
|
||||||
var treePixels []image.Point
|
|
||||||
var buildingPixels []image.Point
|
|
||||||
var buildings [][]image.Point
|
var buildings [][]image.Point
|
||||||
var roadPixels []image.Point
|
var waterMask *PixelMask
|
||||||
var bridgePixels []image.Point
|
var riverMask *PixelMask
|
||||||
|
var treeMask *PixelMask
|
||||||
|
var buildingMask *PixelMask
|
||||||
|
var roadMask *PixelMask
|
||||||
|
var bridgeMask *PixelMask
|
||||||
|
var exitRoadMask *PixelMask
|
||||||
|
|
||||||
// Set up application configuration directory
|
// Set up application configuration directory
|
||||||
configDir, err := os.UserConfigDir()
|
configDir, err := os.UserConfigDir()
|
||||||
@@ -157,47 +159,31 @@ func main() {
|
|||||||
|
|
||||||
// Step 3: Generating Rivers
|
// Step 3: Generating Rivers
|
||||||
var riverImage image.Image
|
var riverImage image.Image
|
||||||
|
riverImage, riverMask = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, seedProvider.Next(), noiseImg, settings.RiverWidthVariability, settings.RiverEdgeRoughness)
|
||||||
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)
|
finalImage := riverImage.(*image.RGBA)
|
||||||
|
waterMask = BuildMaskFromLakes(settings.Width, settings.Height, lakes)
|
||||||
var roadImage *image.RGBA
|
if riverMask != nil {
|
||||||
|
waterMask.Merge(riverMask)
|
||||||
var flatLakePixels []image.Point
|
|
||||||
|
|
||||||
for _, lake := range lakes {
|
|
||||||
|
|
||||||
flatLakePixels = append(flatLakePixels, lake...)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allWaterPixels := append(flatLakePixels, riverPixels...)
|
|
||||||
// Step 4: Generating Roads
|
// Step 4: Generating Roads
|
||||||
roadPixels, bridgePixels, roadImage = GenerateRoads(settings.Width, settings.Height, settings, noiseImg, allWaterPixels, seedProvider.Next())
|
var roadAnchors []image.Point
|
||||||
for y := 0; y < roadImage.Bounds().Max.Y; y++ {
|
roadMask, bridgeMask, exitRoadMask, roadAnchors = GenerateRoads(finalImage, settings.Width, settings.Height, settings, waterMask, seedProvider.Next())
|
||||||
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)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5: Generating Buildings
|
// Step 5: Generating Buildings
|
||||||
buildings, buildingPixels = GenerateBuildings(finalImage, settings.Width, settings.Height, settings, roadPixels, allWaterPixels, seedProvider.Next())
|
buildings, buildingMask = GenerateBuildings(finalImage, settings.Width, settings.Height, settings, roadAnchors, waterMask, roadMask, exitRoadMask, seedProvider.Next())
|
||||||
|
|
||||||
// Step 6: Generating Trees
|
// Step 6: Generating Trees
|
||||||
treePixels = GenerateTrees(finalImage, allWaterPixels, roadPixels, buildingPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next())
|
treeMask = GenerateTrees(finalImage, waterMask, roadMask, buildingMask, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next())
|
||||||
|
|
||||||
// Step 7: Darkening Water Areas
|
// Step 7: Darkening Water Areas
|
||||||
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
darkenedHeightmap := DarkenLakeAreas(noiseImg, waterMask)
|
||||||
|
|
||||||
// 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 Areas
|
||||||
flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, roadPixels)
|
flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, roadMask)
|
||||||
|
|
||||||
// Step 10: Applying Roughness
|
// Step 10: Applying Roughness
|
||||||
compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness)
|
compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness)
|
||||||
@@ -519,7 +505,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, riverPixels, treePixels, roadPixels, bridgePixels, buildingPixels, buildings)
|
showMasksSaveDialog(w, canvasImg.Image, heightmapImg.Image, bumpmapImg.Image, settings, lakes, riverMask, treeMask, roadMask, bridgeMask, buildingMask)
|
||||||
})
|
})
|
||||||
// Main generation button and logic
|
// Main generation button and logic
|
||||||
generateBtn = widget.NewButton("Generate", func() {
|
generateBtn = widget.NewButton("Generate", func() {
|
||||||
@@ -560,15 +546,13 @@ func main() {
|
|||||||
progressBar.SetValue(3.0 / 11.0)
|
progressBar.SetValue(3.0 / 11.0)
|
||||||
})
|
})
|
||||||
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, seedProvider.Next(), noiseImg, settings.RiverWidthVariability, settings.RiverEdgeRoughness)
|
riverImage, riverMask = 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)
|
finalImage := riverImage.(*image.RGBA)
|
||||||
|
waterMask = BuildMaskFromLakes(settings.Width, settings.Height, lakes)
|
||||||
var flatLakePixels []image.Point
|
if riverMask != nil {
|
||||||
for _, lake := range lakes {
|
waterMask.Merge(riverMask)
|
||||||
flatLakePixels = append(flatLakePixels, lake...)
|
|
||||||
}
|
}
|
||||||
allWaterPixels := append(flatLakePixels, riverPixels...)
|
|
||||||
|
|
||||||
// Step 4: Generating Roads
|
// Step 4: Generating Roads
|
||||||
|
|
||||||
@@ -576,16 +560,8 @@ func main() {
|
|||||||
progressLabel.SetText("Step 4 of 11: Generating Roads")
|
progressLabel.SetText("Step 4 of 11: Generating Roads")
|
||||||
progressBar.SetValue(4.0 / 11.0)
|
progressBar.SetValue(4.0 / 11.0)
|
||||||
})
|
})
|
||||||
var roadImage *image.RGBA
|
var roadAnchors []image.Point
|
||||||
roadPixels, bridgePixels, roadImage = GenerateRoads(settings.Width, settings.Height, settings, noiseImg, allWaterPixels, seedProvider.Next()) // Draw roadImage over finalImage
|
roadMask, bridgeMask, exitRoadMask, roadAnchors = GenerateRoads(finalImage, settings.Width, settings.Height, settings, waterMask, seedProvider.Next())
|
||||||
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)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5: Generating Buildings
|
// Step 5: Generating Buildings
|
||||||
|
|
||||||
@@ -593,7 +569,7 @@ func main() {
|
|||||||
progressLabel.SetText("Step 5 of 11: Generating Buildings")
|
progressLabel.SetText("Step 5 of 11: Generating Buildings")
|
||||||
progressBar.SetValue(5.0 / 11.0)
|
progressBar.SetValue(5.0 / 11.0)
|
||||||
})
|
})
|
||||||
buildings, buildingPixels = GenerateBuildings(finalImage, settings.Width, settings.Height, settings, roadPixels, allWaterPixels, seedProvider.Next())
|
buildings, buildingMask = GenerateBuildings(finalImage, settings.Width, settings.Height, settings, roadAnchors, waterMask, roadMask, exitRoadMask, seedProvider.Next())
|
||||||
|
|
||||||
// Step 6: Darkening Water Areas
|
// Step 6: Darkening Water Areas
|
||||||
|
|
||||||
@@ -601,7 +577,7 @@ func main() {
|
|||||||
progressLabel.SetText("Step 6 of 11: Darkening Water Areas")
|
progressLabel.SetText("Step 6 of 11: Darkening Water Areas")
|
||||||
progressBar.SetValue(6.0 / 11.0)
|
progressBar.SetValue(6.0 / 11.0)
|
||||||
})
|
})
|
||||||
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
|
darkenedHeightmap := DarkenLakeAreas(noiseImg, waterMask)
|
||||||
|
|
||||||
// Step 7: Flattening Building Areas
|
// Step 7: Flattening Building Areas
|
||||||
|
|
||||||
@@ -615,7 +591,7 @@ func main() {
|
|||||||
progressLabel.SetText("Step 8 of 11: Flattening Road Areas")
|
progressLabel.SetText("Step 8 of 11: Flattening Road Areas")
|
||||||
progressBar.SetValue(8.0 / 11.0)
|
progressBar.SetValue(8.0 / 11.0)
|
||||||
})
|
})
|
||||||
flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, roadPixels)
|
flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, roadMask)
|
||||||
|
|
||||||
// Step 8: Applying Roughness
|
// Step 8: Applying Roughness
|
||||||
|
|
||||||
@@ -631,7 +607,7 @@ func main() {
|
|||||||
progressLabel.SetText("Step 10 of 11: Generating Trees")
|
progressLabel.SetText("Step 10 of 11: Generating Trees")
|
||||||
progressBar.SetValue(10.0 / 11.0)
|
progressBar.SetValue(10.0 / 11.0)
|
||||||
})
|
})
|
||||||
treePixels = GenerateTrees(finalImage, allWaterPixels, roadPixels, buildingPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next())
|
treeMask = GenerateTrees(finalImage, waterMask, roadMask, buildingMask, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next())
|
||||||
|
|
||||||
// Step 10: Generating Bump Map
|
// Step 10: Generating Bump Map
|
||||||
|
|
||||||
@@ -1105,7 +1081,7 @@ func getImageData(img image.Image, format string) (*bytes.Buffer, 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, riverPixels, treePixels, roadPixels, bridgePixels, buildingPixels []image.Point, buildings [][]image.Point) {
|
func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg, bumpmapImg image.Image, settings *Settings, lakes [][]image.Point, riverMask, treeMask, roadMask, bridgeMask, 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")
|
||||||
@@ -1152,6 +1128,21 @@ func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg, bumpmapImg im
|
|||||||
}
|
}
|
||||||
imgFormat := strings.ToLower(formatSelect.Selected)
|
imgFormat := strings.ToLower(formatSelect.Selected)
|
||||||
bounds := canvasImg.Bounds()
|
bounds := canvasImg.Bounds()
|
||||||
|
maskToGray := func(mask *PixelMask) *image.Gray {
|
||||||
|
out := image.NewGray(bounds)
|
||||||
|
if mask == nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
for y := 0; y < mask.Height; y++ {
|
||||||
|
row := y * mask.Width
|
||||||
|
for x := 0; x < mask.Width; x++ {
|
||||||
|
if mask.Data[row+x] != 0 {
|
||||||
|
out.SetGray(x, y, color.Gray{Y: 255})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// Create mask images from the generated data
|
// Create mask images from the generated data
|
||||||
lakeMask := image.NewGray(bounds)
|
lakeMask := image.NewGray(bounds)
|
||||||
@@ -1160,39 +1151,22 @@ func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg, bumpmapImg im
|
|||||||
lakeMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
|
lakeMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
riverMask := image.NewGray(bounds)
|
riverMaskImg := maskToGray(riverMask)
|
||||||
for _, p := range riverPixels {
|
treeMaskImg := maskToGray(treeMask)
|
||||||
riverMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
|
roadMaskImg := maskToGray(roadMask)
|
||||||
}
|
bridgeMaskImg := maskToGray(bridgeMask)
|
||||||
treeMask := image.NewGray(bounds)
|
buildingMaskImg := maskToGray(buildingMask)
|
||||||
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})
|
|
||||||
}
|
|
||||||
bridgeMask := image.NewGray(bounds)
|
|
||||||
for _, p := range bridgePixels {
|
|
||||||
bridgeMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
|
|
||||||
}
|
|
||||||
buildingMask := image.NewGray(bounds)
|
|
||||||
for _, building := range buildings {
|
|
||||||
for _, p := range building {
|
|
||||||
buildingMask.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,
|
||||||
"heightmap." + imgFormat: heightmapImg,
|
"heightmap." + imgFormat: heightmapImg,
|
||||||
"bump_map." + imgFormat: bumpmapImg,
|
"bump_map." + imgFormat: bumpmapImg,
|
||||||
"lakes_mask." + imgFormat: lakeMask,
|
"lakes_mask." + imgFormat: lakeMask,
|
||||||
"rivers_mask." + imgFormat: riverMask,
|
"rivers_mask." + imgFormat: riverMaskImg,
|
||||||
"trees_mask." + imgFormat: treeMask,
|
"trees_mask." + imgFormat: treeMaskImg,
|
||||||
"roads_mask." + imgFormat: roadMask,
|
"roads_mask." + imgFormat: roadMaskImg,
|
||||||
"bridges_mask." + imgFormat: bridgeMask,
|
"bridges_mask." + imgFormat: bridgeMaskImg,
|
||||||
"buildings_mask." + imgFormat: buildingMask,
|
"buildings_mask." + imgFormat: buildingMaskImg,
|
||||||
}
|
}
|
||||||
// Save the images based on the selected packaging option
|
// Save the images based on the selected packaging option
|
||||||
switch packageSelect.Selected {
|
switch packageSelect.Selected {
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
|
||||||
|
// PixelMask stores per-pixel occupancy using one byte per pixel.
|
||||||
|
type PixelMask struct {
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
Data []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPixelMask(width, height int) *PixelMask {
|
||||||
|
if width <= 0 || height <= 0 {
|
||||||
|
return &PixelMask{}
|
||||||
|
}
|
||||||
|
return &PixelMask{
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
Data: make([]uint8, width*height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PixelMask) index(x, y int) int {
|
||||||
|
return y*m.Width + x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PixelMask) InBounds(x, y int) bool {
|
||||||
|
return m != nil && x >= 0 && y >= 0 && x < m.Width && y < m.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PixelMask) GetXY(x, y int) bool {
|
||||||
|
if !m.InBounds(x, y) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return m.Data[m.index(x, y)] != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PixelMask) SetXY(x, y int) {
|
||||||
|
if m.InBounds(x, y) {
|
||||||
|
m.Data[m.index(x, y)] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PixelMask) GetPoint(p image.Point) bool {
|
||||||
|
return m.GetXY(p.X, p.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PixelMask) SetPoint(p image.Point) {
|
||||||
|
m.SetXY(p.X, p.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PixelMask) Merge(other *PixelMask) {
|
||||||
|
if m == nil || other == nil || m.Width != other.Width || m.Height != other.Height {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := range m.Data {
|
||||||
|
if other.Data[i] != 0 {
|
||||||
|
m.Data[i] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PixelMask) AddPoints(points []image.Point) {
|
||||||
|
for _, p := range points {
|
||||||
|
m.SetPoint(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *PixelMask) ToPoints() []image.Point {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pts := make([]image.Point, 0)
|
||||||
|
for y := 0; y < m.Height; y++ {
|
||||||
|
row := y * m.Width
|
||||||
|
for x := 0; x < m.Width; x++ {
|
||||||
|
if m.Data[row+x] != 0 {
|
||||||
|
pts = append(pts, image.Point{X: x, Y: y})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pts
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildMaskFromLakes(width, height int, lakes [][]image.Point) *PixelMask {
|
||||||
|
mask := NewPixelMask(width, height)
|
||||||
|
for _, lake := range lakes {
|
||||||
|
mask.AddPoints(lake)
|
||||||
|
}
|
||||||
|
return mask
|
||||||
|
}
|
||||||
@@ -31,8 +31,6 @@ type Road struct {
|
|||||||
Importance int
|
Importance int
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastExitRoadPixels []image.Point
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
minRoadWidthPercent = 0.1
|
minRoadWidthPercent = 0.1
|
||||||
maxRoadWidthPercent = 5.0
|
maxRoadWidthPercent = 5.0
|
||||||
@@ -81,54 +79,50 @@ func getRoadWidthRangePixels(settings *Settings, width, height int) (float64, fl
|
|||||||
return minPx, maxPx
|
return minPx, maxPx
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExitRoadPixels() []image.Point {
|
|
||||||
out := make([]image.Point, len(lastExitRoadPixels))
|
|
||||||
copy(out, lastExitRoadPixels)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateRoads creates roads on the map.
|
// GenerateRoads creates roads on the map.
|
||||||
func GenerateRoads(width, height int, settings *Settings, _ image.Image, allWaterPixels []image.Point, seed int64) ([]image.Point, []image.Point, *image.RGBA) {
|
func GenerateRoads(
|
||||||
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
img *image.RGBA,
|
||||||
|
width,
|
||||||
|
height int,
|
||||||
|
settings *Settings,
|
||||||
|
waterMask *PixelMask,
|
||||||
|
seed int64,
|
||||||
|
) (*PixelMask, *PixelMask, *PixelMask, []image.Point) {
|
||||||
|
if img == nil {
|
||||||
|
img = image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
}
|
||||||
randSrc := rand.New(rand.NewSource(seed))
|
randSrc := rand.New(rand.NewSource(seed))
|
||||||
roadColor := color.RGBA{R: 139, G: 69, B: 19, A: 255}
|
roadColor := color.RGBA{R: 139, G: 69, B: 19, A: 255}
|
||||||
bridgeColor := color.RGBA{R: 60, G: 42, B: 33, A: 255}
|
bridgeColor := color.RGBA{R: 60, G: 42, B: 33, A: 255}
|
||||||
waterMap := make(map[image.Point]bool, len(allWaterPixels))
|
|
||||||
for _, p := range allWaterPixels {
|
|
||||||
waterMap[p] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
roadTarget := estimateRoadTarget(settings, randSrc)
|
roadTarget := estimateRoadTarget(settings, randSrc)
|
||||||
pois := generatePOIs(width, height, settings, waterMap, randSrc, roadTarget)
|
pois := generatePOIs(width, height, settings, waterMask, randSrc, roadTarget)
|
||||||
if len(pois) < 2 {
|
if len(pois) < 2 {
|
||||||
return nil, nil, img
|
return NewPixelMask(width, height), NewPixelMask(width, height), NewPixelMask(width, height), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
roads := connectPOIs(pois, width, height, settings, randSrc, waterMap, roadTarget)
|
roads := connectPOIs(pois, width, height, settings, randSrc, waterMask, roadTarget)
|
||||||
roads = appendExitRoads(roads, pois, width, height, settings, randSrc, waterMap)
|
roads = appendExitRoads(roads, pois, width, height, settings, randSrc, waterMask)
|
||||||
if len(roads) == 0 {
|
if len(roads) == 0 {
|
||||||
return nil, nil, img
|
return NewPixelMask(width, height), NewPixelMask(width, height), NewPixelMask(width, height), nil
|
||||||
}
|
}
|
||||||
assignRoadWidths(roads, settings, randSrc, width, height)
|
assignRoadWidths(roads, settings, randSrc, width, height)
|
||||||
|
|
||||||
allRoadPixels := make([]image.Point, 0, len(roads)*64)
|
roadMask := NewPixelMask(width, height)
|
||||||
allBridgePixels := make([]image.Point, 0, len(roads)*16)
|
bridgeMask := NewPixelMask(width, height)
|
||||||
exitRoadPixels := make([]image.Point, 0, len(roads)*16)
|
exitRoadMask := NewPixelMask(width, height)
|
||||||
for _, road := range roads {
|
for _, road := range roads {
|
||||||
roadPixels, bridgePixels := drawRoad(img, road.Points, roadColor, bridgeColor, road.Width)
|
drawRoadToMasks(img, road.Points, roadColor, bridgeColor, road.Width, roadMask, bridgeMask)
|
||||||
allRoadPixels = append(allRoadPixels, roadPixels...)
|
|
||||||
allBridgePixels = append(allBridgePixels, bridgePixels...)
|
|
||||||
if road.Start.IsExit || road.End.IsExit {
|
if road.Start.IsExit || road.End.IsExit {
|
||||||
exitRoadPixels = append(exitRoadPixels, roadPixels...)
|
drawRoadToMasks(img, road.Points, roadColor, bridgeColor, road.Width, exitRoadMask, exitRoadMask)
|
||||||
exitRoadPixels = append(exitRoadPixels, bridgePixels...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastExitRoadPixels = exitRoadPixels
|
|
||||||
|
|
||||||
return allRoadPixels, allBridgePixels, img
|
roadAnchors := roadMask.ToPoints()
|
||||||
|
return roadMask, bridgeMask, exitRoadMask, roadAnchors
|
||||||
}
|
}
|
||||||
|
|
||||||
func generatePOIs(width, height int, settings *Settings, waterMap map[image.Point]bool, randSrc *rand.Rand, roadTarget int) []*PointOfInterest {
|
func generatePOIs(width, height int, settings *Settings, waterMask *PixelMask, randSrc *rand.Rand, roadTarget int) []*PointOfInterest {
|
||||||
distribution := clamp01(settings.RoadDistribution / 100.0)
|
distribution := clamp01(settings.RoadDistribution / 100.0)
|
||||||
minBuildingSizePx, maxBuildingSizePx := getBuildingSizeRangePixels(settings, width, height)
|
minBuildingSizePx, maxBuildingSizePx := getBuildingSizeRangePixels(settings, width, height)
|
||||||
avgBuildingSize := (minBuildingSizePx + maxBuildingSizePx) / 2.0
|
avgBuildingSize := (minBuildingSizePx + maxBuildingSizePx) / 2.0
|
||||||
@@ -160,7 +154,7 @@ func generatePOIs(width, height int, settings *Settings, waterMap map[image.Poin
|
|||||||
}
|
}
|
||||||
p := image.Point{X: x, Y: y}
|
p := image.Point{X: x, Y: y}
|
||||||
// Keep larger spacing between intersections so buildings have room.
|
// Keep larger spacing between intersections so buildings have room.
|
||||||
if waterMap[p] || isTooCloseToExisting(pois, x, y, avgBuildingSize*1.1) {
|
if waterMask.GetPoint(p) || isTooCloseToExisting(pois, x, y, avgBuildingSize*1.1) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pois = append(pois, &PointOfInterest{X: x, Y: y, TargetDegree: sampleTargetDegree(randSrc)})
|
pois = append(pois, &PointOfInterest{X: x, Y: y, TargetDegree: sampleTargetDegree(randSrc)})
|
||||||
@@ -254,7 +248,7 @@ func sampleTargetDegree(randSrc *rand.Rand) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings, randSrc *rand.Rand, waterMap map[image.Point]bool, roadTarget int) []*Road {
|
func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings, randSrc *rand.Rand, waterMask *PixelMask, roadTarget int) []*Road {
|
||||||
minAngle := settings.MinRoadAngle * math.Pi / 180.0
|
minAngle := settings.MinRoadAngle * math.Pi / 180.0
|
||||||
if minAngle < 0 {
|
if minAngle < 0 {
|
||||||
minAngle = 0
|
minAngle = 0
|
||||||
@@ -399,7 +393,7 @@ func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings,
|
|||||||
for _, e := range selectedEdges {
|
for _, e := range selectedEdges {
|
||||||
a := pois[e.a]
|
a := pois[e.a]
|
||||||
b := pois[e.b]
|
b := pois[e.b]
|
||||||
path := calculateRoadPath(a, b, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMap)
|
path := calculateRoadPath(a, b, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask)
|
||||||
imp := a.Connections + b.Connections + int(math.Round((a.ArterialWeight+b.ArterialWeight)*4))
|
imp := a.Connections + b.Connections + int(math.Round((a.ArterialWeight+b.ArterialWeight)*4))
|
||||||
roads = append(roads, &Road{Start: a, End: b, Points: path, Importance: imp})
|
roads = append(roads, &Road{Start: a, End: b, Points: path, Importance: imp})
|
||||||
}
|
}
|
||||||
@@ -407,7 +401,7 @@ func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings,
|
|||||||
return roads
|
return roads
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendExitRoads(roads []*Road, pois []*PointOfInterest, width, height int, settings *Settings, randSrc *rand.Rand, waterMap map[image.Point]bool) []*Road {
|
func appendExitRoads(roads []*Road, pois []*PointOfInterest, width, height int, settings *Settings, randSrc *rand.Rand, waterMask *PixelMask) []*Road {
|
||||||
if settings.RoadExits <= 0 || len(pois) == 0 {
|
if settings.RoadExits <= 0 || len(pois) == 0 {
|
||||||
return roads
|
return roads
|
||||||
}
|
}
|
||||||
@@ -417,7 +411,7 @@ func appendExitRoads(roads []*Road, pois []*PointOfInterest, width, height int,
|
|||||||
usedEdgePoints := make([]image.Point, 0, settings.RoadExits)
|
usedEdgePoints := make([]image.Point, 0, settings.RoadExits)
|
||||||
|
|
||||||
for i := 0; i < settings.RoadExits; i++ {
|
for i := 0; i < settings.RoadExits; i++ {
|
||||||
edgeNode, ok := sampleNonWaterEdgePOI(width, height, randSrc, waterMap, usedEdgePoints)
|
edgeNode, ok := sampleNonWaterEdgePOI(width, height, randSrc, waterMask, usedEdgePoints)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -432,7 +426,7 @@ func appendExitRoads(roads []*Road, pois []*PointOfInterest, width, height int,
|
|||||||
edgeNode.TargetDegree = 1
|
edgeNode.TargetDegree = 1
|
||||||
edgeNode.Connections = 1
|
edgeNode.Connections = 1
|
||||||
|
|
||||||
path := calculateRoadPath(anchor, edgeNode, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMap)
|
path := calculateRoadPath(anchor, edgeNode, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask)
|
||||||
importance := anchor.Connections + edgeNode.Connections + int(math.Round(anchor.ArterialWeight*3))
|
importance := anchor.Connections + edgeNode.Connections + int(math.Round(anchor.ArterialWeight*3))
|
||||||
roads = append(roads, &Road{
|
roads = append(roads, &Road{
|
||||||
Start: anchor,
|
Start: anchor,
|
||||||
@@ -448,14 +442,14 @@ func appendExitRoads(roads []*Road, pois []*PointOfInterest, width, height int,
|
|||||||
return roads
|
return roads
|
||||||
}
|
}
|
||||||
|
|
||||||
func sampleNonWaterEdgePOI(width, height int, randSrc *rand.Rand, waterMap map[image.Point]bool, used []image.Point) (*PointOfInterest, bool) {
|
func sampleNonWaterEdgePOI(width, height int, randSrc *rand.Rand, waterMask *PixelMask, used []image.Point) (*PointOfInterest, bool) {
|
||||||
minSpacing := math.Min(float64(width), float64(height)) * 0.08
|
minSpacing := math.Min(float64(width), float64(height)) * 0.08
|
||||||
minSpacing2 := minSpacing * minSpacing
|
minSpacing2 := minSpacing * minSpacing
|
||||||
|
|
||||||
for tries := 0; tries < 120; tries++ {
|
for tries := 0; tries < 120; tries++ {
|
||||||
p := sampleEdgePOI(width, height, randSrc)
|
p := sampleEdgePOI(width, height, randSrc)
|
||||||
pt := image.Point{X: p.X, Y: p.Y}
|
pt := image.Point{X: p.X, Y: p.Y}
|
||||||
if waterMap[pt] {
|
if waterMask.GetPoint(pt) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tooClose := false
|
tooClose := false
|
||||||
@@ -614,10 +608,8 @@ func assignRoadWidths(roads []*Road, settings *Settings, randSrc *rand.Rand, wid
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawRoad draws a single road on the image including bridges.
|
// drawRoadToMasks draws a single road on the image including bridges.
|
||||||
func drawRoad(img *image.RGBA, points []PathPoint, roadColor, bridgeColor color.Color, width int) ([]image.Point, []image.Point) {
|
func drawRoadToMasks(img *image.RGBA, points []PathPoint, roadColor, bridgeColor color.Color, width int, roadMask, bridgeMask *PixelMask) {
|
||||||
var roadPixels []image.Point
|
|
||||||
var bridgePixels []image.Point
|
|
||||||
bridgeWidth := int(math.Ceil(float64(width) * 1.15))
|
bridgeWidth := int(math.Ceil(float64(width) * 1.15))
|
||||||
if bridgeWidth < 1 {
|
if bridgeWidth < 1 {
|
||||||
bridgeWidth = 1
|
bridgeWidth = 1
|
||||||
@@ -628,8 +620,7 @@ func drawRoad(img *image.RGBA, points []PathPoint, roadColor, bridgeColor color.
|
|||||||
p2 := points[i+1]
|
p2 := points[i+1]
|
||||||
isBridge := p1.IsBridge && p2.IsBridge
|
isBridge := p1.IsBridge && p2.IsBridge
|
||||||
if !isBridge {
|
if !isBridge {
|
||||||
linePoints := drawLine(img, p1.Point.X, p1.Point.Y, p2.Point.X, p2.Point.Y, roadColor, width)
|
drawLineMasked(img, p1.Point.X, p1.Point.Y, p2.Point.X, p2.Point.Y, roadColor, width, roadMask)
|
||||||
roadPixels = append(roadPixels, linePoints...)
|
|
||||||
i++
|
i++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -640,17 +631,16 @@ func drawRoad(img *image.RGBA, points []PathPoint, roadColor, bridgeColor color.
|
|||||||
for end < len(points)-1 && points[end].IsBridge && points[end+1].IsBridge {
|
for end < len(points)-1 && points[end].IsBridge && points[end+1].IsBridge {
|
||||||
end++
|
end++
|
||||||
}
|
}
|
||||||
linePoints := drawLine(
|
drawLineMasked(
|
||||||
img,
|
img,
|
||||||
points[start].Point.X, points[start].Point.Y,
|
points[start].Point.X, points[start].Point.Y,
|
||||||
points[end].Point.X, points[end].Point.Y,
|
points[end].Point.X, points[end].Point.Y,
|
||||||
bridgeColor,
|
bridgeColor,
|
||||||
bridgeWidth,
|
bridgeWidth,
|
||||||
|
bridgeMask,
|
||||||
)
|
)
|
||||||
bridgePixels = append(bridgePixels, linePoints...)
|
|
||||||
i = end
|
i = end
|
||||||
}
|
}
|
||||||
return roadPixels, bridgePixels
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func bresenhamRoad(path []image.Point) []image.Point {
|
func bresenhamRoad(path []image.Point) []image.Point {
|
||||||
@@ -693,27 +683,27 @@ func bresenhamRoad(path []image.Point) []image.Point {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculateRoadPath computes the path for a road including curves and bridges.
|
// calculateRoadPath computes the path for a road including curves and bridges.
|
||||||
func calculateRoadPath(start, end *PointOfInterest, curvyness, avgDim float64, randSrc *rand.Rand, waterMap map[image.Point]bool) []PathPoint {
|
func calculateRoadPath(start, end *PointOfInterest, curvyness, avgDim float64, randSrc *rand.Rand, waterMask *PixelMask) []PathPoint {
|
||||||
dx := end.X - start.X
|
dx := end.X - start.X
|
||||||
dy := end.Y - start.Y
|
dy := end.Y - start.Y
|
||||||
dist := math.Hypot(float64(dx), float64(dy))
|
dist := math.Hypot(float64(dx), float64(dy))
|
||||||
|
|
||||||
if dist == 0 {
|
if dist == 0 {
|
||||||
p := image.Point{X: start.X, Y: start.Y}
|
p := image.Point{X: start.X, Y: start.Y}
|
||||||
return []PathPoint{{Point: p, IsBridge: waterMap[p]}}
|
return []PathPoint{{Point: p, IsBridge: waterMask.GetPoint(p)}}
|
||||||
}
|
}
|
||||||
|
|
||||||
curve := clamp(curvyness, 0, 1)
|
curve := clamp(curvyness, 0, 1)
|
||||||
if curve <= 0 {
|
if curve <= 0 {
|
||||||
points := bresenhamRoad([]image.Point{{X: start.X, Y: start.Y}, {X: end.X, Y: end.Y}})
|
points := bresenhamRoad([]image.Point{{X: start.X, Y: start.Y}, {X: end.X, Y: end.Y}})
|
||||||
return toPathPoints(points, waterMap)
|
return toPathPoints(points, waterMask)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-linear scaling: low values stay fairly straight, high values become very winding.
|
// Non-linear scaling: low values stay fairly straight, high values become very winding.
|
||||||
strength := math.Pow(curve, 1.35)
|
strength := math.Pow(curve, 1.35)
|
||||||
if strength < 0.001 {
|
if strength < 0.001 {
|
||||||
points := bresenhamRoad([]image.Point{{X: start.X, Y: start.Y}, {X: end.X, Y: end.Y}})
|
points := bresenhamRoad([]image.Point{{X: start.X, Y: start.Y}, {X: end.X, Y: end.Y}})
|
||||||
return toPathPoints(points, waterMap)
|
return toPathPoints(points, waterMask)
|
||||||
}
|
}
|
||||||
|
|
||||||
baseControls := int(math.Max(12, dist/(22.0-14.0*strength)))
|
baseControls := int(math.Max(12, dist/(22.0-14.0*strength)))
|
||||||
@@ -774,20 +764,19 @@ func calculateRoadPath(start, end *PointOfInterest, curvyness, avgDim float64, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
points := bresenhamRoad(controlPoints)
|
points := bresenhamRoad(controlPoints)
|
||||||
return toPathPoints(points, waterMap)
|
return toPathPoints(points, waterMask)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toPathPoints(points []image.Point, waterMap map[image.Point]bool) []PathPoint {
|
func toPathPoints(points []image.Point, waterMask *PixelMask) []PathPoint {
|
||||||
pathPoints := make([]PathPoint, len(points))
|
pathPoints := make([]PathPoint, len(points))
|
||||||
for i, p := range points {
|
for i, p := range points {
|
||||||
pathPoints[i] = PathPoint{Point: p, IsBridge: waterMap[p]}
|
pathPoints[i] = PathPoint{Point: p, IsBridge: waterMask.GetPoint(p)}
|
||||||
}
|
}
|
||||||
return pathPoints
|
return pathPoints
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawLine draws a line with specified width on the image.
|
// drawLineMasked draws a line with specified width on the image and mask.
|
||||||
func drawLine(img *image.RGBA, x0, y0, x1, y1 int, col color.Color, width int) []image.Point {
|
func drawLineMasked(img *image.RGBA, x0, y0, x1, y1 int, col color.Color, width int, mask *PixelMask) {
|
||||||
var points []image.Point
|
|
||||||
dx := abs(x1 - x0)
|
dx := abs(x1 - x0)
|
||||||
dy := -abs(y1 - y0)
|
dy := -abs(y1 - y0)
|
||||||
sx := -1
|
sx := -1
|
||||||
@@ -807,7 +796,9 @@ func drawLine(img *image.RGBA, x0, y0, x1, y1 int, col color.Color, width int) [
|
|||||||
py := y0 + j
|
py := y0 + j
|
||||||
if img.Bounds().Min.X <= px && px < img.Bounds().Max.X && img.Bounds().Min.Y <= py && py < img.Bounds().Max.Y {
|
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)
|
img.Set(px, py, col)
|
||||||
points = append(points, image.Point{X: px, Y: py})
|
if mask != nil {
|
||||||
|
mask.SetXY(px, py)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -825,7 +816,6 @@ func drawLine(img *image.RGBA, x0, y0, x1, y1 int, col color.Color, width int) [
|
|||||||
y0 += sy
|
y0 += sy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return points
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func clamp(v, lo, hi float64) float64 {
|
func clamp(v, lo, hi float64) float64 {
|
||||||
|
|||||||
+40
-29
@@ -81,15 +81,22 @@ func ApplyRoughness(heightmap image.Image, roughness float64) image.Image {
|
|||||||
return composite
|
return composite
|
||||||
}
|
}
|
||||||
|
|
||||||
// DarkenLakeAreas darkens the heightmap where lakes exist
|
// DarkenLakeAreas darkens the heightmap where water exists.
|
||||||
func DarkenLakeAreas(heightmap image.Image, lakePixels []image.Point) image.Image {
|
func DarkenLakeAreas(heightmap image.Image, waterMask *PixelMask) image.Image {
|
||||||
bounds := heightmap.Bounds()
|
bounds := heightmap.Bounds()
|
||||||
width := bounds.Dx()
|
width := bounds.Dx()
|
||||||
|
|
||||||
lakeMask := image.NewRGBA(bounds)
|
lakeMask := image.NewRGBA(bounds)
|
||||||
black := color.RGBA{0, 0, 0, 255}
|
black := color.RGBA{0, 0, 0, 255}
|
||||||
for _, p := range lakePixels {
|
if waterMask != nil {
|
||||||
lakeMask.Set(p.X, p.Y, black)
|
for y := 0; y < waterMask.Height; y++ {
|
||||||
|
row := y * waterMask.Width
|
||||||
|
for x := 0; x < waterMask.Width; x++ {
|
||||||
|
if waterMask.Data[row+x] != 0 {
|
||||||
|
lakeMask.Set(x, y, black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blurRadius := float64(width) * 0.05
|
blurRadius := float64(width) * 0.05
|
||||||
@@ -102,18 +109,25 @@ func DarkenLakeAreas(heightmap image.Image, lakePixels []image.Point) image.Imag
|
|||||||
return composite
|
return composite
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlattenRoadAreas smooths terrain under roads
|
// FlattenRoadAreas smooths terrain under roads.
|
||||||
func FlattenRoadAreas(heightmap image.Image, roadPixels []image.Point) image.Image {
|
func FlattenRoadAreas(heightmap image.Image, roadMask *PixelMask) image.Image {
|
||||||
bounds := heightmap.Bounds()
|
bounds := heightmap.Bounds()
|
||||||
width := bounds.Dx()
|
width := bounds.Dx()
|
||||||
|
|
||||||
roadMask := image.NewGray(bounds)
|
roadGrayMask := image.NewGray(bounds)
|
||||||
for _, p := range roadPixels {
|
if roadMask != nil {
|
||||||
roadMask.SetGray(p.X, p.Y, color.Gray{Y: 255})
|
for y := 0; y < roadMask.Height; y++ {
|
||||||
|
row := y * roadMask.Width
|
||||||
|
for x := 0; x < roadMask.Width; x++ {
|
||||||
|
if roadMask.Data[row+x] != 0 {
|
||||||
|
roadGrayMask.SetGray(x, y, color.Gray{Y: 255})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blurRadius := float64(width) * 0.01
|
blurRadius := float64(width) * 0.01
|
||||||
blurredRoadMask := imaging.Blur(roadMask, blurRadius)
|
blurredRoadMask := imaging.Blur(roadGrayMask, blurRadius)
|
||||||
|
|
||||||
blurredHeightmap := imaging.Blur(heightmap, blurRadius)
|
blurredHeightmap := imaging.Blur(heightmap, blurRadius)
|
||||||
|
|
||||||
@@ -146,8 +160,8 @@ func FlattenRoadAreas(heightmap image.Image, roadPixels []image.Point) image.Ima
|
|||||||
return composite
|
return composite
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateTrees places trees on the map based on coverage and noise
|
// GenerateTrees places trees on the map based on coverage and noise.
|
||||||
func GenerateTrees(img *image.RGBA, lakePixels, roadPixels, buildingPixels []image.Point, minTreeSize, maxTreeSize, treeCoverage, treeClumpiness float64, seed int64) []image.Point {
|
func GenerateTrees(img *image.RGBA, waterMask, roadMask, buildingMask *PixelMask, minTreeSize, maxTreeSize, treeCoverage, treeClumpiness float64, seed int64) *PixelMask {
|
||||||
width := img.Bounds().Dx()
|
width := img.Bounds().Dx()
|
||||||
height := img.Bounds().Dy()
|
height := img.Bounds().Dy()
|
||||||
|
|
||||||
@@ -179,19 +193,14 @@ func GenerateTrees(img *image.RGBA, lakePixels, roadPixels, buildingPixels []ima
|
|||||||
}
|
}
|
||||||
threshold := uint8(255 * (1 - (treeCoverage / 100.0)))
|
threshold := uint8(255 * (1 - (treeCoverage / 100.0)))
|
||||||
|
|
||||||
isLake := make(map[image.Point]bool)
|
if waterMask == nil {
|
||||||
for _, p := range lakePixels {
|
waterMask = NewPixelMask(width, height)
|
||||||
isLake[p] = true
|
|
||||||
}
|
}
|
||||||
|
if roadMask == nil {
|
||||||
isRoad := make(map[image.Point]bool)
|
roadMask = NewPixelMask(width, height)
|
||||||
for _, p := range roadPixels {
|
|
||||||
isRoad[p] = true
|
|
||||||
}
|
}
|
||||||
|
if buildingMask == nil {
|
||||||
isBuilding := make(map[image.Point]bool)
|
buildingMask = NewPixelMask(width, height)
|
||||||
for _, p := range buildingPixels {
|
|
||||||
isBuilding[p] = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
randSrc := rand.New(rand.NewSource(seed))
|
randSrc := rand.New(rand.NewSource(seed))
|
||||||
@@ -202,7 +211,7 @@ func GenerateTrees(img *image.RGBA, lakePixels, roadPixels, buildingPixels []ima
|
|||||||
for range numClumpTrees {
|
for range numClumpTrees {
|
||||||
for range 100 {
|
for range 100 {
|
||||||
p := image.Point{X: randSrc.Intn(width), Y: randSrc.Intn(height)}
|
p := image.Point{X: randSrc.Intn(width), Y: randSrc.Intn(height)}
|
||||||
if treeNoiseMap.GrayAt(p.X, p.Y).Y >= threshold && !isLake[p] && !isRoad[p] && !isBuilding[p] {
|
if treeNoiseMap.GrayAt(p.X, p.Y).Y >= threshold && !waterMask.GetPoint(p) && !roadMask.GetPoint(p) && !buildingMask.GetPoint(p) {
|
||||||
initialPoints = append(initialPoints, p)
|
initialPoints = append(initialPoints, p)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -211,10 +220,9 @@ func GenerateTrees(img *image.RGBA, lakePixels, roadPixels, buildingPixels []ima
|
|||||||
|
|
||||||
minRadius := minTreeSize
|
minRadius := minTreeSize
|
||||||
allPoints := poissonDiscSampling(width, height, minRadius, 30, initialPoints, func(p image.Point) bool {
|
allPoints := poissonDiscSampling(width, height, minRadius, 30, initialPoints, func(p image.Point) bool {
|
||||||
return treeNoiseMap.GrayAt(p.X, p.Y).Y >= threshold && !isLake[p] && !isRoad[p] && !isBuilding[p]
|
return treeNoiseMap.GrayAt(p.X, p.Y).Y >= threshold && !waterMask.GetPoint(p) && !roadMask.GetPoint(p) && !buildingMask.GetPoint(p)
|
||||||
}, seed)
|
}, seed)
|
||||||
|
|
||||||
var treePixels []image.Point
|
|
||||||
numGoroutines := runtime.NumCPU()
|
numGoroutines := runtime.NumCPU()
|
||||||
if len(allPoints) < numGoroutines {
|
if len(allPoints) < numGoroutines {
|
||||||
numGoroutines = len(allPoints)
|
numGoroutines = len(allPoints)
|
||||||
@@ -251,7 +259,7 @@ func GenerateTrees(img *image.RGBA, lakePixels, roadPixels, buildingPixels []ima
|
|||||||
for y := p.Y - int(r); y <= p.Y+int(r); y++ {
|
for y := p.Y - int(r); y <= p.Y+int(r); y++ {
|
||||||
for x := p.X - int(r); x <= p.X+int(r); x++ {
|
for x := p.X - int(r); x <= p.X+int(r); x++ {
|
||||||
pt := image.Point{X: x, Y: y}
|
pt := image.Point{X: x, Y: y}
|
||||||
if !pt.In(img.Bounds()) || isLake[pt] || isRoad[pt] || isBuilding[pt] {
|
if !pt.In(img.Bounds()) || waterMask.GetPoint(pt) || roadMask.GetPoint(pt) || buildingMask.GetPoint(pt) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,11 +277,14 @@ func GenerateTrees(img *image.RGBA, lakePixels, roadPixels, buildingPixels []ima
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(results)
|
close(results)
|
||||||
|
|
||||||
|
treeMask := NewPixelMask(width, height)
|
||||||
for res := range results {
|
for res := range results {
|
||||||
treePixels = append(treePixels, res...)
|
for _, p := range res {
|
||||||
|
treeMask.SetPoint(p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return treePixels
|
return treeMask
|
||||||
}
|
}
|
||||||
|
|
||||||
// poissonDiscSampling generates randomly distributed points with minimum radius separation
|
// poissonDiscSampling generates randomly distributed points with minimum radius separation
|
||||||
|
|||||||
@@ -899,7 +899,7 @@ func clamp01(v float64) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateRivers creates rivers flowing across the map from edge to edge
|
// GenerateRivers creates rivers flowing across the map from edge to edge
|
||||||
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) {
|
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, *PixelMask) {
|
||||||
if numRivers == 0 {
|
if numRivers == 0 {
|
||||||
return inputImage, nil
|
return inputImage, nil
|
||||||
}
|
}
|
||||||
@@ -910,16 +910,15 @@ func GenerateRivers(width, height, numRivers int, minWidth, maxWidth, curvyness
|
|||||||
draw.Draw(canvas, canvas.Bounds(), inputImage, image.Point{}, draw.Src)
|
draw.Draw(canvas, canvas.Bounds(), inputImage, image.Point{}, draw.Src)
|
||||||
}
|
}
|
||||||
|
|
||||||
var allRiverPixels []image.Point
|
riverMask := NewPixelMask(width, height)
|
||||||
randSrc := rand.New(rand.NewSource(seed))
|
randSrc := rand.New(rand.NewSource(seed))
|
||||||
avgDim := float64(width+height) / 2.0
|
avgDim := float64(width+height) / 2.0
|
||||||
|
|
||||||
// Build water pixel lookup maps
|
// Build water pixel lookup maps
|
||||||
isWater := make(map[image.Point]bool)
|
isWater := BuildMaskFromLakes(width, height, lakes)
|
||||||
lakePixelMap := make(map[image.Point]int)
|
lakePixelMap := make(map[image.Point]int)
|
||||||
for i, lake := range lakes {
|
for i, lake := range lakes {
|
||||||
for _, p := range lake {
|
for _, p := range lake {
|
||||||
isWater[p] = true
|
|
||||||
lakePixelMap[p] = i
|
lakePixelMap[p] = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -957,7 +956,7 @@ func GenerateRivers(width, height, numRivers int, minWidth, maxWidth, curvyness
|
|||||||
|
|
||||||
// Check for intersections with existing water
|
// Check for intersections with existing water
|
||||||
for _, p := range path {
|
for _, p := range path {
|
||||||
if isWater[p] {
|
if isWater.GetPoint(p) {
|
||||||
if lakeIndex, isLake := lakePixelMap[p]; isLake {
|
if lakeIndex, isLake := lakePixelMap[p]; isLake {
|
||||||
// End river at lake center if it intersects
|
// End river at lake center if it intersects
|
||||||
lakeCenter := findCenter(lakes[lakeIndex])
|
lakeCenter := findCenter(lakes[lakeIndex])
|
||||||
@@ -976,12 +975,12 @@ func GenerateRivers(width, height, numRivers int, minWidth, maxWidth, curvyness
|
|||||||
radius := riverWidthPx / 2.0
|
radius := riverWidthPx / 2.0
|
||||||
|
|
||||||
for _, p := range path {
|
for _, p := range path {
|
||||||
drawCircle(canvas, p, radius, color.RGBA{R: 0, G: 0, B: 255, A: 255}, &allRiverPixels, isWater, heightmap, riverWidthVariability, riverEdgeRoughness)
|
drawCircle(canvas, p, radius, color.RGBA{R: 0, G: 0, B: 255, A: 255}, riverMask, isWater, heightmap, riverWidthVariability, riverEdgeRoughness)
|
||||||
}
|
}
|
||||||
r.Points = path
|
r.Points = path
|
||||||
}
|
}
|
||||||
|
|
||||||
return canvas, allRiverPixels
|
return canvas, riverMask
|
||||||
}
|
}
|
||||||
|
|
||||||
// bresenhamRiver draws a line between control points using Bresenham's algorithm
|
// bresenhamRiver draws a line between control points using Bresenham's algorithm
|
||||||
@@ -1118,7 +1117,7 @@ func getPointOnEdge(width, height, edge int, randSrc *rand.Rand) image.Point {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// drawCircle draws a circular river cross-section with sine wave edge roughening
|
// drawCircle draws a circular river cross-section with sine wave edge roughening
|
||||||
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) {
|
func drawCircle(img *image.RGBA, center image.Point, radius float64, c color.Color, riverMask, isWater *PixelMask, heightmap image.Image, riverWidthVariability, riverEdgeRoughness float64) {
|
||||||
bounds := img.Bounds()
|
bounds := img.Bounds()
|
||||||
|
|
||||||
largeAmplitude := (radius * 0.5) * (riverWidthVariability / 100.0)
|
largeAmplitude := (radius * 0.5) * (riverWidthVariability / 100.0)
|
||||||
@@ -1143,10 +1142,10 @@ func drawCircle(img *image.RGBA, center image.Point, radius float64, c color.Col
|
|||||||
effectiveRadius := radius + waveOffset
|
effectiveRadius := radius + waveOffset
|
||||||
|
|
||||||
if dist <= effectiveRadius {
|
if dist <= effectiveRadius {
|
||||||
if !isWater[p] {
|
if !isWater.GetPoint(p) {
|
||||||
img.Set(x, y, c)
|
img.Set(x, y, c)
|
||||||
*pixels = append(*pixels, p)
|
riverMask.SetPoint(p)
|
||||||
isWater[p] = true
|
isWater.SetPoint(p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user