UNPKG

node-red-contrib-easybotics-led-matrix

Version:

control led matrix with node-red on a raspberryPI, node-red binding of: https://www.npmjs.com/package/easybotics-rpi-rgb-led-matrix

381 lines (296 loc) 8.69 kB
var exports = module.exports = {} //some geo primitives we'll use everywhere exports.Color = function () { this.r this.g this.b this.fromHexString = function (h) { function eatHexString (hex) { // Expand shorthand form (e.g. '03F') to full form (e.g. "0033FF") const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i hex = hex.replace(shorthandRegex, function(m, r, g, b) { return r + r + g + g + b + b }) const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16)} : null } const e = eatHexString(h) this.r = e.r this.g = e.g this.b = e.b } this.toHex = function () { function componentToHex(c) { var hex = c.toString(16) return hex.length == 1 ? '0' + hex : hex } function rgbToHex(r, g, b) { return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b) } return rgbToHex(this.r, this.g, this.b) } this.fromRgbString = function (str) { const s = str.split(',') const output = {r: parseInt(s[0]), g: parseInt(s[1]), b: parseInt(s[2])} this.r = output.r this.g = output.g this.b = output.b return this } this.toRgbString = function () { return this.r + ',' + this.g + ',' + this.b } this.fromRgb = function (r, g, b) { this.r = r this.g = g this.b = b return this } } exports.Point = function (x, y) { this.x = parseInt(x) this.y = parseInt(y) //returns the distance to another point this.distance = function (p) { return Math.sqrt( Math.pow( p.x - this.x) + Math.pow( p.y - this.y)) } //returns the midpoint between this point and another point this.midpoint = function (p) { return exports.Points((this.x + p.x) / 2, (this.y + p.y) / 2) } //draws on an led matrix we give it this.draw = function (l, color, offset) { const xOff = parseInt(offset != undefined ? offset.x : 0) const yOff = parseInt(offset != undefined ? offset.y : 0) l.setPixel(parseInt(this.x) + xOff, parseInt(this.y) + yOff, parseInt(color.r), parseInt(color.g), parseInt(color.b)) } } //takes two points, and returns a line between them exports.Line = function (start, end) { this.start = start this.end = end this.yMax = function () { return start.y > end.y ? start.y : end.y } this.yMin = function () { return start.y < end.y ? start.y : end.y } this.intersects = function (line) { function onSegment (p, q, r) { if (q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y)) { return true } return false } function orientation (p, q, r) { const val = (q.y - p.y) * (r.x - q.x) -(q.x - p.x) * (r.y - q.y) if (val == 0) return 0 // colinear return (val > 0)? 1: 2 // clock or counterclock wise } const p1 = this.start const q1 = this.end const p2 = line.start const q2 = line.end const o1 = orientation(p1, q1, p2) const o2 = orientation(p1, q1, q2) const o3 = orientation(p2, q2, p1) const o4 = orientation(p2, q2, q1) if (o1 != o2 && o3 != o4) return true // Special Cases // p1, q1 and p2 are colinear and p2 lies on segment p1q1 if (o1 == 0 && onSegment(p1, p2, q1)) return true // p1, q1 and q2 are colinear and q2 lies on segment p1q1 if (o2 == 0 && onSegment(p1, q2, q1)) return true // p2, q2 and p1 are colinear and p1 lies on segment p2q2 if (o3 == 0 && onSegment(p2, p1, q2)) return true // p2, q2 and q1 are colinear and q1 lies on segment p2q2 if (o4 == 0 && onSegment(p2, q1, q2)) return true return false // Doesn't fall in any of the above cases } // line intercept math by Paul Bourke http://paulbourke.net/geometry/pointlineplane/ // Determine the intersection point of two line segments // Return FALSE if the lines don't intersect this.intersection = function (line) { const x1 = this.start.x const y1 = this.start.y const x2 = this.end.x const y2 = this.end.y const x3 = line.start.x const y3 = line.start.y const x4 = line.end.x const y4 = line.end.y // Check if none of the lines are of length 0 if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) { return false } const denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)) // Lines are parallel if (denominator === 0) { return false } let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator // is the intersection along the segments if (ua < 0 || ua > 1 || ub < 0 || ub > 1) { return false } // Return a object with the x and y coordinates of the intersection let x = x1 + ua * (x2 - x1) let y = y1 + ua * (y2 - y1) return new exports.Point(x, y) } //draw on an led matrix we give it this.draw = function (l, color, offset) { const xOff = parseInt(offset != undefined ? offset.x : 0) const yOff = parseInt(offset != undefined ? offset.y : 0) l.drawLine(this.start.x + xOff, this.start.y + yOff, this.end.x + xOff, this.end.y + yOff, parseInt(color.r), parseInt(color.g), parseInt(color.b)) } } exports.Polygon = function (p) { this.points = p this.drawLineCache this.drawFillCache = [] this.boundryIntersections = function (l) { var num = 0 var lowestX = undefined var highestX = undefined const height = l.start.y for (const c of this.getLines()) { if(height == c.yMax()) continue const interSect = l.intersection(c) //increment number of intersects if(interSect) { num++ //update lowest and highest X found if(interSect.x > highestX || highestX == undefined) highestX = interSect.x if(interSect.y < lowestX || lowestX == undefined) lowestX = interSect.x } } return {num: num, lowestX: lowestX, highestX: highestX} } this.clipBounds = function () { var tx = this.points[0].x var ty = this.points[0].y var bx = this.points[0].x var by = this.points[0].y for( const p of this.points) { tx = p.x < tx ? p.x : tx ty = p.y < ty ? p.y : ty bx = p.x > bx ? p.x : bx by = p.y > by ? p.y : by } return {topLeft: new exports.Point(tx, ty), bottomRight: new exports.Point(bx, by)} } this.getLines = function () { const lines = [] const first = this.points[0] var last = undefined for(const p of this.points) { if (last) lines.push(new exports.Line(last, p)) last = p } lines.push(new exports.Line(last, first)) return lines } //moved this to a pure function so its fine to call async //pure function just means it doesn't modify any variables outside its scope //returns a buffer instead of editing the classes one this.pureFill = function() { var dfCache = [] const bounds = this.clipBounds() for(var y = bounds.topLeft.y; y < bounds.bottomRight.y; y++) { for(var x = bounds.topLeft.x; x < bounds.bottomRight.x; x++) { const leftTest = new exports.Line( new exports.Point(bounds.topLeft.x, y), new exports.Point(x, y)) const rightTest = new exports.Line( new exports.Point(x, y), new exports.Point(bounds.bottomRight.x, y)) const left = this.boundryIntersections(leftTest) const right = this.boundryIntersections(rightTest) if ((left.num % 2) && (right.num % 2) ) { //draw a line from the current position to the next line intersection dfCache.push( new exports.Line( new exports.Point(left.highestX, y), new exports.Point( right.lowestX, y))) //skip to the next line intersection, because we just filled in up to there anyway x = right.lowestX } } } return dfCache } //wrap our pure function in a promise //promises run asynchronously, and have a .then() method which defines //-- what happens when the result is returned this.promiseWrapperFill = function() { //tricky error here, which is that 'this' is not captured in these lambdas const n = this //so we just make a n. thing to use as this //probably should have assigned a const to this at the global scope, which ill probably do return new Promise(function(resolve) { const buffer = n.pureFill() resolve(buffer) }) } this.fill = function ( callback) { //remmeber that 'this' isn't captured const n = this //.then() notation this.promiseWrapperFill().then(function(result) { n.drawFillCache = result if(callback) callback() //callback(); }) } this.draw = function (l, color, offset) { this.drawLinesCache = this.getLines() for(const c of this.drawLinesCache) { c.draw(l, color, offset) } for(const p of this.drawFillCache) { p.draw(l, color, offset) } } }