📐 using earcut for triangulation, implementing own Canvas

This commit is contained in:
Michael Straßburger 2016-09-27 13:57:59 +02:00
parent a3fb7149f2
commit 06528f488d
5 changed files with 117 additions and 86 deletions

View File

@ -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

View File

@ -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",

View File

@ -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()

View File

@ -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

View File

@ -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