redid wall generation completely.
This commit is contained in:
@@ -169,23 +169,23 @@ func GenerateRoadsWithPOIs(
|
||||
if len(roads) == 0 {
|
||||
return NewPixelMask(width, height), NewPixelMask(width, height), NewPixelMask(width, height), nil, nil
|
||||
}
|
||||
roads = applyWallCrossingRules(roads, wallLayout, waterMask, randSrc)
|
||||
if len(roads) == 0 {
|
||||
return NewPixelMask(width, height), NewPixelMask(width, height), NewPixelMask(width, height), nil, nil
|
||||
}
|
||||
|
||||
// Add gate roads after wall-crossing rules (so they are never filtered out).
|
||||
if wallLayout != nil && len(wallLayout.Gates) > 0 {
|
||||
gateRoads := generateGateRoads(wallLayout, settings, waterMask, width, height, randSrc)
|
||||
roads = append(roads, gateRoads...)
|
||||
roads = ensureGateRoadConnections(gateRoads, roads, wallLayout, settings, waterMask, width, height, randSrc)
|
||||
}
|
||||
|
||||
roads = applyWallCrossingRules(roads, wallLayout, waterMask, randSrc)
|
||||
if len(roads) == 0 {
|
||||
return NewPixelMask(width, height), NewPixelMask(width, height), NewPixelMask(width, height), nil, nil
|
||||
}
|
||||
|
||||
roads = reduceRepeatedBridges(roads, waterMask, width, height, randSrc)
|
||||
if len(roads) == 0 {
|
||||
return NewPixelMask(width, height), NewPixelMask(width, height), NewPixelMask(width, height), nil, nil
|
||||
}
|
||||
roads = ensureRoadNetworkConnected(roads, settings, randSrc, waterMask, wallLayout, width, height)
|
||||
roads = applyWallCrossingRules(roads, wallLayout, waterMask, randSrc)
|
||||
assignRoadWidths(roads, settings, randSrc, width, height, wallLayout)
|
||||
|
||||
roadMask := NewPixelMask(width, height)
|
||||
@@ -655,32 +655,6 @@ func appendExitRoads(roads []*Road, pois []*PointOfInterest, width, height int,
|
||||
}
|
||||
|
||||
path := calculateRoadPath(anchor, edgeNode, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask, wallLayout)
|
||||
if wallLayout != nil && wallLayout.Mask != nil && len(crossedWallIDs(path, wallLayout)) == 0 {
|
||||
bestScore := -1.0
|
||||
bestAnchor := anchor
|
||||
bestPath := path
|
||||
for _, cand := range pois {
|
||||
testPath := calculateRoadPath(cand, edgeNode, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask, wallLayout)
|
||||
if len(crossedWallIDs(testPath, wallLayout)) == 0 {
|
||||
continue
|
||||
}
|
||||
d := math.Hypot(float64(cand.X-edgeNode.X), float64(cand.Y-edgeNode.Y))
|
||||
score := cand.ArterialWeight*2.0 + clamp(1.0-d/2000.0, 0, 1)
|
||||
if score > bestScore {
|
||||
bestScore = score
|
||||
bestAnchor = cand
|
||||
bestPath = testPath
|
||||
}
|
||||
}
|
||||
anchor = bestAnchor
|
||||
path = bestPath
|
||||
}
|
||||
if wallLayout != nil && wallLayout.Mask != nil && len(wallLayout.Coverages) > 0 {
|
||||
// Exit roads always use the gate-cheat when walls exist so they are always placeable.
|
||||
if forced, ok := forcePathThroughWallGate(anchor, edgeNode, wallLayout, waterMask); ok {
|
||||
path = forced
|
||||
}
|
||||
}
|
||||
|
||||
anchor.Connections++
|
||||
edgeNode.IsExit = true
|
||||
@@ -1081,14 +1055,14 @@ func calculateRoadPath(start, end *PointOfInterest, curvyness, avgDim float64, r
|
||||
curve := clamp(curvyness, 0, 1)
|
||||
if curve <= 0 {
|
||||
points := bresenhamRoad([]image.Point{{X: start.X, Y: start.Y}, {X: end.X, Y: end.Y}})
|
||||
return straightenPathAcrossWalls(toPathPoints(points, waterMask), wallLayout, waterMask)
|
||||
return toPathPoints(points, waterMask)
|
||||
}
|
||||
|
||||
// Non-linear scaling: low values stay fairly straight, high values become very winding.
|
||||
strength := math.Pow(curve, 1.35)
|
||||
if strength < 0.001 {
|
||||
points := bresenhamRoad([]image.Point{{X: start.X, Y: start.Y}, {X: end.X, Y: end.Y}})
|
||||
return straightenPathAcrossWalls(toPathPoints(points, waterMask), wallLayout, waterMask)
|
||||
return toPathPoints(points, waterMask)
|
||||
}
|
||||
|
||||
baseControls := int(math.Max(12, dist/(22.0-14.0*strength)))
|
||||
@@ -1149,7 +1123,7 @@ func calculateRoadPath(start, end *PointOfInterest, curvyness, avgDim float64, r
|
||||
}
|
||||
|
||||
points := bresenhamRoad(controlPoints)
|
||||
return straightenPathAcrossWalls(toPathPoints(points, waterMask), wallLayout, waterMask)
|
||||
return toPathPoints(points, waterMask)
|
||||
}
|
||||
|
||||
func toPathPoints(points []image.Point, waterMask *PixelMask) []PathPoint {
|
||||
@@ -1352,95 +1326,41 @@ func containsWallID(ids []int, wallID int) bool {
|
||||
}
|
||||
|
||||
func applyWallCrossingRules(roads []*Road, wallLayout *FortificationLayout, waterMask *PixelMask, randSrc *rand.Rand) []*Road {
|
||||
if len(roads) == 0 || wallLayout == nil || wallLayout.Mask == nil || len(wallLayout.Coverages) == 0 {
|
||||
if len(roads) == 0 || wallLayout == nil || wallLayout.Mask == nil {
|
||||
return roads
|
||||
}
|
||||
|
||||
// Straighten each wall crossing segment first.
|
||||
for _, road := range roads {
|
||||
road.Points = straightenPathAcrossWalls(road.Points, wallLayout, waterMask)
|
||||
}
|
||||
|
||||
type roadInfo struct {
|
||||
road *Road
|
||||
ids []int
|
||||
}
|
||||
infos := make([]roadInfo, 0, len(roads))
|
||||
for _, road := range roads {
|
||||
infos = append(infos, roadInfo{road: road, ids: crossedWallIDs(road.Points, wallLayout)})
|
||||
}
|
||||
|
||||
const repeatWallFactor = 0.55
|
||||
wallCrossCount := make(map[int]int)
|
||||
requiredWalls := make(map[int]bool)
|
||||
for i, cov := range wallLayout.Coverages {
|
||||
if cov < 95 {
|
||||
requiredWalls[i+1] = true
|
||||
}
|
||||
}
|
||||
|
||||
keep := make([]bool, len(infos))
|
||||
for i, info := range infos {
|
||||
if len(info.ids) == 0 {
|
||||
keep[i] = true
|
||||
continue
|
||||
}
|
||||
if info.road.Start.IsExit || info.road.End.IsExit {
|
||||
keep[i] = true
|
||||
for _, wid := range info.ids {
|
||||
wallCrossCount[wid]++
|
||||
}
|
||||
continue
|
||||
}
|
||||
if crossesSameWallMultipleTimes(info.road.Points, wallLayout) {
|
||||
keep[i] = false
|
||||
continue
|
||||
}
|
||||
|
||||
keepProb := 1.0
|
||||
for _, wid := range info.ids {
|
||||
c := wallCrossCount[wid]
|
||||
if c > 0 {
|
||||
keepProb *= math.Pow(repeatWallFactor, float64(c))
|
||||
}
|
||||
}
|
||||
if randSrc.Float64() <= keepProb {
|
||||
keep[i] = true
|
||||
for _, wid := range info.ids {
|
||||
wallCrossCount[wid]++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure at least one crossing on each wall unless its configured coverage is >= 95%.
|
||||
for wallID := range requiredWalls {
|
||||
if wallCrossCount[wallID] > 0 {
|
||||
continue
|
||||
}
|
||||
for i, info := range infos {
|
||||
if keep[i] {
|
||||
continue
|
||||
}
|
||||
if !containsWallID(info.ids, wallID) {
|
||||
continue
|
||||
}
|
||||
keep[i] = true
|
||||
for _, wid := range info.ids {
|
||||
wallCrossCount[wid]++
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_ = waterMask
|
||||
_ = randSrc
|
||||
filtered := make([]*Road, 0, len(roads))
|
||||
for i, info := range infos {
|
||||
if keep[i] {
|
||||
filtered = append(filtered, info.road)
|
||||
for _, road := range roads {
|
||||
if pathRespectsWallPassages(road.Points, wallLayout.Mask, wallLayout.GateMask) {
|
||||
filtered = append(filtered, road)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func pathRespectsWallPassages(points []PathPoint, exclusionMask, gateMask *PixelMask) bool {
|
||||
if len(points) == 0 || exclusionMask == nil {
|
||||
return true
|
||||
}
|
||||
for _, pp := range points {
|
||||
x := pp.Point.X
|
||||
y := pp.Point.Y
|
||||
if !exclusionMask.InBounds(x, y) {
|
||||
continue
|
||||
}
|
||||
if !exclusionMask.GetXY(x, y) {
|
||||
continue
|
||||
}
|
||||
if gateMask != nil && gateMask.GetXY(x, y) {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func crossesSameWallMultipleTimes(points []PathPoint, wallLayout *FortificationLayout) bool {
|
||||
if wallLayout == nil || wallLayout.Mask == nil || len(points) < 2 {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user