2013-12-05 11:14:12 +01:00
|
|
|
function drawCircle(x0,y0,r,res) {
|
|
|
|
if (res==undefined) res = 50; //circle resolution
|
|
|
|
beginShape();
|
2014-01-09 17:05:03 +01:00
|
|
|
var step = Math.PI * 2.0 / res;
|
|
|
|
for (var a=0; a<Math.PI*2; a+=step) {
|
|
|
|
var x = Math.sin(a+Math.PI) * r + x0;
|
|
|
|
var y = Math.cos(a+Math.PI) * r + y0;
|
2013-12-05 11:14:12 +01:00
|
|
|
if (a==0) shapeMoveTo(x,y);
|
|
|
|
else shapeLineTo(x,y);
|
|
|
|
}
|
2014-01-09 17:05:03 +01:00
|
|
|
|
|
|
|
//close shape
|
|
|
|
var x = Math.sin(0+Math.PI) * r + x0;
|
|
|
|
var y = Math.cos(0+Math.PI) * r + y0;
|
|
|
|
shapeLineTo(x,y);
|
|
|
|
|
2013-12-05 11:14:12 +01:00
|
|
|
endShape();
|
|
|
|
}
|
|
|
|
|
|
|
|
function beginShape(x,y) {
|
|
|
|
setSketchModified(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
function shapeMoveTo(x,y) {
|
|
|
|
_points.push([x, y, true]);
|
2014-01-17 00:21:29 +01:00
|
|
|
adjustBounds(x, y);
|
2013-12-05 11:14:12 +01:00
|
|
|
adjustPreviewTransformation();
|
|
|
|
draw(x, y, .5);
|
|
|
|
}
|
|
|
|
|
|
|
|
function shapeLineTo(x,y) {
|
|
|
|
_points.push([x, y, false]);
|
2014-01-17 00:21:29 +01:00
|
|
|
adjustBounds(x, y);
|
2013-12-05 11:14:12 +01:00
|
|
|
adjustPreviewTransformation();
|
|
|
|
draw(x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
function endShape() {
|
|
|
|
renderToImageDataPreview();
|
2014-01-09 17:05:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function getBounds(points) {
|
2014-02-02 02:43:33 +01:00
|
|
|
var xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity;
|
2014-01-09 17:05:03 +01:00
|
|
|
for (var i=0; i<points.length; i++) {
|
|
|
|
var p = points[i];
|
|
|
|
xMin = Math.min(xMin,p[0]);
|
|
|
|
xMax = Math.max(xMax,p[0]);
|
|
|
|
yMin = Math.min(yMin,p[1]);
|
|
|
|
yMax = Math.max(yMax,p[1]);
|
|
|
|
}
|
|
|
|
return {x:xMin,y:yMin,width:xMax-xMin,height:yMax-yMin};
|
|
|
|
}
|
|
|
|
|
|
|
|
function translatePoints(points,x,y) {
|
|
|
|
for (var i=0; i<points.length; i++) {
|
|
|
|
points[i][0] += x;
|
|
|
|
points[i][1] += y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function scalePoints(points,x,y) {
|
|
|
|
if (y==undefined) y = x;
|
|
|
|
for (var i=0; i<points.length; i++) {
|
|
|
|
points[i][0] *= x;
|
|
|
|
points[i][1] *= y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function rotatePoints(points, radians, cx, cy) {
|
|
|
|
if (cx==undefined) cx = 0;
|
|
|
|
if (cy==undefined) cy = 0;
|
|
|
|
|
|
|
|
var cos = Math.cos(radians);
|
|
|
|
var sin = Math.sin(radians);
|
|
|
|
|
|
|
|
for (var i=0; i<points.length; i++) {
|
|
|
|
var x = points[i][0];
|
|
|
|
var y = points[i][1];
|
|
|
|
var nx = (cos * (x - cx)) - (sin * (y - cy)) + cx;
|
|
|
|
var ny = (sin * (x - cx)) + (cos * (y - cy)) + cy;
|
|
|
|
points[i][0] = nx;
|
|
|
|
points[i][1] = ny;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function moveShape(x,y) {
|
2014-01-17 00:28:57 +01:00
|
|
|
var bounds = getBounds(_points);
|
2014-01-17 12:37:54 +01:00
|
|
|
var delta = reduceTransformToFit(x, y, 1.0, bounds);
|
2014-01-17 00:28:57 +01:00
|
|
|
|
|
|
|
if (delta.x != 0 || delta.y != 0) {
|
|
|
|
translatePoints(_points, delta.x, delta.y);
|
|
|
|
updateView();
|
|
|
|
}
|
2014-01-09 17:05:03 +01:00
|
|
|
}
|
|
|
|
|
2014-01-17 00:28:57 +01:00
|
|
|
//TODO: reduction of zoomValue is still not completely correct (but acceptable?)
|
2014-01-17 12:37:54 +01:00
|
|
|
//TODO: bounds should be cached and marked dirty on modification of points array; translations could be combined in several places
|
2014-01-09 17:05:03 +01:00
|
|
|
function zoomShape(zoomValue) {
|
2014-01-17 00:28:57 +01:00
|
|
|
var bounds = getBounds(_points);
|
2014-01-17 12:37:54 +01:00
|
|
|
var transform = reduceTransformToFit(0, 0, zoomValue, bounds);
|
2014-01-17 00:28:57 +01:00
|
|
|
|
2014-01-17 12:37:54 +01:00
|
|
|
translatePoints(_points, transform.x, transform.y); //move points towards center as far as necessary to avoid clipping
|
|
|
|
translatePoints(_points, -bounds.x, -bounds.y);
|
|
|
|
translatePoints(_points, -bounds.width / 2, -bounds.height / 2);
|
|
|
|
scalePoints(_points, transform.zf, transform.zf);
|
|
|
|
translatePoints(_points, bounds.width / 2, bounds.height / 2);
|
|
|
|
translatePoints(_points, bounds.x, bounds.y);
|
2014-01-17 00:28:57 +01:00
|
|
|
updateView();
|
2014-01-09 17:05:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function rotateShape(radians) {
|
|
|
|
var bounds = getBounds(_points);
|
|
|
|
var cx = bounds.x + bounds.width/2;
|
|
|
|
var cy = bounds.y + bounds.height/2;
|
|
|
|
rotatePoints(_points, radians, cx, cy);
|
2014-01-17 12:37:54 +01:00
|
|
|
|
|
|
|
var bounds = getBounds(_points);
|
|
|
|
var transform = reduceTransformToFit(0, 0, 1.0, bounds);
|
|
|
|
translatePoints(_points, transform.x, transform.y);
|
|
|
|
scalePoints(_points, transform.zf, transform.zf);
|
|
|
|
|
2014-01-09 17:05:03 +01:00
|
|
|
updateView();
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateView() {
|
|
|
|
setSketchModified(true);
|
2014-01-17 16:28:48 +01:00
|
|
|
redrawDoodle(true);
|
2014-01-09 17:05:03 +01:00
|
|
|
adjustPreviewTransformation();
|
|
|
|
renderToImageDataPreview();
|
2014-01-17 00:28:57 +01:00
|
|
|
|
|
|
|
if (debugMode) {
|
|
|
|
var bounds = getBounds(_points);
|
|
|
|
drawCircleTemp(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2, 5, 'red');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-17 12:37:54 +01:00
|
|
|
//when x,y!=0,0: reduces them such that transformed bounds will still fit on canvas (given that they fit prior to the transform)
|
|
|
|
//otherwise: calculate translation + zoom reduce such that given bounds will fit on canvas after transformation
|
|
|
|
function reduceTransformToFit(x, y, zf, bounds) {
|
|
|
|
var zw = bounds.width * zf; zh = bounds.height * zf;
|
|
|
|
var newBounds = { x: bounds.x - (zw - bounds.width) / 2, y: bounds.y - (zh - bounds.height) / 2, width: zw, height: zh };
|
|
|
|
// console.log("bounds: " + bounds.x + ", " + bounds.y + ", " + bounds.width + ", " + bounds.height);
|
|
|
|
// console.log("newBounds: " + newBounds.x + ", " + newBounds.y + ", " + newBounds.width + ", " + newBounds.height);
|
|
|
|
|
|
|
|
var ldx = Math.max(x, -newBounds.x);
|
|
|
|
var rdx = Math.min(x, canvasWidth - (newBounds.x + newBounds.width));
|
|
|
|
var tdy = Math.max(y, -newBounds.y);
|
|
|
|
var bdy = Math.min(y, canvasHeight - (newBounds.y + newBounds.height));
|
|
|
|
|
|
|
|
if (x != 0 || y != 0) { //movement was requested
|
|
|
|
return { x: nearestZero(ldx, rdx), y: nearestZero(tdy, bdy) };
|
|
|
|
} else { //no movement requested
|
|
|
|
var delta = { x: ldx + rdx, y: tdy + bdy };
|
|
|
|
if (ldx != 0 && rdx != 0) delta.x /= 2;
|
|
|
|
if (tdy != 0 && bdy != 0) delta.y /= 2;
|
|
|
|
|
|
|
|
delta.x /= zf;
|
|
|
|
delta.y /= zf;
|
|
|
|
|
|
|
|
var zxMax = Math.min(zf, canvasWidth / newBounds.width);
|
|
|
|
var zyMax = Math.min(zf, canvasHeight / newBounds.height);
|
2014-01-17 18:03:42 +01:00
|
|
|
// var oldZF = zf;
|
2014-01-17 12:37:54 +01:00
|
|
|
// var dir = zf >= 1.0 ? 1 : 0;
|
|
|
|
zf = Math.min(zxMax, zyMax);
|
|
|
|
// if (dir == 1 && zf < 1.0) zf = 1;
|
2014-01-17 18:03:42 +01:00
|
|
|
// console.log("orgZF, zxMax, zyMax, finZF: " + oldZF + ", " + zxMax + ", " + zyMax + ", " + zf);
|
2014-01-17 12:37:54 +01:00
|
|
|
|
|
|
|
return { x: delta.x, y: delta.y, zf: zf };
|
|
|
|
}
|
2014-01-17 00:28:57 +01:00
|
|
|
}
|
|
|
|
|
2014-01-17 12:37:54 +01:00
|
|
|
function nearestZero(v1, v2) { return Math.abs(v1) < Math.abs(v2) ? v1 : v2; }
|
|
|
|
|
2014-01-17 00:28:57 +01:00
|
|
|
//*draws* a circle (i.e. it is not added as points to shape)
|
|
|
|
function drawCircleTemp(x, y, r, color) {
|
|
|
|
ctx.beginPath();
|
|
|
|
ctx.lineWidth = 1;
|
|
|
|
ctx.fillStyle = color;
|
|
|
|
ctx.arc(x, y, r, 0, 2 * Math.PI, false);
|
|
|
|
ctx.fill();
|
|
|
|
ctx.stroke();
|
|
|
|
ctx.fillStyle = 'black';
|
|
|
|
}
|