changed how building distribution works

This commit is contained in:
grimsace
2026-04-06 09:50:05 -05:00
parent fdac5ef45a
commit 4888e10f73
3 changed files with 89 additions and 22 deletions
+80 -21
View File
@@ -170,18 +170,28 @@ func GenerateBuildings(
// 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
maxPlacementAttempts := settings.NumBuildings * 3 // To prevent infinite loops
minBuildingSizePx, maxBuildingSizePx := getBuildingSizeRangePixels(settings, width, height)
avgBuildingSizePx := (minBuildingSizePx + maxBuildingSizePx) * 0.5
anchorUsage := make(map[image.Point]int, len(anchorPoints))
normalAnchorCap := 0
exitAnchorCap := 0
distributionFactor := clamp01(settings.BuildingDistribution / 100.0)
roadPlacementBias := 1.0 - distributionFactor
roadAnchorMinOffset := clamp(avgBuildingSizePx*0.55, 2, 16)
roadAnchorMaxOffset := roadAnchorMinOffset + math.Pow(distributionFactor, 1.05)*clamp(avgBuildingSizePx*13.0, 16, 180)
roadSearchTries := 42
randomSearchTries := 18
if len(normalRoadAnchors) > 0 {
normalAnchorCap = max(2, int(math.Ceil((float64(settings.NumBuildings)/float64(len(normalRoadAnchors)))*1.15)))
}
if len(exitRoadAnchors) > 0 {
exitAnchorCap = max(1, int(math.Ceil((float64(settings.NumBuildings)/float64(len(exitRoadAnchors)))*0.20)))
}
if settings.NumBuildings <= 80 {
normalAnchorCap = max(normalAnchorCap, 6)
exitAnchorCap = max(exitAnchorCap, 2)
}
pickAnchorWithCapacity := func(candidates []image.Point, capLimit int) (image.Point, bool) {
if len(candidates) == 0 {
@@ -209,13 +219,28 @@ func GenerateBuildings(
return image.Point{}, false
}
pickLeastUsedAnchor := func(candidates []image.Point) (image.Point, bool) {
if len(candidates) == 0 {
return image.Point{}, false
}
best := candidates[randSrc.Intn(len(candidates))]
bestCount := anchorUsage[best]
for _, candidate := range candidates {
if count := anchorUsage[candidate]; count < bestCount {
best = candidate
bestCount = count
}
}
return best, true
}
for buildingsPlaced < settings.NumBuildings && maxPlacementAttempts > 0 {
maxPlacementAttempts--
// Select an anchor point for the new building
var anchor image.Point
usedRoadAnchor := false
if randSrc.Float64() > settings.BuildingDistribution/100.0 {
if len(anchorPoints) > 0 && randSrc.Float64() < roadPlacementBias {
// Buildings should only rarely use exit-road anchors.
useExitAnchor := len(exitRoadAnchors) > 0 && randSrc.Float64() < 0.02
if useExitAnchor {
@@ -235,31 +260,72 @@ func GenerateBuildings(
}
}
if !usedRoadAnchor {
p, ok := sampleRandomLandPoint(width, height, waterMask, roadMask, randSrc)
if !ok {
continue
if a, ok := pickLeastUsedAnchor(normalRoadAnchors); ok {
anchor = a
usedRoadAnchor = true
} else if a, ok := pickLeastUsedAnchor(anchorPoints); ok {
anchor = a
usedRoadAnchor = true
} else {
p, ok := sampleRandomLandPoint(width, height, waterMask, roadMask, randSrc)
if !ok {
continue
}
anchor = p
}
anchor = p
}
} else {
}
if !usedRoadAnchor {
p, ok := sampleRandomLandPoint(width, height, waterMask, roadMask, randSrc)
if !ok {
continue // No land to place buildings on
if a, ok := pickLeastUsedAnchor(normalRoadAnchors); ok {
anchor = a
usedRoadAnchor = true
} else if a, ok := pickLeastUsedAnchor(anchorPoints); ok {
anchor = a
usedRoadAnchor = true
} else {
continue // No land to place buildings on
}
} else {
anchor = p
}
anchor = p
}
searchTries := randomSearchTries
if usedRoadAnchor {
searchTries = roadSearchTries
}
// Search for a valid building location around the anchor
for i := 0; i < searchTries; i++ {
searchRadius := float64(i) * 2.0 // Search in expanding circles
// Sample building shape/size first so road-adjacent placement can leave enough setback.
size := minBuildingSizePx + randSrc.Float64()*(maxBuildingSizePx-minBuildingSizePx)
shape := settings.BuildingShape
if shape == "mixed" {
shape = chooseShape(randSrc, settings.BuildingShapeRatios)
}
angle := randSrc.Float64() * 2 * math.Pi
dist := searchRadius * randSrc.Float64()
dist := 0.0
if usedRoadAnchor {
minSetback := max(roadAnchorMinOffset, size*0.85+2.0)
progress := float64(i) / float64(max(searchTries-1, 1))
searchRadius := minSetback + progress*(roadAnchorMaxOffset-minSetback)
if searchRadius < minSetback {
searchRadius = minSetback
}
dist = minSetback + randSrc.Float64()*(searchRadius-minSetback)
} else {
searchRadius := clamp(float64(i+1)*avgBuildingSizePx*0.45, avgBuildingSizePx, avgBuildingSizePx*5.5)
dist = randSrc.Float64() * searchRadius
}
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 {
// Non-road placement is fully random over the map.
if !usedRoadAnchor {
center = image.Point{
X: randSrc.Intn(width),
Y: randSrc.Intn(height),
@@ -271,13 +337,6 @@ func GenerateBuildings(
continue
}
// Attempt to create a building at the selected center
size := minBuildingSizePx + randSrc.Float64()*(maxBuildingSizePx-minBuildingSizePx)
shape := settings.BuildingShape
if shape == "mixed" {
shape = chooseShape(randSrc, settings.BuildingShapeRatios)
}
var pixels []image.Point
var ok bool