mirror of
https://github.com/rastapasta/mapscii.git
synced 2024-11-21 15:43:08 +01:00
📐 using earcut for triangulation, implementing own Canvas
This commit is contained in:
parent
a3fb7149f2
commit
06528f488d
@ -27,7 +27,6 @@ No web browser around? No worries - discover the planet in your console!
|
||||
## Behind the scenes
|
||||
### Libraries
|
||||
#### Mastering the console
|
||||
* [`drawille-canvas-blessed-contrib`](https://github.com/yaronn/drawille-canvas-blessed-contrib/) for [braille](http://www.fileformat.info/info/unicode/block/braille_patterns/utf8test.htm) rendering
|
||||
* [`x256`](https://github.com/substack/node-x256) for finding nearest xterm-256 [color codes](https://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg)
|
||||
* [`term-mouse`](https://github.com/CoderPuppy/term-mouse) for mouse handling
|
||||
* [`keypress`](https://github.com/TooTallNate/keypress) for input handling
|
||||
@ -38,9 +37,10 @@ No web browser around? No worries - discover the planet in your console!
|
||||
* [`vector-tile-js`](https://github.com/mapbox/vector-tile-js) for [VectorTile](https://github.com/mapbox/vector-tile-spec/tree/master/2.1) parsing
|
||||
|
||||
#### Juggling the vectors and numbers
|
||||
* [`pnltri`](https://github.com/jahting/pnltri.js) for polygon triangulation to draw 'em filled
|
||||
* [`earcut`](https://github.com/mapbox/earcut) for polygon triangulation
|
||||
* [`rbush`](https://github.com/mourner/rbush) for 2D spatial indexing based label and mouse collision detection
|
||||
* [`gl-matrix`](https://github.com/toji/gl-matrix) for vector and matrix operations
|
||||
* [`breseham`](https://github.com/madbence/node-bresenham) for line calculations
|
||||
* [`sphericalmercator`](https://github.com/mapbox/node-sphericalmercator) for [EPSG:3857](http://spatialreference.org/ref/sr-org/6864/) <> [WGS84](http://spatialreference.org/ref/epsg/wgs-84/) conversions
|
||||
|
||||
### TODOs
|
||||
|
@ -20,12 +20,12 @@
|
||||
"author": "Michael Straßburger <codepoet@cpan.org>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bresenham": "0.0.4",
|
||||
"coffee-script": "^1.10.0",
|
||||
"drawille-canvas-blessed-contrib": "git+https://github.com/rastapasta/drawille-canvas-blessed-contrib.git",
|
||||
"earcut": "^2.1.1",
|
||||
"gl-matrix": "^2.3.2",
|
||||
"keypress": "^0.2.1",
|
||||
"pbf": "^3.0.0",
|
||||
"pnltri": "^2.1.1",
|
||||
"rbush": "^2.0.1",
|
||||
"sphericalmercator": "^1.0.5",
|
||||
"term-mouse": "^0.1.1",
|
||||
|
@ -5,8 +5,9 @@
|
||||
Simple pixel to barille character mapper
|
||||
|
||||
Implementation inspired by node-drawille (https://github.com/madbence/node-drawille)
|
||||
* added written text support
|
||||
* added color support
|
||||
* added support for filled polygons
|
||||
* added text label support
|
||||
* general optimizations
|
||||
-> more bit shifting/operations, less Math.floors
|
||||
|
||||
@ -78,12 +79,5 @@ module.exports = class BrailleBuffer
|
||||
@colorBuffer[idx] = @termColor color
|
||||
|
||||
writeText: (text, x, y, color, center = true) ->
|
||||
x -= text.length/2 if center
|
||||
x -= text.length/2+1 if center
|
||||
@setChar text.charAt(i), x+i*2, y, color for i in [0...text.length]
|
||||
|
||||
# buffer = new BrailleBuffer 100, 16
|
||||
# for i in [0...255]
|
||||
# buffer.setPixel i, 8+8*Math.cos(i/10*Math.PI/2), i
|
||||
#
|
||||
# buffer.writeText "ruth", 10, 0, 111
|
||||
# console.log buffer.frame()
|
||||
|
@ -2,40 +2,107 @@
|
||||
termap - Terminal Map Viewer
|
||||
by Michael Strassburger <codepoet@cpan.org>
|
||||
|
||||
Extends drawille-canvas to add additional drawing functions.
|
||||
To be PRed up the tree at some point.
|
||||
Simple pixel to barille character mapper
|
||||
|
||||
Implementation inspired by node-drawille-canvas (https://github.com/madbence/node-drawille-canvas)
|
||||
* added support for filled polygons
|
||||
* improved text rendering
|
||||
|
||||
Will most likely be turned into a stand alone module at some point
|
||||
###
|
||||
|
||||
BlessedCanvas = require 'drawille-canvas-blessed-contrib'
|
||||
vec2 = require('gl-matrix').vec2
|
||||
bresenham = require 'bresenham'
|
||||
glMatrix = require 'gl-matrix'
|
||||
earcut = require 'earcut'
|
||||
|
||||
module.exports = class Canvas extends BlessedCanvas
|
||||
BrailleBuffer = require './BrailleBuffer'
|
||||
|
||||
# bresenham: (from, to) ->
|
||||
# points = []
|
||||
# adx = Math.abs dx = to.x - from.x
|
||||
# ady = Math.abs dy = to.y - from.y
|
||||
# eps = 0
|
||||
# sx = if dx > 0 then 1 else -1
|
||||
# sy = if dy > 0 then 1 else -1
|
||||
#
|
||||
# [x, y] = from
|
||||
# if adx > ady
|
||||
# while if sx < 0 then x >= x1 else x <= x1
|
||||
# points.add x:, y: y
|
||||
# eps += ady
|
||||
# if eps<<1 >= adx
|
||||
# y += sy
|
||||
# eps -= adx
|
||||
#
|
||||
# x += sx
|
||||
# else
|
||||
# while if sy < 0 then y >= y1 else y <= y1
|
||||
# fn(x, y);
|
||||
# eps += adx;
|
||||
# if eps<<1 >= ady
|
||||
# x += sx;
|
||||
# eps -= ady;
|
||||
#
|
||||
# y += sy
|
||||
# arr
|
||||
vec2 = glMatrix.vec2
|
||||
mat2d = glMatrix.mat2d
|
||||
|
||||
module.exports = class Canvas
|
||||
matrix: null
|
||||
|
||||
constructor: (@width, @height) ->
|
||||
@buffer = new BrailleBuffer @width, @height
|
||||
@reset()
|
||||
|
||||
reset: ->
|
||||
@matrix = mat2d.create()
|
||||
|
||||
print: ->
|
||||
process.stdout.write @buffer.frame()
|
||||
|
||||
translate: (x, y) ->
|
||||
mat2d.translate @matrix, @matrix, vec2.fromValues(x, y)
|
||||
|
||||
clear: ->
|
||||
@buffer.clear()
|
||||
|
||||
text: (text, x, y, color, center = true) ->
|
||||
@buffer.writeText text, x, y, color, center
|
||||
|
||||
polyline: (points, color) ->
|
||||
projected = (@_project point[0], point[1] for point in points)
|
||||
for i in [1...projected.length]
|
||||
bresenham projected[i-1]..., projected[i]...,
|
||||
(x, y) => @buffer.setPixel x, y, color
|
||||
|
||||
# TODO: support for polygon holes
|
||||
polygon: (points, color) ->
|
||||
vertices = []
|
||||
for point in points
|
||||
vertices = vertices.concat @_project point[0], point[1]
|
||||
|
||||
triangles = earcut vertices
|
||||
extract = (pointId) ->
|
||||
[vertices[pointId*2], vertices[pointId*2+1]]
|
||||
|
||||
for i in [0...triangles.length] by 3
|
||||
@_filledTriangle extract(triangles[i]), extract(triangles[i+1]), extract(triangles[i+2]), color
|
||||
|
||||
_bresenham: (pointA, pointB) ->
|
||||
bresenham pointA[0], pointA[1],
|
||||
pointB[0], pointB[1]
|
||||
|
||||
_project: (x, y) ->
|
||||
point = vec2.transformMat2d vec2.create(), vec2.fromValues(x, y), @matrix
|
||||
[Math.floor(point[0]), Math.floor(point[1])]
|
||||
|
||||
_filledRectangle: (x, y, width, height, color) ->
|
||||
pointA = @_project x, y
|
||||
pointB = @_project x+width, y
|
||||
pointC = @_project x, y+height
|
||||
pointD = @_project x+width, y+height
|
||||
|
||||
@_filledTriangle pointA, pointB, pointC, color
|
||||
@_filledTriangle pointC, pointB, pointD, color
|
||||
|
||||
# Draws a filled triangle
|
||||
_filledTriangle: (pointA, pointB, pointC, color) ->
|
||||
a = @_bresenham pointB, pointC
|
||||
b = @_bresenham pointA, pointC
|
||||
c = @_bresenham pointA, pointB
|
||||
|
||||
# Filter out any points outside of the visible area
|
||||
# TODO: benchmark - is it more effective to filter double points, or does
|
||||
# it req more computing time than actually setting points multiple times?
|
||||
|
||||
last = null
|
||||
points = a.concat(b).concat(c)
|
||||
.filter (point) => 0 <= point.y < @height
|
||||
.sort (a, b) -> if a.y is b.y then a.x - b.x else a.y-b.y
|
||||
.filter (point) ->
|
||||
[lastPoint, last] = [last, point]
|
||||
not lastPoint or lastPoint.x isnt point.x or lastPoint.y isnt point.y
|
||||
|
||||
for i, point of points
|
||||
next = points[i*1+1]
|
||||
|
||||
if point.y is next?.y
|
||||
left = Math.max 0, point.x
|
||||
right = Math.min @width, next?.x
|
||||
@buffer.setPixel x, point.y, color for x in [left..right]
|
||||
|
||||
else
|
||||
@buffer.setPixel point.x, point.y, color
|
||||
|
@ -5,7 +5,6 @@
|
||||
The Console Vector Tile renderer - bäm!
|
||||
###
|
||||
x256 = require 'x256'
|
||||
triangulator = new (require('pnltri')).Triangulator()
|
||||
|
||||
Canvas = require './Canvas'
|
||||
LabelBuffer = require './LabelBuffer'
|
||||
@ -17,7 +16,8 @@ module.exports = class Renderer
|
||||
fillPolygons: true
|
||||
language: 'de'
|
||||
|
||||
drawOrder: ["admin", "building", "road", "water", "poi_label", "place_label", "housenum_label"]
|
||||
#"poi_label", "housenum_label", "water",
|
||||
drawOrder: ["admin", "building", "road", "place_label"]
|
||||
|
||||
icons:
|
||||
car: "🚗"
|
||||
@ -77,14 +77,13 @@ module.exports = class Renderer
|
||||
# @canvas.strokeStyle = x256 utils.hex2rgb(color)...
|
||||
# @canvas.fillRect 0, 0, @width, @height
|
||||
# else
|
||||
@canvas.clearRect 0, 0, @width, @height
|
||||
@canvas.clear()
|
||||
@canvas.reset()
|
||||
|
||||
@canvas.save()
|
||||
@canvas.translate @view[0], @view[1]
|
||||
@_drawLayers()
|
||||
@canvas.restore()
|
||||
|
||||
@_write @canvas._canvas.frame()
|
||||
@canvas.print()
|
||||
|
||||
@isDrawing = false
|
||||
|
||||
@ -114,9 +113,7 @@ module.exports = class Renderer
|
||||
return false
|
||||
|
||||
toDraw = for points in feature.points
|
||||
for point in points
|
||||
x: point.x/scale
|
||||
y: point.y/scale
|
||||
[point.x/scale, point.y/scale] for point in points
|
||||
|
||||
color = style.paint['line-color'] or style.paint['fill-color'] or style.paint['text-color']
|
||||
|
||||
@ -124,16 +121,15 @@ module.exports = class Renderer
|
||||
if color instanceof Object
|
||||
color = color.stops[0][1]
|
||||
|
||||
@canvas.fillStyle = @canvas.strokeStyle = x256 utils.hex2rgb color
|
||||
colorCode = x256 utils.hex2rgb color
|
||||
|
||||
switch feature.type
|
||||
when "LineString"
|
||||
@_drawWithLines points for points in toDraw
|
||||
@canvas.polyline points, colorCode for points in toDraw
|
||||
true
|
||||
|
||||
when "Polygon"
|
||||
unless @config.fillPolygons and @_drawWithTriangles toDraw
|
||||
@_drawWithLines points for points in toDraw
|
||||
@canvas.polygon toDraw[0], colorCode
|
||||
true
|
||||
|
||||
when "Point"
|
||||
@ -150,37 +146,11 @@ module.exports = class Renderer
|
||||
x = point.x - text.length
|
||||
#continue if x-@view[0] < 0
|
||||
if @labelBuffer.writeIfPossible text, x, point.y
|
||||
@canvas.fillText text, x, point.y
|
||||
@canvas.text text, x, point.y, colorCode
|
||||
wasDrawn = true
|
||||
|
||||
wasDrawn
|
||||
|
||||
_drawWithTriangles: (points) ->
|
||||
try
|
||||
triangles = triangulator.triangulate_polygon points
|
||||
catch
|
||||
return false
|
||||
|
||||
return false unless triangles.length
|
||||
|
||||
# TODO: triangles are returned as vertex references to a flattened input.
|
||||
# optimize it!
|
||||
|
||||
arr = points.reduce (a, b) -> a.concat b
|
||||
for triangle in triangles
|
||||
try
|
||||
@canvas.fillTriangle arr[triangle[0]], arr[triangle[1]], arr[triangle[2]]
|
||||
catch
|
||||
return false
|
||||
true
|
||||
|
||||
_drawWithLines: (points) ->
|
||||
@canvas.beginPath()
|
||||
first = points.shift()
|
||||
@canvas.moveTo first.x, first.y
|
||||
@canvas.lineTo point.x, point.y for point in points
|
||||
@canvas.stroke()
|
||||
|
||||
_isOnScreen: (point) ->
|
||||
point.x+@view[0]>=4 and
|
||||
point.x+@view[0]<@width-4 and
|
||||
|
Loading…
Reference in New Issue
Block a user