Files
RPG_City_Maker_Reborn/buildings.go
T

228 lines
6.9 KiB
Go

package main
import (
"image"
"image/color"
"math"
"math/rand"
"sort"
)
// 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 {
// Early exit if no buildings are to be generated
if settings.NumBuildings == 0 {
return nil
}
// Initialize random number generator
randSrc := rand.New(rand.NewSource(seed))
buildingColor := color.RGBA{R: 128, G: 128, B: 128, A: 255} // Gray color for buildings
// Create lookup maps for water and road pixels for efficient collision detection
isWater := make(map[image.Point]bool)
for _, p := range allWaterPixels {
isWater[p] = true
}
isRoad := make(map[image.Point]bool)
for _, p := range roadPixels {
isRoad[p] = true
}
// Initialize building data structures
isBuilding := make(map[image.Point]bool)
var buildingPixels []image.Point
var anchorPoints []image.Point
// Determine anchor points for building placement
if len(roadPixels) > 0 {
anchorPoints = roadPixels
} else {
// If no roads, use all land pixels as anchors
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
p := image.Point{X: x, Y: y}
if !isWater[p] {
anchorPoints = append(anchorPoints, p)
}
}
}
}
// Early exit if no anchor points are available
if len(anchorPoints) == 0 {
return nil
}
// Sort anchor points for deterministic placement
sort.Slice(anchorPoints, func(i, j int) bool {
if anchorPoints[i].Y != anchorPoints[j].Y {
return anchorPoints[i].Y < anchorPoints[j].Y
}
return anchorPoints[i].X < anchorPoints[j].X
})
// Collect all land points for random placement
landPoints := make([]image.Point, 0, width*height)
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
p := image.Point{X: x, Y: y}
if !isWater[p] && !isRoad[p] {
landPoints = append(landPoints, p)
}
}
}
// Main loop for placing buildings
buildingsPlaced := 0
searchTries := 100 // Number of attempts to find a spot for a building around an anchor
maxPlacementAttempts := settings.NumBuildings * 5 // To prevent infinite loops
for buildingsPlaced < settings.NumBuildings && maxPlacementAttempts > 0 {
maxPlacementAttempts--
// Select an anchor point for the new building
var anchor image.Point
if randSrc.Float64() > settings.BuildingDistribution/100.0 {
// Place near roads or other existing features
anchor = anchorPoints[randSrc.Intn(len(anchorPoints))]
} else {
// Place randomly on any available land
if len(landPoints) == 0 {
continue // No land to place buildings on
}
anchor = landPoints[randSrc.Intn(len(landPoints))]
}
// Search for a valid building location around the anchor
for i := 0; i < searchTries; i++ {
searchRadius := float64(i) * 2.0 // Search in expanding circles
angle := randSrc.Float64() * 2 * math.Pi
dist := searchRadius * randSrc.Float64()
center := image.Point{
X: anchor.X + int(dist*math.Cos(angle)),
Y: anchor.Y + int(dist*math.Sin(angle)),
}
// For fully random distribution, pick any point on the map
if settings.BuildingDistribution == 100 {
center = image.Point{
X: randSrc.Intn(width),
Y: randSrc.Intn(height),
}
}
// Ensure the center point is within the map boundaries
if center.X < 0 || center.Y < 0 || center.X >= width || center.Y >= height {
continue
}
// Attempt to create a building at the selected center
size := settings.MinBuildingSize + randSrc.Float64()*(settings.MaxBuildingSize-settings.MinBuildingSize)
shape := settings.BuildingShape
if shape == "mixed" {
shape = chooseShape(randSrc, settings.BuildingShapeRatios)
}
pixels, ok := getBuildingPixels(center, size, shape, isWater, isRoad, isBuilding, width, height, randSrc)
if ok {
// If successful, draw the building and update data structures
for _, p := range pixels {
img.Set(p.X, p.Y, buildingColor)
isBuilding[p] = true
buildingPixels = append(buildingPixels, p)
}
buildingsPlaced++
break // Move to the next building
}
}
}
return buildingPixels
}
// chooseShape selects a building shape based on the provided ratios.
func chooseShape(randSrc *rand.Rand, ratios map[string]float64) string {
// Create a slice of shapes and their cumulative weights
var shapes []string
var weights []float64
var cumulativeWeight float64
for shape, weight := range ratios {
shapes = append(shapes, shape)
cumulativeWeight += weight
weights = append(weights, cumulativeWeight)
}
// Generate a random number between 0 and the total weight
randNum := randSrc.Float64() * cumulativeWeight
// Find the shape corresponding to the random number
for i, weight := range weights {
if randNum < weight {
return shapes[i]
}
}
// Default to the first shape if something goes wrong
return shapes[0]
}
// 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) {
var pixels []image.Point
var halfSize = int(size / 2)
// Generate pixels based on the selected building shape
switch shape {
case "squares":
for y := center.Y - halfSize; y <= center.Y+halfSize; y++ {
for x := center.X - halfSize; x <= center.X+halfSize; x++ {
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] {
return nil, false // Collision detected
}
pixels = append(pixels, p)
}
}
case "circles":
r2 := (size / 2) * (size / 2)
for y := center.Y - halfSize; y <= center.Y+halfSize; y++ {
for x := center.X - halfSize; x <= center.X+halfSize; x++ {
dx, dy := float64(x-center.X), float64(y-center.Y)
if dx*dx+dy*dy <= r2 {
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] {
return nil, false // Collision detected
}
pixels = append(pixels, p)
}
}
}
case "rectangles":
// Create rectangles with varied aspect ratios
longSide := size
shortSide := randSrc.Float64()*(size-float64(halfSize)) + float64(halfSize)
var w, h int
if randSrc.Intn(2) == 0 {
w, h = int(longSide), int(shortSide)
} else {
w, h = int(shortSide), int(longSide)
}
halfW, halfH := w/2, h/2
// Check for collisions and gather pixels
for y := center.Y - halfH; y <= center.Y+halfH; y++ {
for x := center.X - halfW; x <= center.X+halfW; x++ {
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] {
return nil, false // Collision detected
}
pixels = append(pixels, p)
}
}
}
// Final check to ensure pixels were generated
if len(pixels) == 0 {
return nil, false
}
return pixels, true
}