diff --git a/buildings.go b/buildings.go index 23f4562..a6961e4 100644 --- a/buildings.go +++ b/buildings.go @@ -173,22 +173,68 @@ func GenerateBuildings( searchTries := 100 // Number of attempts to find a spot for a building around an anchor maxPlacementAttempts := settings.NumBuildings * 5 // To prevent infinite loops minBuildingSizePx, maxBuildingSizePx := getBuildingSizeRangePixels(settings, width, height) + anchorUsage := make(map[image.Point]int, len(anchorPoints)) + normalAnchorCap := 0 + exitAnchorCap := 0 + 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))) + } + + pickAnchorWithCapacity := func(candidates []image.Point, capLimit int) (image.Point, bool) { + if len(candidates) == 0 { + return image.Point{}, false + } + if capLimit <= 0 { + return candidates[randSrc.Intn(len(candidates))], true + } + best := candidates[randSrc.Intn(len(candidates))] + bestCount := anchorUsage[best] + for tries := 0; tries < min(16, len(candidates)*2); tries++ { + candidate := candidates[randSrc.Intn(len(candidates))] + count := anchorUsage[candidate] + if count < capLimit { + return candidate, true + } + if count < bestCount { + best = candidate + bestCount = count + } + } + if bestCount < capLimit { + return best, true + } + return image.Point{}, false + } 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 { // Buildings should only rarely use exit-road anchors. useExitAnchor := len(exitRoadAnchors) > 0 && randSrc.Float64() < 0.02 if useExitAnchor { - anchor = exitRoadAnchors[randSrc.Intn(len(exitRoadAnchors))] + if a, ok := pickAnchorWithCapacity(exitRoadAnchors, exitAnchorCap); ok { + anchor = a + usedRoadAnchor = true + } } else if len(normalRoadAnchors) > 0 { - anchor = normalRoadAnchors[randSrc.Intn(len(normalRoadAnchors))] + if a, ok := pickAnchorWithCapacity(normalRoadAnchors, normalAnchorCap); ok { + anchor = a + usedRoadAnchor = true + } } else if len(anchorPoints) > 0 { - anchor = anchorPoints[randSrc.Intn(len(anchorPoints))] - } else { + if a, ok := pickAnchorWithCapacity(anchorPoints, normalAnchorCap); ok { + anchor = a + usedRoadAnchor = true + } + } + if !usedRoadAnchor { p, ok := sampleRandomLandPoint(width, height, waterMask, roadMask, randSrc) if !ok { continue @@ -247,6 +293,9 @@ func GenerateBuildings( img.Set(p.X, p.Y, buildingColor) buildingMask.SetPoint(p) } + if usedRoadAnchor { + anchorUsage[anchor]++ + } buildings = append(buildings, pixels) buildingsPlaced++ break // Move to the next building diff --git a/roads.go b/roads.go index 452f64c..8e2367a 100644 --- a/roads.go +++ b/roads.go @@ -23,12 +23,21 @@ type PathPoint struct { IsBridge bool } +type RoadTier int + +const ( + RoadTierLocal RoadTier = iota + RoadTierCollector + RoadTierArterial +) + // Road represents a connection between two points of interest. type Road struct { Start, End *PointOfInterest Width int Points []PathPoint Importance int + Tier RoadTier } const ( @@ -198,7 +207,7 @@ func GenerateRoadsWithPOIs( } } - roadAnchors := roadMask.ToPoints() + roadAnchors := collectRoadAnchors(roads, settings, waterMask, width, height) return roadMask, bridgeMask, exitRoadMask, roadAnchors, roads } @@ -293,12 +302,13 @@ func generateEdgeToEdgeExitRoads(exitRoads, width, height int, settings *Setting start, end := sampleDifferentEdgePair(width, height, randSrc) start.IsExit = true end.IsExit = true - path := calculateRoadPath(start, end, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask, wallLayout) + path := calculateRoadPath(start, end, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask, wallLayout, RoadTierArterial) roads = append(roads, &Road{ Start: start, End: end, Points: path, Importance: 1, + Tier: RoadTierArterial, }) } return roads @@ -491,10 +501,23 @@ func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings, if roadTarget < len(pois)-1 { roadTarget = len(pois) - 1 } + collectorTarget := max(len(pois)-1, max(roadTarget, roadTarget+max(2, roadTarget/8))) + totalBudget := max(collectorTarget, roadTarget+max(3, roadTarget/4)) + + centerX := float64(width-1) * 0.5 + centerY := float64(height-1) * 0.5 + centerRadius := math.Max(math.Min(float64(width), float64(height))*0.28, 1) + + centerCloseness := func(p *PointOfInterest) float64 { + d := math.Hypot(float64(p.X)-centerX, float64(p.Y)-centerY) + return 1.0 - clamp01(d/centerRadius) + } type edgeCandidate struct { - a, b int - score float64 + a, b int + score float64 + dist float64 + arterialMean float64 } candidates := make([]edgeCandidate, 0, len(pois)*6) @@ -518,7 +541,13 @@ func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings, arterialBias := 1.0 - math.Abs(a.ArterialWeight-b.ArterialWeight) distanceBias := 1.0 - clamp01(d/(edgeDist*1.6)) score := arterialBias*0.65 + distanceBias*0.35 + randSrc.Float64()*0.08 - candidates = append(candidates, edgeCandidate{a: i, b: j, score: score}) + candidates = append(candidates, edgeCandidate{ + a: i, + b: j, + score: score, + dist: d, + arterialMean: (a.ArterialWeight + b.ArterialWeight) * 0.5, + }) } } if len(candidates) == 0 { @@ -529,14 +558,31 @@ func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings, return candidates[i].score > candidates[j].score }) - selected := make(map[uint64]bool, roadTarget) - adjAngles := make([][]float64, len(pois)) - selectedEdges := make([]edgeCandidate, 0, roadTarget) + type selectedEdge struct { + edge edgeCandidate + tier RoadTier + } - addEdge := func(pick edgeCandidate) { + selected := make(map[uint64]bool, totalBudget) + adjAngles := make([][]float64, len(pois)) + selectedEdges := make([]selectedEdge, 0, totalBudget) + + nodeCapacity := func(p *PointOfInterest, tier RoadTier) int { + base := max(1, p.TargetDegree) + switch tier { + case RoadTierArterial: + return max(base+2, 4) + case RoadTierCollector: + return base + 1 + default: + return base + } + } + + addEdge := func(pick edgeCandidate, tier RoadTier) { key := edgeKey(pick.a, pick.b) selected[key] = true - selectedEdges = append(selectedEdges, pick) + selectedEdges = append(selectedEdges, selectedEdge{edge: pick, tier: tier}) a := pois[pick.a] b := pois[pick.b] angAB := math.Atan2(float64(b.Y-a.Y), float64(b.X-a.X)) @@ -547,14 +593,14 @@ func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings, adjAngles[pick.b] = append(adjAngles[pick.b], angBA) } - canUseEdge := func(pick edgeCandidate) bool { + canUseEdge := func(pick edgeCandidate, tier RoadTier) bool { key := edgeKey(pick.a, pick.b) if selected[key] { return false } a := pois[pick.a] b := pois[pick.b] - if a.Connections >= max(1, a.TargetDegree+1) || b.Connections >= max(1, b.TargetDegree+1) { + if a.Connections >= nodeCapacity(a, tier) || b.Connections >= nodeCapacity(b, tier) { return false } angAB := math.Atan2(float64(b.Y-a.Y), float64(b.X-a.X)) @@ -565,33 +611,80 @@ func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings, return pick.score-degreePenalty(a, b) >= -0.4 } - // Phase 1: enforce one connected backbone. - start := 0 - bestWeight := pois[0].ArterialWeight - for i := 1; i < len(pois); i++ { - if pois[i].ArterialWeight > bestWeight { - start = i - bestWeight = pois[i].ArterialWeight + arterialCount := max(2, min(len(pois), min(10, 2+roadTarget/16))) + arterialOrder := make([]int, len(pois)) + for i := range arterialOrder { + arterialOrder[i] = i + } + sort.Slice(arterialOrder, func(i, j int) bool { + pi := pois[arterialOrder[i]] + pj := pois[arterialOrder[j]] + scoreI := pi.ArterialWeight - centerCloseness(pi)*0.22 + scoreJ := pj.ArterialWeight - centerCloseness(pj)*0.22 + if scoreI == scoreJ { + return centerCloseness(pi) < centerCloseness(pj) + } + return scoreI > scoreJ + }) + arterialNodes := make(map[int]bool, arterialCount) + arterialMinSpacing := edgeDist * 0.55 + arterialMinSpacing2 := arterialMinSpacing * arterialMinSpacing + for _, idx := range arterialOrder { + if len(arterialNodes) >= arterialCount { + break + } + keep := true + for chosen := range arterialNodes { + dx := float64(pois[chosen].X - pois[idx].X) + dy := float64(pois[chosen].Y - pois[idx].Y) + if dx*dx+dy*dy < arterialMinSpacing2 { + keep = false + break + } + } + if keep { + arterialNodes[idx] = true } } + for _, idx := range arterialOrder { + if len(arterialNodes) >= arterialCount { + break + } + arterialNodes[idx] = true + } + + start := arterialOrder[0] connected := make([]bool, len(pois)) connected[start] = true connectedCount := 1 - for connectedCount < len(pois) && len(selectedEdges) < roadTarget { + // Phase 1: connect the major arterial skeleton first. + arterialBudget := max(1, min(len(arterialNodes)-1, min(10, 2+roadTarget/20))) + for len(selectedEdges) < arterialBudget { bestIdx := -1 bestScore := -1.0 for idx, c := range candidates { + if !arterialNodes[c.a] || !arterialNodes[c.b] { + continue + } + if c.dist < edgeDist*0.35 { + continue + } aConn := connected[c.a] bConn := connected[c.b] if aConn == bConn { continue } - if !canUseEdge(c) { + if !canUseEdge(c, RoadTierArterial) { continue } - if c.score > bestScore { - bestScore = c.score + a := pois[c.a] + b := pois[c.b] + centerPenalty := centerCloseness(a) * centerCloseness(b) * 0.45 + degreePenalty := clamp01(float64(a.Connections+b.Connections) / 8.0) + score := c.arterialMean*0.58 + clamp01(c.dist/edgeDist)*0.27 + c.score*0.15 - centerPenalty - degreePenalty*0.18 + if score > bestScore { + bestScore = score bestIdx = idx } } @@ -599,7 +692,7 @@ func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings, break } pick := candidates[bestIdx] - addEdge(pick) + addEdge(pick, RoadTierArterial) if !connected[pick.a] { connected[pick.a] = true connectedCount++ @@ -610,25 +703,71 @@ func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings, } } - // Phase 2: add extra links up to the target. - for _, pick := range candidates { - if len(selectedEdges) >= roadTarget { + // Phase 2: connect remaining nodes with collector roads. + for connectedCount < len(pois) && len(selectedEdges) < collectorTarget { + bestIdx := -1 + bestScore := -1.0 + for idx, c := range candidates { + aConn := connected[c.a] + bConn := connected[c.b] + if aConn == bConn { + continue + } + if !canUseEdge(c, RoadTierCollector) { + continue + } + a := pois[c.a] + b := pois[c.b] + connectedBonus := 0.0 + if arterialNodes[c.a] || arterialNodes[c.b] { + connectedBonus = 0.20 + } + distScore := 1.0 - clamp01(c.dist/(edgeDist*1.1)) + centerPenalty := centerCloseness(a) * centerCloseness(b) * 0.35 + degreePenalty := clamp01(float64(a.Connections+b.Connections) / 7.0) + score := c.score*0.28 + c.arterialMean*0.27 + distScore*0.35 + connectedBonus - centerPenalty - degreePenalty*0.14 + if score > bestScore { + bestScore = score + bestIdx = idx + } + } + if bestIdx == -1 { break } - if !canUseEdge(pick) { + pick := candidates[bestIdx] + addEdge(pick, RoadTierCollector) + if !connected[pick.a] { + connected[pick.a] = true + connectedCount++ + } + if !connected[pick.b] { + connected[pick.b] = true + connectedCount++ + } + } + + // Phase 3: add shorter local links inside districts. + for _, pick := range candidates { + if len(selectedEdges) >= totalBudget { + break + } + if pick.dist > edgeDist*0.85 { continue } - addEdge(pick) + if !canUseEdge(pick, RoadTierLocal) { + continue + } + addEdge(pick, RoadTierLocal) } roads := make([]*Road, 0, len(selectedEdges)) avgDim := float64(width+height) / 2 for _, e := range selectedEdges { - a := pois[e.a] - b := pois[e.b] - path := calculateRoadPath(a, b, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask, wallLayout) + a := pois[e.edge.a] + b := pois[e.edge.b] + path := calculateRoadPath(a, b, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask, wallLayout, e.tier) 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, Tier: e.tier}) } return roads @@ -654,7 +793,7 @@ func appendExitRoads(roads []*Road, pois []*PointOfInterest, width, height int, continue } - path := calculateRoadPath(anchor, edgeNode, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask, wallLayout) + path := calculateRoadPath(anchor, edgeNode, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask, wallLayout, RoadTierArterial) anchor.Connections++ edgeNode.IsExit = true @@ -666,6 +805,7 @@ func appendExitRoads(roads []*Road, pois []*PointOfInterest, width, height int, End: edgeNode, Points: path, Importance: importance, + Tier: RoadTierArterial, }) usedEdgePoints = append(usedEdgePoints, image.Point{X: edgeNode.X, Y: edgeNode.Y}) exitRoadsAdded++ @@ -839,11 +979,33 @@ func estimateRoadTarget(settings *Settings) int { return settings.NumBuildings } divisor := float64(max(settings.BuildingsPerRoad, 1)) - roads := int(math.Round(float64(max(settings.NumBuildings, 1)) / divisor)) - if roads < 1 { - roads = 1 + buildings := float64(max(settings.NumBuildings, 1)) + baseRoads := buildings / divisor + scale := 1.0 + if buildings > 400 { + scale *= 0.96 } - return roads + if buildings > 1200 { + scale *= 0.92 + } + if buildings > 3000 { + scale *= 0.88 + } + roads := baseRoads * scale + if buildings > 200 { + roads += math.Pow((buildings-200.0)/divisor, 0.72) * 0.35 + } + if buildings > 1200 { + roads += math.Pow((buildings-1200.0)/(divisor*1.8), 0.68) * 0.22 + } + if buildings > 1000 { + roads *= 0.97 + } + result := int(math.Round(roads)) + if result < 1 { + result = 1 + } + return result } func edgeKey(a, b int) uint64 { @@ -1042,7 +1204,7 @@ func bresenhamRoad(path []image.Point) []image.Point { } // calculateRoadPath computes the path for a road including curves and bridges. -func calculateRoadPath(start, end *PointOfInterest, curvyness, avgDim float64, randSrc *rand.Rand, waterMask *PixelMask, wallLayout *FortificationLayout) []PathPoint { +func calculateRoadPath(start, end *PointOfInterest, curvyness, avgDim float64, randSrc *rand.Rand, waterMask *PixelMask, wallLayout *FortificationLayout, tier RoadTier) []PathPoint { dx := end.X - start.X dy := end.Y - start.Y dist := math.Hypot(float64(dx), float64(dy)) @@ -1052,77 +1214,59 @@ func calculateRoadPath(start, end *PointOfInterest, curvyness, avgDim float64, r return []PathPoint{{Point: p, IsBridge: waterMask.GetPoint(p)}} } + _ = wallLayout + curve := clamp(curvyness, 0, 1) - if curve <= 0 { + if curve <= 0.01 || dist < 10 { points := bresenhamRoad([]image.Point{{X: start.X, Y: start.Y}, {X: end.X, Y: end.Y}}) 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 toPathPoints(points, waterMask) - } - - baseControls := int(math.Max(12, dist/(22.0-14.0*strength))) - controlPoints := make([]image.Point, baseControls+1) perpX, perpY := -float64(dy)/dist, float64(dx)/dist - lengthScale := clamp(dist/(avgDim*0.55), 0.45, 2.4) + strength := math.Pow(curve, 1.1) + baseAmp := clamp(dist*(0.018+0.055*strength), 1.5, avgDim*0.06) - ampBase := clamp(dist*(0.01+0.13*strength*strength), 2, avgDim*0.16) - amp1 := ampBase * (0.9 + randSrc.Float64()*0.25) - amp2 := ampBase * (0.45 + randSrc.Float64()*0.20) - amp3 := ampBase * (0.20 + randSrc.Float64()*0.15) - - w1 := clamp(dist*(1.10-0.70*strength), 30, avgDim*0.95) - w2 := clamp(dist*(0.55-0.30*strength), 16, avgDim*0.55) - w3 := clamp(dist*(0.26-0.12*strength), 8, avgDim*0.30) - - type wave struct { - amplitude float64 - wavelength float64 - phase float64 - } - - waves := []wave{ - { - amplitude: amp1, - wavelength: w1, - phase: randSrc.Float64() * 2 * math.Pi, - }, - { - amplitude: amp2, - wavelength: w2, - phase: randSrc.Float64() * 2 * math.Pi, - }, - { - amplitude: amp3, - wavelength: w3, - phase: randSrc.Float64() * 2 * math.Pi, - }, - } - - for i := 0; i <= baseControls; i++ { - t := float64(i) / float64(baseControls) + addControl := func(points []image.Point, t, lateral float64) []image.Point { x := float64(start.X) + t*float64(dx) y := float64(start.Y) + t*float64(dy) - - // Keep endpoints fixed while allowing large mid-segment deflection. - envelope := math.Pow(math.Sin(t*math.Pi), 0.78) - offset := 0.0 - for _, w := range waves { - angle := (dist*t/w.wavelength)*2*math.Pi + w.phase - offset += math.Sin(angle) * w.amplitude - } - offset *= envelope * lengthScale - - x += offset * perpX - y += offset * perpY - controlPoints[i] = image.Point{X: int(math.Round(x)), Y: int(math.Round(y))} + x += lateral * perpX + y += lateral * perpY + return append(points, image.Point{X: int(math.Round(x)), Y: int(math.Round(y))}) } - points := bresenhamRoad(controlPoints) + polyline := []image.Point{{X: start.X, Y: start.Y}} + switch tier { + case RoadTierArterial: + lateral := baseAmp * (0.7 + randSrc.Float64()*0.35) + if randSrc.Float64() < 0.5 { + lateral = -lateral + } + polyline = addControl(polyline, 0.33, lateral*0.45) + polyline = addControl(polyline, 0.66, lateral) + case RoadTierCollector: + lateral := baseAmp * (0.9 + randSrc.Float64()*0.45) + if randSrc.Float64() < 0.5 { + lateral = -lateral + } + polyline = addControl(polyline, 0.35, lateral*0.65) + polyline = addControl(polyline, 0.72, lateral) + default: + lateralA := baseAmp * (0.65 + randSrc.Float64()*0.30) + lateralB := lateralA * (0.35 + randSrc.Float64()*0.25) + if randSrc.Float64() < 0.5 { + lateralA = -lateralA + } + if randSrc.Float64() < 0.8 { + lateralB = lateralA * (0.35 + randSrc.Float64()*0.20) + } else { + lateralB = -lateralB + } + polyline = addControl(polyline, 0.30, lateralA) + polyline = addControl(polyline, 0.68, lateralB) + } + polyline = append(polyline, image.Point{X: end.X, Y: end.Y}) + + points := bresenhamRoad(polyline) return toPathPoints(points, waterMask) } @@ -1475,12 +1619,13 @@ func ensureRoadNetworkConnected(roads []*Road, settings *Settings, randSrc *rand b := nodes[bestB] a.Connections++ b.Connections++ - path := calculateRoadPath(a, b, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask, wallLayout) + path := calculateRoadPath(a, b, settings.RoadCurvyness/100.0, avgDim, randSrc, waterMask, wallLayout, RoadTierCollector) roads = append(roads, &Road{ Start: a, End: b, Points: path, Importance: a.Connections + b.Connections + 2, + Tier: RoadTierCollector, }) } @@ -1698,6 +1843,7 @@ func generateGateRoads(wallLayout *FortificationLayout, settings *Settings, wate Points: path, Width: roadWidth, Importance: 10, // high importance so gate roads get wide treatment + Tier: RoadTierArterial, }) } return roads @@ -1825,8 +1971,93 @@ func ensureGateRoadConnections(gateRoads []*Road, allRoads []*Road, wallLayout * Points: path, Width: connW, Importance: 6, + Tier: RoadTierCollector, }) } } return append(allRoads, connectors...) } + +func collectRoadAnchors(roads []*Road, settings *Settings, waterMask *PixelMask, width, height int) []image.Point { + if len(roads) == 0 { + return nil + } + + minBuildingSizePx, maxBuildingSizePx := getBuildingSizeRangePixels(settings, width, height) + spacing := int(math.Round(clamp((minBuildingSizePx+maxBuildingSizePx)*0.5, 8, 28))) + if spacing < 6 { + spacing = 6 + } + cellSize := max(4, spacing/2) + + type anchorCell struct { + x int + y int + } + + cells := make(map[anchorCell][]image.Point) + anchors := make([]image.Point, 0, len(roads)*4) + + addAnchor := func(p image.Point) { + if p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height { + return + } + if waterMask != nil && waterMask.GetPoint(p) { + return + } + + cx := p.X / cellSize + cy := p.Y / cellSize + for dy := -1; dy <= 1; dy++ { + for dx := -1; dx <= 1; dx++ { + key := anchorCell{x: cx + dx, y: cy + dy} + for _, existing := range cells[key] { + ddx := existing.X - p.X + ddy := existing.Y - p.Y + if ddx*ddx+ddy*ddy < spacing*spacing { + return + } + } + } + } + + key := anchorCell{x: cx, y: cy} + cells[key] = append(cells[key], p) + anchors = append(anchors, p) + } + + nodeDegree := make(map[*PointOfInterest]int, len(roads)*2) + for _, road := range roads { + if road.Start != nil { + nodeDegree[road.Start]++ + } + if road.End != nil { + nodeDegree[road.End]++ + } + } + + for _, road := range roads { + if road.Start != nil && (nodeDegree[road.Start] > 1 || !road.Start.IsExit) { + addAnchor(image.Point{X: road.Start.X, Y: road.Start.Y}) + } + if road.End != nil && (nodeDegree[road.End] > 1 || !road.End.IsExit) { + addAnchor(image.Point{X: road.End.X, Y: road.End.Y}) + } + + step := spacing + if road.Tier == RoadTierArterial { + step = int(math.Round(float64(spacing) * 1.35)) + } + if step < 6 { + step = 6 + } + for i := step / 2; i < len(road.Points); i += step { + if road.Points[i].IsBridge { + continue + } + addAnchor(road.Points[i].Point) + } + } + + return anchors +}