added procedural building generation
This commit is contained in:
+209
-1
@@ -122,7 +122,16 @@ func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, r
|
||||
if shape == "mixed" {
|
||||
shape = chooseShape(randSrc, settings.BuildingShapeRatios)
|
||||
}
|
||||
pixels, ok := getBuildingPixels(center, size, shape, isWater, isRoad, isBuilding, width, height, randSrc)
|
||||
|
||||
var pixels []image.Point
|
||||
var ok bool
|
||||
|
||||
if shape == "procedural" {
|
||||
pixels, ok = getProceduralBuildingPixels(center, size, settings, isWater, isRoad, isBuilding, width, height, randSrc)
|
||||
} else {
|
||||
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 {
|
||||
@@ -138,6 +147,205 @@ func GenerateBuildings(img *image.RGBA, width, height int, settings *Settings, r
|
||||
return buildingPixels
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Determine complexity
|
||||
complexity := settings.MinBuildingComplexity
|
||||
if settings.BuildingComplexityRatio > randSrc.Float64()*100 {
|
||||
complexity = settings.MinBuildingComplexity + randSrc.Intn(settings.MaxBuildingComplexity-settings.MinBuildingComplexity+1)
|
||||
}
|
||||
|
||||
type shapeDescription struct {
|
||||
shape string
|
||||
center image.Point
|
||||
size float64
|
||||
}
|
||||
|
||||
var shapeDescriptions []shapeDescription
|
||||
var buildingCenter image.Point
|
||||
|
||||
// Generate component shapes
|
||||
for i := 0; i < complexity; i++ {
|
||||
shape := chooseShape(randSrc, settings.BuildingShapeRatios)
|
||||
componentSize := size * (0.5 + randSrc.Float64()*0.5) // Components can be 50-100% of the building size
|
||||
|
||||
var newCenter image.Point
|
||||
if i == 0 {
|
||||
newCenter = center
|
||||
buildingCenter = center
|
||||
} else {
|
||||
// Place subsequent components near existing ones
|
||||
prevShape := shapeDescriptions[randSrc.Intn(len(shapeDescriptions))]
|
||||
angle := randSrc.Float64() * 2 * math.Pi
|
||||
dist := componentSize * (0.25 + randSrc.Float64()*0.5) // Overlap between 25% and 75%
|
||||
newCenter = image.Point{
|
||||
X: prevShape.center.X + int(dist*math.Cos(angle)),
|
||||
Y: prevShape.center.Y + int(dist*math.Sin(angle)),
|
||||
}
|
||||
}
|
||||
shapeDescriptions = append(shapeDescriptions, shapeDescription{shape, newCenter, componentSize})
|
||||
}
|
||||
|
||||
// Find the bounding box of the unscaled building
|
||||
var minX, minY, maxX, maxY int
|
||||
for i, sd := range shapeDescriptions {
|
||||
halfSize := int(sd.size / 2)
|
||||
if i == 0 {
|
||||
minX, minY = sd.center.X-halfSize, sd.center.Y-halfSize
|
||||
maxX, maxY = sd.center.X+halfSize, sd.center.Y+halfSize
|
||||
} else {
|
||||
if sd.center.X-halfSize < minX {
|
||||
minX = sd.center.X - halfSize
|
||||
}
|
||||
if sd.center.Y-halfSize < minY {
|
||||
minY = sd.center.Y - halfSize
|
||||
}
|
||||
if sd.center.X+halfSize > maxX {
|
||||
maxX = sd.center.X + halfSize
|
||||
}
|
||||
if sd.center.Y+halfSize > maxY {
|
||||
maxY = sd.center.Y + halfSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate scaling factor
|
||||
currentWidth := float64(maxX - minX)
|
||||
currentHeight := float64(maxY - minY)
|
||||
scale := size / math.Max(currentWidth, currentHeight)
|
||||
|
||||
// Generate final pixels
|
||||
var finalPixels []image.Point
|
||||
pixelMap := make(map[image.Point]bool)
|
||||
for _, sd := range shapeDescriptions {
|
||||
scaledSize := sd.size * scale
|
||||
scaledCenterX := buildingCenter.X + int((float64(sd.center.X)-float64(minX)-currentWidth/2)*scale)
|
||||
scaledCenterY := buildingCenter.Y + int((float64(sd.center.Y)-float64(minY)-currentHeight/2)*scale)
|
||||
pixels, ok := getComponentPixels(image.Point{X: scaledCenterX, Y: scaledCenterY}, scaledSize, sd.shape, randSrc)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, p := range pixels {
|
||||
if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height || isWater[p] || isRoad[p] || isBuilding[p] {
|
||||
return nil, false
|
||||
}
|
||||
if !pixelMap[p] {
|
||||
finalPixels = append(finalPixels, p)
|
||||
pixelMap[p] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(finalPixels) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return finalPixels, true
|
||||
}
|
||||
|
||||
// scalePixels scales the building to the final size.
|
||||
func scalePixels(pixels []image.Point, finalSize float64) []image.Point {
|
||||
if len(pixels) == 0 {
|
||||
return pixels
|
||||
}
|
||||
|
||||
// Find the bounding box of the pixels
|
||||
minX, minY := pixels[0].X, pixels[0].Y
|
||||
maxX, maxY := pixels[0].X, pixels[0].Y
|
||||
for _, p := range pixels {
|
||||
if p.X < minX {
|
||||
minX = p.X
|
||||
}
|
||||
if p.Y < minY {
|
||||
minY = p.Y
|
||||
}
|
||||
if p.X > maxX {
|
||||
maxX = p.X
|
||||
}
|
||||
if p.Y > maxY {
|
||||
maxY = p.Y
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the current dimensions
|
||||
currentWidth := float64(maxX - minX)
|
||||
currentHeight := float64(maxY - minY)
|
||||
|
||||
// Determine the scaling factor
|
||||
scale := finalSize / math.Max(currentWidth, currentHeight)
|
||||
|
||||
// Calculate the center of the bounding box
|
||||
centerX := float64(minX) + currentWidth/2
|
||||
centerY := float64(minY) + currentHeight/2
|
||||
|
||||
// Scale and translate the pixels
|
||||
var scaledPixels []image.Point
|
||||
pixelMap := make(map[image.Point]bool) // To avoid duplicate pixels
|
||||
for _, p := range pixels {
|
||||
// Translate to origin
|
||||
translatedX := float64(p.X) - centerX
|
||||
translatedY := float64(p.Y) - centerY
|
||||
|
||||
// Scale
|
||||
scaledX := translatedX * scale
|
||||
scaledY := translatedY * scale
|
||||
|
||||
// Translate back to the center
|
||||
finalX := int(math.Round(scaledX + centerX))
|
||||
finalY := int(math.Round(scaledY + centerY))
|
||||
|
||||
newPoint := image.Point{X: finalX, Y: finalY}
|
||||
if !pixelMap[newPoint] {
|
||||
scaledPixels = append(scaledPixels, newPoint)
|
||||
pixelMap[newPoint] = true
|
||||
}
|
||||
}
|
||||
|
||||
return scaledPixels
|
||||
}
|
||||
|
||||
// getComponentPixels generates the pixels for a single shape component without collision checks.
|
||||
func getComponentPixels(center image.Point, size float64, shape string, randSrc *rand.Rand) ([]image.Point, bool) {
|
||||
var pixels []image.Point
|
||||
var halfSize = int(size / 2)
|
||||
|
||||
switch shape {
|
||||
case "squares":
|
||||
for y := center.Y - halfSize; y <= center.Y+halfSize; y++ {
|
||||
for x := center.X - halfSize; x <= center.X+halfSize; x++ {
|
||||
pixels = append(pixels, image.Point{X: x, Y: y})
|
||||
}
|
||||
}
|
||||
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 {
|
||||
pixels = append(pixels, image.Point{X: x, Y: y})
|
||||
}
|
||||
}
|
||||
}
|
||||
case "rectangles":
|
||||
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
|
||||
for y := center.Y - halfH; y <= center.Y+halfH; y++ {
|
||||
for x := center.X - halfW; x <= center.X+halfW; x++ {
|
||||
pixels = append(pixels, image.Point{X: x, Y: y})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pixels, len(pixels) > 0
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user