// A Polyline is a set of points inputted by the user using the V2 editor to trace a blueprint. // Additionally, it manages a set of MX objects which correspond to the walls in 3D. // In this way, it attempts to bridge the 2D (canvas, imperative) and 3D (css, declarative) views. var Polyline = Fiber.extend(function(base){ var exports = {} exports.init = function(){ this.points = [] this.mx_points = [] this.closed = false } exports.add = function(p){ this.points.push( p ) this.mx_points.push( new MX.Point(p) ) } exports.firstPoint = function(){ return this.points[0] } exports.lastPoint = function(){ return this.points[this.points.length-1] } exports.canCloseWith = function(p){ return (this.points.length > 2 && this.points[0].distanceTo( p ) < 10/map.zoom) } exports.getHeadAtIndex = function(index){ if (index == 0) { return null } if (index == this.points.length-1) { return this.clone() } var head = new Polyline() head.points = this.points.slice(0, index+1) return head } exports.getTailAtIndex = function(index){ if (index == this.points.length-1) { return null } if (index == 0) { return this.clone() } var tail = new Polyline() tail.points = this.points.slice(index, this.points.length) return tail } exports.clone = function(){ var clone = new Polyline() clone.points = this.points.concat() } exports.hasPointNear = function(p){ var point for (var i = 0; i < this.points.length; i++){ point = this.points[i] if (point.distanceTo( p ) < 10/map.zoom) { return point } } return null } exports.hasEndPointNear = function(p){ if (this.closed || ! this.points.length) return null if (this.firstPoint().distanceTo( p ) < 10/map.zoom) { return this.firstPoint() } if (this.lastPoint().distanceTo( p ) < 10/map.zoom) { return this.lastPoint() } return null } exports.hasSegmentNear = function(p, min_dist){ var p1, p2, d1, d2, sum, rat var dx, dy, new_x, new_y, x, y, closest_distance = min_dist || Infinity var closest_i = -1 var points = this.points var p1, p2 = points[0] for (var i = 1; i < points.length; i++) { p1 = p2 p2 = points[i] d1 = p2.a - p1.a d2 = p2.b - p1.b sum = d1*d1 + d2*d2 rat = ((p.a - p1.a) * d1 + (p.b - p1.b) * d2) / sum rat = rat < 0 ? 0 : rat < 1 ? rat : 1 new_x = p1.a + rat * d1 new_y = p1.b + rat * d2 dx = new_x - p.a dy = new_y - p.b sum2 = sqrt(dx*dx+dy*dy) if (sum2 < closest_distance) { x = new_x y = new_y closest_distance = sum2 closest_i = i } } if (closest_i == -1) return null return { x: x, y: y, distance: closest_distance, head: closest_i-1, tail: closest_i, } } exports.draw = function(ctx){ var points = this.points if (! points.length) return if (points.length == 1) { ctx.fillStyle = "#f80" map.draw.dot_at(this.points[0].a, points[0].b, 5) } if (points.length > 1) { ctx.fillStyle = "rgba(255,255,0,0.1)" ctx.strokeStyle = "#f80" ctx.lineWidth = 2 / map.zoom ctx.beginPath() ctx.moveTo(points[0].a, points[0].b) points.forEach(function(point, i){ i && ctx.lineTo(point.a, point.b) }) ctx.stroke() if (! map.ui.placing || this.closed) { ctx.fill() } } } exports.draw_line = function (ctx, p){ var last = this.points[this.points.length-1] ctx.strokeStyle = "#f80" ctx.lineWidth = 2 / map.zoom ctx.beginPath() ctx.moveTo(last.a, last.b) ctx.lineTo(p.a, p.b) ctx.stroke() } exports.close = function(){ this.points[this.points.length] = this.points[0] this.closed = true } exports.build = function(){ this.mx_points && this.mx_points.forEach(function(mx){ scene.remove(mx) }) this.mx = new MX.Polyline(this) } exports.rebuild = function(){ this.mx.rebuild() } exports.getSegments = function(){ if (this.points.length == 1) { return [] } var segments = [] for (var i = 1; i < this.points.length; i++) { segments.push( [ this.points[i-1], this.points[i] ] ) } return segments } exports.serialize = function(){ return this.points.map(function(point){ return [point.a, point.b] }) } exports.deserialize = function(points){ this.points = points.map(function(point){ return new vec2(point[0], point[1]) }) } exports.reset = function(){ this.mx_points.forEach(function(mx){ scene.remove(mx) }) this.mx_points.length = 0 this.points.length = 0 } exports.destroy = function(){ this.reset() this.mx && this.mx.destroy() } return exports })