Skip to content

Commit

Permalink
Add an attempt to render polygons instead of individual tiles for bou…
Browse files Browse the repository at this point in the history
…ndaries
  • Loading branch information
BenOvermyer committed May 17, 2019
1 parent d083756 commit f8930b1
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 24 deletions.
10 changes: 10 additions & 0 deletions pkg/country/country.go
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/ironarachne/world/pkg/grid"
"github.com/ironarachne/world/pkg/heraldry"
"github.com/ironarachne/world/pkg/region"
"github.com/ironarachne/world/pkg/worldmap"
)

// Country is a geographic and political area
Expand Down Expand Up @@ -57,6 +58,15 @@ func Generate() Country {
return country
}

// GetAllTiles returns a slice of all tiles in the country
func (c Country) GetAllTiles(worldMap worldmap.WorldMap) []worldmap.Tile {
coords := c.GetAllTileCoordinates()

tiles := worldmap.FindTilesByCoordinates(coords, worldMap.Tiles)

return tiles
}

// GetAllTileCoordinates returns a slice of all coordinates in the country
func (c Country) GetAllTileCoordinates() []grid.Coordinate {
coords := []grid.Coordinate{}
Expand Down
58 changes: 58 additions & 0 deletions pkg/grid/coordinates.go
@@ -1,5 +1,20 @@
package grid

import "math"

// Coordinate is an x, y location
type Coordinate struct {
X int
Y int
}

// Distance returns the distance between two points
func Distance(a Coordinate, b Coordinate) float64 {
distance := math.Sqrt(math.Pow(float64(b.X-a.X), 2) + math.Pow(float64(b.Y-a.Y), 2))

return distance
}

// Equals tests the equality of two coordinates
func Equals(a Coordinate, b Coordinate) bool {
if a.X == b.X && a.Y == b.Y {
Expand All @@ -8,6 +23,19 @@ func Equals(a Coordinate, b Coordinate) bool {
return false
}

// GetUniqueCoordinates returns only unique coordinates from a slice of coordinates
func GetUniqueCoordinates(coords []Coordinate) []Coordinate {
unique := []Coordinate{}

for _, c := range coords {
if !InSlice(c, unique) {
unique = append(unique, c)
}
}

return unique
}

// InSlice tests to see if a coordinate is in a slice of coordinates
func InSlice(c Coordinate, target []Coordinate) bool {
for _, t := range target {
Expand All @@ -30,3 +58,33 @@ func RemoveCoordinatesFromSlice(coordinatesToRemove []Coordinate, target []Coord

return coords
}

// SortCoordinatesByDistance sorts coordinates by distance
func SortCoordinatesByDistance(coords []Coordinate) []Coordinate {
nearest := 0

for i, c := range coords {
nearest = NearestCoordinateIndex(c, coords)
coords[i], coords[nearest] = coords[nearest], coords[i]
}

return coords
}

// NearestCoordinateIndex finds the index in a slice of coordinates of the nearest coordinate to the given one
func NearestCoordinateIndex(p Coordinate, coords []Coordinate) int {
var distance float64

smallestDistance := Distance(p, coords[0])
smallest := 0

for i, c := range coords {
distance = Distance(p, c)
if distance < smallestDistance {
smallest = i
smallestDistance = distance
}
}

return smallest
}
36 changes: 36 additions & 0 deletions pkg/grid/edges.go
@@ -0,0 +1,36 @@
package grid

// Edge is a line
type Edge struct {
A Coordinate
B Coordinate
}

// GetUniqueEdges returns all the unique edges in a slice
func GetUniqueEdges(edges []Edge) []Edge {
unique := []Edge{}

for _, e := range edges {
if !isEdgeInSlice(e, unique) {
unique = append(unique, e)
}
}

return unique
}

func isEdgeEqual(a Edge, b Edge) bool {
if (Equals(a.A, b.A) && Equals(a.B, b.B)) || (Equals(a.A, b.B) && Equals(a.B, b.A)) {
return true
}
return false
}

func isEdgeInSlice(edge Edge, edges []Edge) bool {
for _, e := range edges {
if isEdgeEqual(edge, e) {
return true
}
}
return false
}
6 changes: 0 additions & 6 deletions pkg/grid/grid.go
@@ -1,11 +1,5 @@
package grid

// Coordinate is an x, y location
type Coordinate struct {
X int
Y int
}

// Grid is a grid
type Grid struct {
Coordinates [][]Coordinate
Expand Down
6 changes: 4 additions & 2 deletions pkg/world/world.go
Expand Up @@ -139,11 +139,13 @@ func Generate() World {

for _, c := range world.Countries {
pattern = ""
style = "fill:" + c.Heraldry.Tinctures[0] + ";fill-opacity:0.5"
style = "fill:" + c.Heraldry.Tinctures[0] + ";fill-opacity:0.5;stroke:#000000;stroke-width:2"
if len(c.Heraldry.Patterns) > 0 {
pattern = c.Heraldry.Patterns[0]
}
boundary = worldmap.CreateBoundary(c.Name, style, pattern, c.GetAllTileCoordinates())
boundary = worldmap.CreateBoundary(c.Name, style, pattern, c.GetAllTiles(world.WorldMap))
boundary.Edges = world.WorldMap.CalculateBoundaryEdges(boundary)
boundary.Vertices = world.WorldMap.CalculateBoundaryVertices(boundary)
boundaries = append(boundaries, boundary)
}
world.WorldMap.Boundaries = boundaries
Expand Down
43 changes: 36 additions & 7 deletions pkg/worldmap/boundaries.go
Expand Up @@ -6,20 +6,49 @@ import (

// Boundary is a political boundary or other area
type Boundary struct {
Label string
Style string
Pattern string
TileCoordinates []grid.Coordinate
Label string
Style string
Pattern string
Tiles []Tile
Edges []grid.Edge
Vertices []grid.Coordinate
}

// CreateBoundary creates a boundary given a set of coordinates
func CreateBoundary(label string, style string, pattern string, coords []grid.Coordinate) Boundary {
// CalculateBoundaryEdges calculates the edges of a boundary given a tile pixel size and coordinates
func (worldMap WorldMap) CalculateBoundaryEdges(boundary Boundary) []grid.Edge {
var edges []grid.Edge
allEdges := []grid.Edge{}

for _, t := range boundary.Tiles {
edges = t.CalculateEdges(worldMap.TileHeight, worldMap.TileWidth)
allEdges = append(allEdges, edges...)
}

boundaryEdges := grid.GetUniqueEdges(allEdges)

return boundaryEdges
}

// CalculateBoundaryVertices calculates the vertices of a boundary given a tile pixel size and coordinates
func (worldMap WorldMap) CalculateBoundaryVertices(boundary Boundary) []grid.Coordinate {
allVertices := []grid.Coordinate{}

for _, e := range boundary.Edges {
allVertices = append(allVertices, e.A)
allVertices = append(allVertices, e.B)
}

return allVertices
}

// CreateBoundary creates a boundary given a set of tiles
func CreateBoundary(label string, style string, pattern string, tiles []Tile) Boundary {
boundary := Boundary{}

boundary.Label = label
boundary.Style = style
boundary.Pattern = pattern
boundary.TileCoordinates = coords
boundary.Tiles = tiles

return boundary
}
50 changes: 41 additions & 9 deletions pkg/worldmap/renderers.go
Expand Up @@ -6,21 +6,33 @@ import (
svg "github.com/ajstarks/svgo"
"github.com/ironarachne/world/pkg/grid"
"github.com/ironarachne/world/pkg/heraldry"
"github.com/ironarachne/world/pkg/slices"
)

// RenderAsSVG renders a world map to SVG
func (worldMap WorldMap) RenderAsSVG() string {
var labelMidpoint grid.Coordinate
tileWidth := 32
tileHeight := 32
var line string
var xs []int
var ys []int
var maxX int
var maxY int
var minX int
var minY int

tileWidth := worldMap.TileWidth
tileHeight := worldMap.TileHeight

existingPatterns := []string{}

buffer := new(bytes.Buffer)
canvas := svg.New(buffer)
canvas.Start(worldMap.Width*tileWidth, worldMap.Height*tileHeight)
canvas.Def()
for _, b := range worldMap.Boundaries {
if b.Pattern != "" {
if b.Pattern != "" && !slices.StringIn(b.Pattern, existingPatterns) {
heraldry.InsertErmine(canvas, b.Pattern)
existingPatterns = append(existingPatterns, b.Pattern)
}
}
canvas.DefEnd()
Expand All @@ -31,13 +43,33 @@ func (worldMap WorldMap) RenderAsSVG() string {
}
for _, b := range worldMap.Boundaries {
labelMidpoint = grid.Coordinate{X: 0, Y: 0}
for _, c := range b.TileCoordinates {
labelMidpoint.X += c.X * tileWidth
labelMidpoint.Y += c.Y * tileHeight
canvas.Rect(c.X*tileWidth, c.Y*tileHeight, tileWidth, tileHeight, b.Style)
line = "M"
xs = []int{}
ys = []int{}
minX = b.Vertices[0].X
minY = b.Vertices[0].Y
maxX = 0
maxY = 0
for _, v := range b.Vertices {
if minX > v.X {
minX = v.X
}
if minY > v.Y {
minY = v.Y
}
if maxX < v.X {
maxX = v.X
}
if maxY < v.Y {
maxY = v.Y
}
xs = append(xs, v.X)
ys = append(ys, v.Y)
}
labelMidpoint.X = int(labelMidpoint.X / len(b.TileCoordinates))
labelMidpoint.Y = int(labelMidpoint.Y/len(b.TileCoordinates)) + tileHeight
labelMidpoint.X = int((minX + maxX) / 2)
labelMidpoint.Y = int((minY + maxY) / 2)
canvas.Polygon(xs, ys, b.Style)
canvas.Path(line, b.Style)
canvas.Text(labelMidpoint.X, labelMidpoint.Y, b.Label, "fill:#000000;font-size:20px")
}
canvas.End()
Expand Down
56 changes: 56 additions & 0 deletions pkg/worldmap/tiles.go
Expand Up @@ -10,6 +10,8 @@ import (
// Tile is a map tile
type Tile struct {
Coordinate grid.Coordinate
Points []grid.Coordinate
Edges []grid.Edge
Temperature int
Humidity int
IsInhabited bool
Expand All @@ -22,6 +24,40 @@ type tileGenerator struct {
tileType string
}

// CalculateTilePoints returns the pixel positions for the vertices of the tile
func (worldMap WorldMap) CalculateTilePoints(tile Tile) []grid.Coordinate {
coords := []grid.Coordinate{}
tileHeight := worldMap.TileHeight
tileWidth := worldMap.TileWidth

point := grid.Coordinate{X: tileWidth * tile.Coordinate.X, Y: tileHeight * tile.Coordinate.Y}
coords = append(coords, point)
point = grid.Coordinate{X: (tileWidth * tile.Coordinate.X) + tileWidth, Y: tileHeight * tile.Coordinate.Y}
coords = append(coords, point)
point = grid.Coordinate{X: (tileWidth * tile.Coordinate.X) + tileWidth, Y: (tileHeight * tile.Coordinate.Y) + tileHeight}
coords = append(coords, point)
point = grid.Coordinate{X: tileWidth * tile.Coordinate.X, Y: (tileHeight * tile.Coordinate.Y) + tileHeight}
coords = append(coords, point)

return coords
}

// CalculateEdges returns the pixel edges of a tile
func (tile Tile) CalculateEdges(tileHeight int, tileWidth int) []grid.Edge {
edges := []grid.Edge{}

edge1 := grid.Edge{A: tile.Points[0], B: tile.Points[1]}
edges = append(edges, edge1)
edge2 := grid.Edge{A: tile.Points[1], B: tile.Points[2]}
edges = append(edges, edge2)
edge3 := grid.Edge{A: tile.Points[2], B: tile.Points[3]}
edges = append(edges, edge3)
edge4 := grid.Edge{A: tile.Points[3], B: tile.Points[0]}
edges = append(edges, edge4)

return edges
}

// AllCoordinates gets a slice of all coordinates in the map
func (worldMap WorldMap) AllCoordinates() []grid.Coordinate {
coords := []grid.Coordinate{}
Expand Down Expand Up @@ -56,6 +92,8 @@ func (worldMap WorldMap) initializeTiles() [][]Tile {
var c grid.Coordinate
var row []Tile
var tile Tile
var points []grid.Coordinate
var edges []grid.Edge

for y := 0; y < worldMap.Height; y++ {
row = []Tile{}
Expand All @@ -72,6 +110,10 @@ func (worldMap WorldMap) initializeTiles() [][]Tile {
IsInhabited: false,
TileType: "ocean",
}
points = worldMap.CalculateTilePoints(tile)
tile.Points = points
edges = tile.CalculateEdges(worldMap.TileHeight, worldMap.TileWidth)
tile.Edges = edges
row = append(row, tile)
}
tiles = append(tiles, row)
Expand Down Expand Up @@ -247,6 +289,20 @@ func (worldMap WorldMap) findArtifactTilesOfType(tileType string) []grid.Coordin
return filteredCoords
}

// FindTilesByCoordinates finds tiles that match the given slice of coordinates
func FindTilesByCoordinates(coords []grid.Coordinate, tiles [][]Tile) []Tile {
var tile Tile

filteredTiles := []Tile{}

for _, c := range coords {
tile = tiles[c.Y][c.X]
filteredTiles = append(filteredTiles, tile)
}

return filteredTiles
}

// FindTilesOfType finds tiles that match the tile type
func FindTilesOfType(tileType string, tiles [][]Tile) []grid.Coordinate {
coords := []grid.Coordinate{}
Expand Down

0 comments on commit f8930b1

Please sign in to comment.