changed how road curvyness works
This commit is contained in:
@@ -45,7 +45,7 @@ func GenerateRoads(width, height int, settings *Settings, noiseImg image.Image,
|
|||||||
|
|
||||||
var allRoadPixels []image.Point
|
var allRoadPixels []image.Point
|
||||||
for _, road := range roads {
|
for _, road := range roads {
|
||||||
roadPixels := drawCurve(img, road.Start, road.End, road.Control, roadColor, road.Width)
|
roadPixels := drawRoad(img, road.Points, roadColor, road.Width)
|
||||||
allRoadPixels = append(allRoadPixels, roadPixels...)
|
allRoadPixels = append(allRoadPixels, roadPixels...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +149,9 @@ func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings,
|
|||||||
|
|
||||||
visited[startNode] = true
|
visited[startNode] = true
|
||||||
|
|
||||||
|
avgDim := float64(width+height) / 2.0
|
||||||
|
numControlPoints := max(int(avgDim*0.03), 60)
|
||||||
|
|
||||||
for len(visited) < len(pois) {
|
for len(visited) < len(pois) {
|
||||||
var closest *PointOfInterest
|
var closest *PointOfInterest
|
||||||
var fromNode *PointOfInterest
|
var fromNode *PointOfInterest
|
||||||
@@ -197,19 +200,12 @@ func connectPOIs(pois []*PointOfInterest, width, height int, settings *Settings,
|
|||||||
}
|
}
|
||||||
existingRoads[key] = true
|
existingRoads[key] = true
|
||||||
|
|
||||||
// Create control point for curve
|
path := calculateRoadPath(fromNode, closest, settings.RoadCurvyness/100.0, avgDim, randSrc, numControlPoints)
|
||||||
midX := (fromNode.X + closest.X) / 2
|
|
||||||
midY := (fromNode.Y + closest.Y) / 2
|
|
||||||
dist := math.Sqrt(math.Pow(float64(fromNode.X-closest.X), 2) + math.Pow(float64(fromNode.Y-closest.Y), 2))
|
|
||||||
offset := dist * (settings.RoadCurvyness / 100.0) * (randSrc.Float64() - 0.5)
|
|
||||||
|
|
||||||
controlX := int(float64(midX) + offset)
|
|
||||||
controlY := int(float64(midY) + offset)
|
|
||||||
|
|
||||||
roads = append(roads, &Road{
|
roads = append(roads, &Road{
|
||||||
Start: fromNode,
|
Start: fromNode,
|
||||||
End: closest,
|
End: closest,
|
||||||
Control: &PointOfInterest{X: controlX, Y: controlY},
|
Points: path,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// No more reachable POIs
|
// No more reachable POIs
|
||||||
@@ -245,20 +241,111 @@ func assignRoadWidths(roads []*Road, settings *Settings) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawCurve(img *image.RGBA, p0, p1, p2 *PointOfInterest, col color.Color, width int) []image.Point {
|
func drawRoad(img *image.RGBA, points []image.Point, col color.Color, width int) []image.Point {
|
||||||
var points []image.Point
|
var roadPixels []image.Point
|
||||||
var lastX, lastY int = -1, -1
|
for i := 0; i < len(points)-1; i++ {
|
||||||
for t := 0.0; t <= 1.0; t += 0.01 {
|
linePoints := drawLine(img, points[i].X, points[i].Y, points[i+1].X, points[i+1].Y, col, width)
|
||||||
x := (1-t)*(1-t)*float64(p0.X) + 2*(1-t)*t*float64(p2.X) + t*t*float64(p1.X)
|
roadPixels = append(roadPixels, linePoints...)
|
||||||
y := (1-t)*(1-t)*float64(p0.Y) + 2*(1-t)*t*float64(p2.Y) + t*t*float64(p1.Y)
|
|
||||||
|
|
||||||
if lastX != -1 {
|
|
||||||
linePoints := drawLine(img, lastX, lastY, int(x), int(y), col, width)
|
|
||||||
points = append(points, linePoints...)
|
|
||||||
}
|
|
||||||
lastX, lastY = int(x), int(y)
|
|
||||||
}
|
}
|
||||||
return points
|
return roadPixels
|
||||||
|
}
|
||||||
|
|
||||||
|
func bresenhamRoad(path []image.Point) []image.Point {
|
||||||
|
if len(path) < 2 {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullPath []image.Point
|
||||||
|
for i := 0; i < len(path)-1; i++ {
|
||||||
|
p1, p2 := path[i], path[i+1]
|
||||||
|
dx, dy := p2.X-p1.X, p2.Y-p1.Y
|
||||||
|
absDx, absDy := int(math.Abs(float64(dx))), int(math.Abs(float64(dy)))
|
||||||
|
sx, sy := 1, 1
|
||||||
|
if dx < 0 {
|
||||||
|
sx = -1
|
||||||
|
}
|
||||||
|
if dy < 0 {
|
||||||
|
sy = -1
|
||||||
|
}
|
||||||
|
err := absDx - absDy
|
||||||
|
|
||||||
|
x, y := p1.X, p1.Y
|
||||||
|
for {
|
||||||
|
fullPath = append(fullPath, image.Point{X: x, Y: y})
|
||||||
|
if x == p2.X && y == p2.Y {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e2 := 2 * err
|
||||||
|
if e2 > -absDy {
|
||||||
|
err -= absDy
|
||||||
|
x += sx
|
||||||
|
}
|
||||||
|
if e2 < absDx {
|
||||||
|
err += absDx
|
||||||
|
y += sy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fullPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateRoadPath(start, end *PointOfInterest, curvyness, avgDim float64, randSrc *rand.Rand, numControlPoints int) []image.Point {
|
||||||
|
dx := end.X - start.X
|
||||||
|
dy := end.Y - start.Y
|
||||||
|
dist := math.Sqrt(float64(dx*dx + dy*dy))
|
||||||
|
|
||||||
|
if dist == 0 {
|
||||||
|
return []image.Point{{X: start.X, Y: start.Y}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if curvyness == 0 {
|
||||||
|
return bresenhamRoad([]image.Point{{X: start.X, Y: start.Y}, {X: end.X, Y: end.Y}})
|
||||||
|
}
|
||||||
|
|
||||||
|
type wave struct {
|
||||||
|
amplitude float64
|
||||||
|
numWaves float64
|
||||||
|
phase float64
|
||||||
|
}
|
||||||
|
|
||||||
|
waves := make([]wave, 3)
|
||||||
|
amp := (avgDim / 10.0) * curvyness
|
||||||
|
mainWavelength := avgDim / 4.0
|
||||||
|
if mainWavelength < 1 {
|
||||||
|
mainWavelength = 1
|
||||||
|
}
|
||||||
|
baseNumWaves := (dist / mainWavelength) * curvyness
|
||||||
|
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
freqMultiplier := 1.0 + float64(i)
|
||||||
|
randomizedNumWaves := baseNumWaves * freqMultiplier * (0.75 + randSrc.Float64()*0.5)
|
||||||
|
waves[i] = wave{
|
||||||
|
amplitude: amp,
|
||||||
|
numWaves: randomizedNumWaves,
|
||||||
|
phase: randSrc.Float64() * 2 * math.Pi,
|
||||||
|
}
|
||||||
|
amp /= 3
|
||||||
|
}
|
||||||
|
|
||||||
|
controlPoints := make([]image.Point, numControlPoints+1)
|
||||||
|
for i := 0; i <= numControlPoints; i++ {
|
||||||
|
t := float64(i) / float64(numControlPoints)
|
||||||
|
x := float64(start.X) + t*float64(dx)
|
||||||
|
y := float64(start.Y) + t*float64(dy)
|
||||||
|
perpX, perpY := -float64(dy)/dist, float64(dx)/dist
|
||||||
|
|
||||||
|
totalOffset := 0.0
|
||||||
|
for _, w := range waves {
|
||||||
|
totalOffset += math.Sin(t*w.numWaves*2*math.Pi+w.phase) * w.amplitude
|
||||||
|
}
|
||||||
|
totalOffset *= math.Sin(t * math.Pi)
|
||||||
|
|
||||||
|
x += totalOffset * perpX
|
||||||
|
y += totalOffset * perpY
|
||||||
|
controlPoints[i] = image.Point{X: int(math.Round(x)), Y: int(math.Round(y))}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bresenhamRoad(controlPoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bresenham's line algorithm for drawing segments of the curve
|
// Bresenham's line algorithm for drawing segments of the curve
|
||||||
|
|||||||
-134
@@ -246,137 +246,3 @@ func poissonDiscSampling(width, height int, minRadius float64, k int, initialPoi
|
|||||||
}
|
}
|
||||||
return points
|
return points
|
||||||
}
|
}
|
||||||
func bresenham(path []image.Point) []image.Point {
|
|
||||||
if len(path) < 2 {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
var fullPath []image.Point
|
|
||||||
for i := range len(path) - 1 {
|
|
||||||
p1, p2 := path[i], path[i+1]
|
|
||||||
dx, dy := p2.X-p1.X, p2.Y-p1.Y
|
|
||||||
absDx, absDy := int(math.Abs(float64(dx))), int(math.Abs(float64(dy)))
|
|
||||||
sx, sy := 1, 1
|
|
||||||
if dx < 0 {
|
|
||||||
sx = -1
|
|
||||||
}
|
|
||||||
if dy < 0 {
|
|
||||||
sy = -1
|
|
||||||
}
|
|
||||||
err := absDx - absDy
|
|
||||||
|
|
||||||
x, y := p1.X, p1.Y
|
|
||||||
for {
|
|
||||||
fullPath = append(fullPath, image.Point{X: x, Y: y})
|
|
||||||
if x == p2.X && y == p2.Y {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
e2 := 2 * err
|
|
||||||
if e2 > -absDy {
|
|
||||||
err -= absDy
|
|
||||||
x += sx
|
|
||||||
}
|
|
||||||
if e2 < absDx {
|
|
||||||
err += absDx
|
|
||||||
y += sy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fullPath
|
|
||||||
}
|
|
||||||
func calculatePath(start, end image.Point, curvyness, avgDim float64, randSrc *rand.Rand, numControlPoints int) []image.Point {
|
|
||||||
|
|
||||||
dx := end.X - start.X
|
|
||||||
|
|
||||||
dy := end.Y - start.Y
|
|
||||||
|
|
||||||
dist := math.Sqrt(float64(dx*dx + dy*dy))
|
|
||||||
|
|
||||||
if dist == 0 {
|
|
||||||
|
|
||||||
return []image.Point{start}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if curvyness == 0 {
|
|
||||||
|
|
||||||
return bresenham([]image.Point{start, end})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type wave struct {
|
|
||||||
amplitude float64
|
|
||||||
|
|
||||||
numWaves float64
|
|
||||||
|
|
||||||
phase float64
|
|
||||||
}
|
|
||||||
|
|
||||||
waves := make([]wave, 3)
|
|
||||||
|
|
||||||
amp := (avgDim / 10.0) * curvyness
|
|
||||||
|
|
||||||
mainWavelength := avgDim / 4.0
|
|
||||||
|
|
||||||
if mainWavelength < 1 {
|
|
||||||
|
|
||||||
mainWavelength = 1
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
baseNumWaves := (dist / mainWavelength) * curvyness
|
|
||||||
|
|
||||||
for i := range 3 {
|
|
||||||
|
|
||||||
freqMultiplier := 1.0 + float64(i)
|
|
||||||
|
|
||||||
randomizedNumWaves := baseNumWaves * freqMultiplier * (0.75 + randSrc.Float64()*0.5)
|
|
||||||
|
|
||||||
waves[i] = wave{
|
|
||||||
|
|
||||||
amplitude: amp,
|
|
||||||
|
|
||||||
numWaves: randomizedNumWaves,
|
|
||||||
|
|
||||||
phase: randSrc.Float64() * 2 * math.Pi,
|
|
||||||
}
|
|
||||||
|
|
||||||
amp /= 3
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
controlPoints := make([]image.Point, numControlPoints+1)
|
|
||||||
|
|
||||||
for i := range numControlPoints + 1 {
|
|
||||||
|
|
||||||
t := float64(i) / float64(numControlPoints)
|
|
||||||
|
|
||||||
x := float64(start.X) + t*float64(dx)
|
|
||||||
|
|
||||||
y := float64(start.Y) + t*float64(dy)
|
|
||||||
|
|
||||||
perpX, perpY := -float64(dy)/dist, float64(dx)/dist
|
|
||||||
|
|
||||||
totalOffset := 0.0
|
|
||||||
|
|
||||||
for _, w := range waves {
|
|
||||||
|
|
||||||
totalOffset += math.Sin(t*w.numWaves*2*math.Pi+w.phase) * w.amplitude
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply an envelope to ensure start/end points are anchored
|
|
||||||
|
|
||||||
totalOffset *= math.Sin(t * math.Pi)
|
|
||||||
|
|
||||||
x += totalOffset * perpX
|
|
||||||
|
|
||||||
y += totalOffset * perpY
|
|
||||||
|
|
||||||
controlPoints[i] = image.Point{X: int(math.Round(x)), Y: int(math.Round(y))}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return bresenham(controlPoints)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ func GenerateRivers(width, height, numRivers int, minWidth, maxWidth, curvyness
|
|||||||
r.Start = getPointOnEdge(width, height, startEdge, randSrc)
|
r.Start = getPointOnEdge(width, height, startEdge, randSrc)
|
||||||
r.End = getPointOnEdge(width, height, endEdge, randSrc)
|
r.End = getPointOnEdge(width, height, endEdge, randSrc)
|
||||||
|
|
||||||
path := calculatePath(r.Start, r.End, curvyness/100.0, avgDim, randSrc, numControlPoints)
|
path := calculateRiverPath(r.Start, r.End, curvyness/100.0, avgDim, randSrc, numControlPoints)
|
||||||
|
|
||||||
for _, p := range path {
|
for _, p := range path {
|
||||||
if isWater[p] {
|
if isWater[p] {
|
||||||
@@ -244,7 +244,7 @@ func GenerateRivers(width, height, numRivers int, minWidth, maxWidth, curvyness
|
|||||||
// Intersection is with another river
|
// Intersection is with another river
|
||||||
r.End = p
|
r.End = p
|
||||||
}
|
}
|
||||||
path = calculatePath(r.Start, r.End, curvyness/100.0, avgDim, randSrc, numControlPoints)
|
path = calculateRiverPath(r.Start, r.End, curvyness/100.0, avgDim, randSrc, numControlPoints)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,6 +262,104 @@ func GenerateRivers(width, height, numRivers int, minWidth, maxWidth, curvyness
|
|||||||
return canvas, allRiverPixels
|
return canvas, allRiverPixels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bresenhamRiver(path []image.Point) []image.Point {
|
||||||
|
if len(path) < 2 {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullPath []image.Point
|
||||||
|
for i := 0; i < len(path)-1; i++ {
|
||||||
|
p1, p2 := path[i], path[i+1]
|
||||||
|
dx, dy := p2.X-p1.X, p2.Y-p1.Y
|
||||||
|
absDx, absDy := int(math.Abs(float64(dx))), int(math.Abs(float64(dy)))
|
||||||
|
sx, sy := 1, 1
|
||||||
|
if dx < 0 {
|
||||||
|
sx = -1
|
||||||
|
}
|
||||||
|
if dy < 0 {
|
||||||
|
sy = -1
|
||||||
|
}
|
||||||
|
err := absDx - absDy
|
||||||
|
|
||||||
|
x, y := p1.X, p1.Y
|
||||||
|
for {
|
||||||
|
fullPath = append(fullPath, image.Point{X: x, Y: y})
|
||||||
|
if x == p2.X && y == p2.Y {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e2 := 2 * err
|
||||||
|
if e2 > -absDy {
|
||||||
|
err -= absDy
|
||||||
|
x += sx
|
||||||
|
}
|
||||||
|
if e2 < absDx {
|
||||||
|
err += absDx
|
||||||
|
y += sy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fullPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateRiverPath(start, end image.Point, curvyness, avgDim float64, randSrc *rand.Rand, numControlPoints int) []image.Point {
|
||||||
|
dx := end.X - start.X
|
||||||
|
dy := end.Y - start.Y
|
||||||
|
dist := math.Sqrt(float64(dx*dx + dy*dy))
|
||||||
|
|
||||||
|
if dist == 0 {
|
||||||
|
return []image.Point{start}
|
||||||
|
}
|
||||||
|
|
||||||
|
if curvyness == 0 {
|
||||||
|
return bresenhamRiver([]image.Point{start, end})
|
||||||
|
}
|
||||||
|
|
||||||
|
type wave struct {
|
||||||
|
amplitude float64
|
||||||
|
numWaves float64
|
||||||
|
phase float64
|
||||||
|
}
|
||||||
|
|
||||||
|
waves := make([]wave, 3)
|
||||||
|
amp := (avgDim / 10.0) * curvyness
|
||||||
|
mainWavelength := avgDim / 4.0
|
||||||
|
if mainWavelength < 1 {
|
||||||
|
mainWavelength = 1
|
||||||
|
}
|
||||||
|
baseNumWaves := (dist / mainWavelength) * curvyness
|
||||||
|
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
freqMultiplier := 1.0 + float64(i)
|
||||||
|
randomizedNumWaves := baseNumWaves * freqMultiplier * (0.75 + randSrc.Float64()*0.5)
|
||||||
|
waves[i] = wave{
|
||||||
|
amplitude: amp,
|
||||||
|
numWaves: randomizedNumWaves,
|
||||||
|
phase: randSrc.Float64() * 2 * math.Pi,
|
||||||
|
}
|
||||||
|
amp /= 3
|
||||||
|
}
|
||||||
|
|
||||||
|
controlPoints := make([]image.Point, numControlPoints+1)
|
||||||
|
for i := 0; i <= numControlPoints; i++ {
|
||||||
|
t := float64(i) / float64(numControlPoints)
|
||||||
|
x := float64(start.X) + t*float64(dx)
|
||||||
|
y := float64(start.Y) + t*float64(dy)
|
||||||
|
perpX, perpY := -float64(dy)/dist, float64(dx)/dist
|
||||||
|
|
||||||
|
totalOffset := 0.0
|
||||||
|
for _, w := range waves {
|
||||||
|
totalOffset += math.Sin(t*w.numWaves*2*math.Pi+w.phase) * w.amplitude
|
||||||
|
}
|
||||||
|
totalOffset *= math.Sin(t * math.Pi)
|
||||||
|
|
||||||
|
x += totalOffset * perpX
|
||||||
|
y += totalOffset * perpY
|
||||||
|
controlPoints[i] = image.Point{X: int(math.Round(x)), Y: int(math.Round(y))}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bresenhamRiver(controlPoints)
|
||||||
|
}
|
||||||
|
|
||||||
func findCenter(pixels []image.Point) image.Point {
|
func findCenter(pixels []image.Point) image.Point {
|
||||||
if len(pixels) == 0 {
|
if len(pixels) == 0 {
|
||||||
return image.Point{}
|
return image.Point{}
|
||||||
|
|||||||
Reference in New Issue
Block a user