/*
* Poly2Tri Copyright (c) 2009-2014, Poly2Tri Contributors
* http://code.google.com/p/poly2tri/
*
* poly2tri.js (JavaScript port) (c) 2009-2014, Poly2Tri Contributors
* https://github.com/r3mi/poly2tri.js
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Poly2Tri nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* jshint browser:true, jquery:true, globalstrict:true */
/* global poly2tri, Kinetic */
"use strict";
if (typeof $ === 'undefined') {
window.alert("jQuery not found -- dependencies not installed ?");
throw new Error("jQuery not loaded -- bower dependencies not installed ?");
}
// Styles
var TRIANGLE_FILL_COLOR = "#e0c4ef";
var TRIANGLE_STROKE_COLOR = "#911ccd";
var TRIANGLE_STROKE_WIDTH = 1;
var CONSTRAINT_COLOR = "rgba(0,0,0,0.6)";
var CONSTRAINT_DASH_ARRAY = [10, 5];
var CONSTRAINT_STROKE_WIDTH = 4;
var ERROR_COLOR = "rgba(255,0,0,0.8)";
var ERROR_RADIUS = 4;
var CANVAS_MARGIN = 5;
function clearData() {
$(".info").css('visibility', 'hidden');
$("textarea").val("");
$("#attribution").empty();
}
function setVisibleLayers(stage) {
var visible = $("#draw_constraints").is(':checked');
stage.find('.constraints').each(function(layer) {
layer.setVisible(visible);
});
}
function parsePoints(str) {
var floats = str.split(/[^-+eE\.\d]+/).map(parseFloat).filter(function(val) {
return !isNaN(val);
});
var i, points = [];
// bitwise 'and' to ignore any isolated float at the end
/* jshint bitwise:false */
for (i = 0; i < (floats.length & 0x7FFFFFFE); i += 2) {
points.push(new poly2tri.Point(floats[i], floats[i + 1]));
}
return points;
}
// XXX why is it needed ? normally KineticJS should accept our {x,y} objects,
// but it doesn't work in practice.
function makeKineticPoints(points) {
return points.map(function(point) {
return [point.x, point.y];
});
}
// Helper to override Kinetic.Shape.draw in order to have fixed width strokes
// or radius, independently of scale
// (strokeScaleEnabled = false doesn't give the expected result)
function provideFixedLineWidth(shape, setLineWidth) {
var originalDrawFunc = shape.getDrawFunc();
shape.setDrawFunc(function() {
var linescale = 1 / this.getStage().getScaleX();
setLineWidth.call(this, linescale);
originalDrawFunc.apply(this, arguments);
});
}
function drawTriangles(stage, triangles) {
var layer = new Kinetic.Layer({name: "triangles"});
triangles.forEach(function(t) {
var triangle = new Kinetic.Polygon({
points: makeKineticPoints(t.getPoints()),
fill: TRIANGLE_FILL_COLOR,
stroke: TRIANGLE_STROKE_COLOR
});
provideFixedLineWidth(triangle, function(linescale) {
this.setStrokeWidth(TRIANGLE_STROKE_WIDTH * linescale);
});
layer.add(triangle);
});
stage.add(layer);
}
function drawConstraints(stage, contour, holes, points) {
var layer = new Kinetic.Layer({name: "constraints"});
var polygon = new Kinetic.Polygon({
points: makeKineticPoints(contour),
stroke: CONSTRAINT_COLOR,
dashArrayEnabled: true
});
provideFixedLineWidth(polygon, function(linescale) {
this.setStrokeWidth(CONSTRAINT_STROKE_WIDTH * linescale);
var dashArray = CONSTRAINT_DASH_ARRAY.map(function(dash) {
return dash * linescale;
});
this.setDashArray(dashArray);
});
layer.add(polygon);
holes.forEach(function(hole) {
var polygon = new Kinetic.Polygon({
points: makeKineticPoints(hole),
stroke: CONSTRAINT_COLOR,
dashArrayEnabled: true
});
provideFixedLineWidth(polygon, function(linescale) {
this.setStrokeWidth(CONSTRAINT_STROKE_WIDTH * linescale);
var dashArray = CONSTRAINT_DASH_ARRAY.map(function(dash) {
return dash * linescale;
});
this.setDashArray(dashArray);
});
layer.add(polygon);
});
points.forEach(function(point) {
var circle = new Kinetic.Circle({
x: point.x,
y: point.y,
fill: CONSTRAINT_COLOR
});
provideFixedLineWidth(circle, function(linescale) {
this.setRadius(CONSTRAINT_STROKE_WIDTH * linescale);
});
layer.add(circle);
});
stage.add(layer);
}
function drawErrors(stage, error_points) {
var layer = new Kinetic.Layer({name: "errors"});
error_points.forEach(function(point) {
var circle = new Kinetic.Circle({
x: point.x,
y: point.y,
fill: ERROR_COLOR
});
provideFixedLineWidth(circle, function(linescale) {
this.setRadius(ERROR_RADIUS * linescale);
});
layer.add(circle);
});
stage.add(layer);
}
function triangulate(stage) {
// clear the canvas
stage.destroyChildren();
// reset drag
stage.setAbsolutePosition(0, 0);
$(".info").css('visibility', 'visible');
// parse contour
var contour = parsePoints($("textarea#poly_contour").val());
$("#contour_size").text(contour.length);
// parse holes
var holes = [];
$("textarea#poly_holes").val().split(/\n\s*\n/).forEach(function(val) {
var hole = parsePoints(val);
if (hole.length > 0) {
holes.push(hole);
}
});
$("#holes_size").text(holes.length);
// parse points
var points = parsePoints($("textarea#poly_points").val());
$("#points_size").text(points.length);
// perform triangulation
var swctx;
var error_points;
try {
// prepare SweepContext
swctx = new poly2tri.SweepContext(contour, {cloneArrays: true});
swctx.addHoles(holes).addPoints(points);
// triangulate
swctx.triangulate();
} catch (e) {
window.alert(e);
error_points = e.points;
}
var triangles = swctx.getTriangles() || [];
$("#triangles_size").text(triangles.length);
// auto scale / translate
var bounds = swctx.getBoundingBox();
var xscale = (stage.getWidth() - 2 * CANVAS_MARGIN) / (bounds.max.x - bounds.min.x);
var yscale = (stage.getHeight() - 2 * CANVAS_MARGIN) / (bounds.max.y - bounds.min.y);
var scale = Math.min(xscale, yscale);
// CANVAS_MARGIN is fixed and needs to be unscaled
stage.setOffset(bounds.min.x - CANVAS_MARGIN / scale, bounds.min.y - CANVAS_MARGIN / scale);
stage.setScale(scale);
var base = new Kinetic.Layer({name: "base"});
stage.add(base);
// draw result
drawTriangles(stage, triangles);
drawConstraints(stage, contour, holes, points);
if (error_points) {
drawErrors(stage, error_points);
}
stage.draw();
setVisibleLayers(stage);
}
// Adapted from "Zoom to point and scale (kineticjs+mousewheel)"
// http://nightlycoding.com/index.php/2013/08/zoom-to-point-and-scale-kineticjsmousewheel/
function onMouseWheel(e, delta) {
//prevent the actual wheel movement
e.preventDefault();
var stage = e.data;
var scale = stage.getScaleX(); // scaleX === scaleY in this app
// Change scale by +/- 10%
// ("delta" has been normalized at +/-1 by the jquery-mousewheel plugin).
var new_scale = scale * (1 + delta * 0.1);
if (new_scale > 0.0) {
var pointer = stage.getPointerPosition();
var stage_pos = stage.getAbsolutePosition();
var x = pointer.x - (pointer.x - stage_pos.x) / scale * new_scale;
var y = pointer.y - (pointer.y - stage_pos.y) / scale * new_scale;
stage.setPosition(x, y);
stage.setScale(new_scale);
stage.draw();
}
}
// Display pointer coordinates
function onMouseMove(e) {
var stage = e.data;
var pointer = stage.getPointerPosition();
var stage_pos = stage.getAbsolutePosition();
var x = (pointer.x - stage_pos.x) / stage.getScaleX() + stage.getOffsetX();
var y = (pointer.y - stage_pos.y) / stage.getScaleY() + stage.getOffsetY();
var digits = Math.min(stage.getScaleX() / 10, 5);
$('#pointer_x').text(x.toFixed(digits));
$('#pointer_y').text(y.toFixed(digits));
}
// Load index.json and populate 'preset' menu.
// By default, only show the entries with 'demo'=true.
// Use ?all=1 to force showing all entries.
function loadPresetMenu() {
var $menu = $("#preset");
var all = +($.url().param('all'));
$menu.empty().append($('