Skip to content

Commit

Permalink
Limit sin and cosin very small floats on 90/270 degrees (#4734)
Browse files Browse the repository at this point in the history
* so far ok

* fix transformations decimal

* remove some changes

* less changes

* test passing

* less calculation

* added shortcut

* modified tests
  • Loading branch information
asturur authored Feb 18, 2018
1 parent 687cd7b commit 2e532cc
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 73 deletions.
2 changes: 1 addition & 1 deletion src/filters/hue_rotation.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
mainParameter: 'rotation',

calculateMatrix: function() {
var rad = this.rotation * Math.PI, cos = Math.cos(rad), sin = Math.sin(rad),
var rad = this.rotation * Math.PI, cos = fabric.util.cos(rad), sin = fabric.util.sin(rad),
aThird = 1 / 3, aThirdSqtSin = Math.sqrt(aThird) * sin, OneMinusCos = 1 - cos;
this.matrix = [
1, 0, 0, 0, 0,
Expand Down
100 changes: 59 additions & 41 deletions src/mixins/object_geometry.mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
}

var degreesToRadians = fabric.util.degreesToRadians,
multiplyMatrices = fabric.util.multiplyTransformMatrices;
multiplyMatrices = fabric.util.multiplyTransformMatrices,
transformPoint = fabric.util.transformPoint;

fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {

Expand Down Expand Up @@ -355,45 +356,55 @@
* @chainable
*/
calcCoords: function(absolute) {
var theta = degreesToRadians(this.angle),
var rotateMatrix = this._calcRotateMatrix(),
translateMatrix = this._calcTranslateMatrix(),
startMatrix = multiplyMatrices(translateMatrix, rotateMatrix),
vpt = this.getViewportTransform(),
dim = absolute ? this._getTransformedDimensions() : this._calculateCurrentDimensions(),
currentWidth = dim.x, currentHeight = dim.y,
sinTh = theta ? Math.sin(theta) : 0,
cosTh = theta ? Math.cos(theta) : 1,
_angle = currentWidth > 0 ? Math.atan(currentHeight / currentWidth) : 0,
_hypotenuse = (currentWidth / Math.cos(_angle)) / 2,
offsetX = Math.cos(_angle + theta) * _hypotenuse,
offsetY = Math.sin(_angle + theta) * _hypotenuse,
center = this.getCenterPoint(),
// offset added for rotate and scale actions
coords = absolute ? center : fabric.util.transformPoint(center, vpt),
tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY),
tr = new fabric.Point(tl.x + (currentWidth * cosTh), tl.y + (currentWidth * sinTh)),
bl = new fabric.Point(tl.x - (currentHeight * sinTh), tl.y + (currentHeight * cosTh)),
br = new fabric.Point(coords.x + offsetX, coords.y + offsetY);
finalMatrix = absolute ? startMatrix : multiplyMatrices(vpt, startMatrix),
dim = this._getTransformedDimensions(),
w = dim.x / 2, h = dim.y / 2,
tl = transformPoint({ x: -w, y: -h }, finalMatrix),
tr = transformPoint({ x: w, y: -h }, finalMatrix),
bl = transformPoint({ x: -w, y: h }, finalMatrix),
br = transformPoint({ x: w, y: h }, finalMatrix);
if (!absolute) {
var padding = this.padding, angle = degreesToRadians(this.angle),
cos = fabric.util.cos(angle), sin = fabric.util.sin(angle),
cosP = cos * padding, sinP = sin * padding, cosPSinP = cosP + sinP,
cosPMinusSinP = cosP - sinP;
if (padding) {
tl.x -= cosPMinusSinP;
tl.y -= cosPSinP;
tr.x += cosPSinP;
tr.y -= cosPMinusSinP;
bl.x -= cosPSinP;
bl.y += cosPMinusSinP;
br.x += cosPMinusSinP;
br.y += cosPSinP;
}
var ml = new fabric.Point((tl.x + bl.x) / 2, (tl.y + bl.y) / 2),
mt = new fabric.Point((tr.x + tl.x) / 2, (tr.y + tl.y) / 2),
mr = new fabric.Point((br.x + tr.x) / 2, (br.y + tr.y) / 2),
mb = new fabric.Point((br.x + bl.x) / 2, (br.y + bl.y) / 2),
mtr = new fabric.Point(mt.x + sinTh * this.rotatingPointOffset, mt.y - cosTh * this.rotatingPointOffset);
mtr = new fabric.Point(mt.x + sin * this.rotatingPointOffset, mt.y - cos * this.rotatingPointOffset);
}

// debugging

/* setTimeout(function() {
canvas.contextTop.fillStyle = 'green';
canvas.contextTop.fillRect(mb.x, mb.y, 3, 3);
canvas.contextTop.fillRect(bl.x, bl.y, 3, 3);
canvas.contextTop.fillRect(br.x, br.y, 3, 3);
canvas.contextTop.fillRect(tl.x, tl.y, 3, 3);
canvas.contextTop.fillRect(tr.x, tr.y, 3, 3);
canvas.contextTop.fillRect(ml.x, ml.y, 3, 3);
canvas.contextTop.fillRect(mr.x, mr.y, 3, 3);
canvas.contextTop.fillRect(mt.x, mt.y, 3, 3);
canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3);
}, 50); */
// if (!absolute) {
// var canvas = this.canvas;
// setTimeout(function() {
// canvas.contextTop.clearRect(0, 0, 700, 700);
// canvas.contextTop.fillStyle = 'green';
// canvas.contextTop.fillRect(mb.x, mb.y, 3, 3);
// canvas.contextTop.fillRect(bl.x, bl.y, 3, 3);
// canvas.contextTop.fillRect(br.x, br.y, 3, 3);
// canvas.contextTop.fillRect(tl.x, tl.y, 3, 3);
// canvas.contextTop.fillRect(tr.x, tr.y, 3, 3);
// canvas.contextTop.fillRect(ml.x, ml.y, 3, 3);
// canvas.contextTop.fillRect(mr.x, mr.y, 3, 3);
// canvas.contextTop.fillRect(mt.x, mt.y, 3, 3);
// canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3);
// }, 50);
// }

var coords = {
// corners
Expand Down Expand Up @@ -437,16 +448,21 @@
*/
_calcRotateMatrix: function() {
if (this.angle) {
var theta = degreesToRadians(this.angle), cos = Math.cos(theta), sin = Math.sin(theta);
// trying to keep rounding error small, ugly but it works.
if (cos === 6.123233995736766e-17 || cos === -1.8369701987210297e-16) {
cos = 0;
}
var theta = degreesToRadians(this.angle), cos = fabric.util.cos(theta), sin = fabric.util.sin(theta);
return [cos, sin, -sin, cos, 0, 0];
}
return fabric.iMatrix.concat();
},

/**
* calculate the translation matrix for an object transform
* @return {Array} rotation matrix for the object
*/
_calcTranslateMatrix: function() {
var center = this.getCenterPoint();
return [1, 0, 0, 1, center.x, center.y];
},

transformMatrixKey: function(skipGroup) {
var sep = '_', prefix = '';
if (!skipGroup && this.group) {
Expand Down Expand Up @@ -485,8 +501,7 @@
if (cache.key === key) {
return cache.value;
}
var center = this.getCenterPoint(),
matrix = [1, 0, 0, 1, center.x, center.y],
var matrix = this._calcTranslateMatrix(),
rotateMatrix,
dimensionMatrix = this._calcDimensionsTransformMatrix(this.skewX, this.skewY, true);
if (this.angle) {
Expand Down Expand Up @@ -542,8 +557,11 @@
if (typeof skewY === 'undefined') {
skewY = this.skewY;
}
var dimensions = this._getNonTransformedDimensions(),
dimX = dimensions.x / 2, dimY = dimensions.y / 2,
var dimensions = this._getNonTransformedDimensions();
if (skewX === 0 && skewY === 0) {
return { x: dimensions.x * this.scaleX, y: dimensions.y * this.scaleY };
}
var dimX = dimensions.x / 2, dimY = dimensions.y / 2,
points = [
{
x: -dimX,
Expand Down
4 changes: 2 additions & 2 deletions src/mixins/object_interactivity.mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@
/* Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, */
/* 0.707106 stands for sqrt(2)/2 */
cornerHypotenuse = this.cornerSize * 0.707106,
cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
cosHalfOffset = cornerHypotenuse * fabric.util.cos(newTheta),
sinHalfOffset = cornerHypotenuse * fabric.util.sin(newTheta),
x, y;

for (var point in coords) {
Expand Down
4 changes: 2 additions & 2 deletions src/mixins/object_origin.mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@
adjustPosition: function(to) {
var angle = degreesToRadians(this.angle),
hypotFull = this.getScaledWidth(),
xFull = Math.cos(angle) * hypotFull,
yFull = Math.sin(angle) * hypotFull,
xFull = fabric.util.cos(angle) * hypotFull,
yFull = fabric.util.sin(angle) * hypotFull,
offsetFrom, offsetTo;

//TODO: this function does not consider mixed situation like top, center.
Expand Down
2 changes: 1 addition & 1 deletion src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
*/
fabric.parseTransformAttribute = (function() {
function rotateMatrix(matrix, args) {
var cos = Math.cos(args[0]), sin = Math.sin(args[0]),
var cos = fabric.util.cos(args[0]), sin = fabric.util.sin(args[0]),
x = 0, y = 0;
if (args.length === 3) {
x = args[1];
Expand Down
8 changes: 4 additions & 4 deletions src/shapes/circle.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@
);
}
else {
var startX = Math.cos(this.startAngle) * this.radius,
startY = Math.sin(this.startAngle) * this.radius,
endX = Math.cos(this.endAngle) * this.radius,
endY = Math.sin(this.endAngle) * this.radius,
var startX = fabric.util.cos(this.startAngle) * this.radius,
startY = fabric.util.sin(this.startAngle) * this.radius,
endX = fabric.util.cos(this.endAngle) * this.radius,
endY = fabric.util.sin(this.endAngle) * this.radius,
largeFlag = angle > pi ? '1' : '0';

markup.push(
Expand Down
12 changes: 6 additions & 6 deletions src/util/arc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
}

var PI = Math.PI, th = rotateX * PI / 180,
sinTh = Math.sin(th),
cosTh = Math.cos(th),
sinTh = fabric.util.sin(th),
cosTh = fabric.util.cos(th),
fromX = 0, fromY = 0;

rx = Math.abs(rx);
Expand Down Expand Up @@ -76,10 +76,10 @@
return segmentToBezierCache[argsString2];
}

var costh2 = Math.cos(th2),
sinth2 = Math.sin(th2),
costh3 = Math.cos(th3),
sinth3 = Math.sin(th3),
var costh2 = fabric.util.cos(th2),
sinth2 = fabric.util.sin(th2),
costh3 = fabric.util.cos(th3),
sinth3 = fabric.util.sin(th3),
toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,
toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,
cp1X = fromX + mT * ( -cosTh * rx * sinth2 - sinTh * ry * costh2),
Expand Down
50 changes: 47 additions & 3 deletions src/util/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,57 @@
atan2 = Math.atan2,
pow = Math.pow,
abs = Math.abs,
PiBy180 = Math.PI / 180;
PiBy180 = Math.PI / 180,
PiBy2 = Math.PI / 2;

/**
* @namespace fabric.util
*/
fabric.util = {

/**
* Calculate the cos of an angle, avoiding returning floats for known results
* @static
* @memberOf fabric.util
* @param {Number} angle the angle in radians or in degree
* @return {Number}
*/
cos: function(angle) {
if (angle === 0) { return 1; }
if (angle < 0) {
// cos(a) = cos(-a)
angle = -angle;
}
var angleSlice = angle / PiBy2;
switch (angleSlice) {
case 1: case 3: return 0;
case 2: return -1;
}
return Math.cos(angle);
},

/**
* Calculate the sin of an angle, avoiding returning floats for known results
* @static
* @memberOf fabric.util
* @param {Number} angle the angle in radians or in degree
* @return {Number}
*/
sin: function(angle) {
if (angle === 0) { return 0; }
var angleSlice = angle / PiBy2, sign = 1;
if (angle < 0) {
// sin(-a) = -sin(a)
sign = -1;
}
switch (angleSlice) {
case 1: return sign;
case 2: return 0;
case 3: return -sign;
}
return Math.sin(angle);
},

/**
* Removes value from an array.
* Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
Expand Down Expand Up @@ -86,8 +130,8 @@
* @return {Object} The new rotated point
*/
rotateVector: function(vector, radians) {
var sin = Math.sin(radians),
cos = Math.cos(radians),
var sin = fabric.util.sin(radians),
cos = fabric.util.cos(radians),
rx = vector.x * cos - vector.y * sin,
ry = vector.x * sin + vector.y * cos;
return {
Expand Down
Loading

0 comments on commit 2e532cc

Please sign in to comment.