322 lines
11 KiB
JavaScript
322 lines
11 KiB
JavaScript
import * as matrix from './matrix.js';
|
|
import Point from './Point.js';
|
|
var mathMin = Math.min;
|
|
var mathMax = Math.max;
|
|
var mathAbs = Math.abs;
|
|
var XY = ['x', 'y'];
|
|
var WH = ['width', 'height'];
|
|
var lt = new Point();
|
|
var rb = new Point();
|
|
var lb = new Point();
|
|
var rt = new Point();
|
|
var _intersectCtx = createIntersectContext();
|
|
var _minTv = _intersectCtx.minTv;
|
|
var _maxTv = _intersectCtx.maxTv;
|
|
var _lenMinMax = [0, 0];
|
|
var BoundingRect = (function () {
|
|
function BoundingRect(x, y, width, height) {
|
|
BoundingRect.set(this, x, y, width, height);
|
|
}
|
|
BoundingRect.set = function (target, x, y, width, height) {
|
|
if (width < 0) {
|
|
x = x + width;
|
|
width = -width;
|
|
}
|
|
if (height < 0) {
|
|
y = y + height;
|
|
height = -height;
|
|
}
|
|
target.x = x;
|
|
target.y = y;
|
|
target.width = width;
|
|
target.height = height;
|
|
return target;
|
|
};
|
|
BoundingRect.prototype.union = function (other) {
|
|
var x = mathMin(other.x, this.x);
|
|
var y = mathMin(other.y, this.y);
|
|
if (isFinite(this.x) && isFinite(this.width)) {
|
|
this.width = mathMax(other.x + other.width, this.x + this.width) - x;
|
|
}
|
|
else {
|
|
this.width = other.width;
|
|
}
|
|
if (isFinite(this.y) && isFinite(this.height)) {
|
|
this.height = mathMax(other.y + other.height, this.y + this.height) - y;
|
|
}
|
|
else {
|
|
this.height = other.height;
|
|
}
|
|
this.x = x;
|
|
this.y = y;
|
|
};
|
|
BoundingRect.prototype.applyTransform = function (m) {
|
|
BoundingRect.applyTransform(this, this, m);
|
|
};
|
|
BoundingRect.prototype.calculateTransform = function (b) {
|
|
var a = this;
|
|
var sx = b.width / a.width;
|
|
var sy = b.height / a.height;
|
|
var m = matrix.create();
|
|
matrix.translate(m, m, [-a.x, -a.y]);
|
|
matrix.scale(m, m, [sx, sy]);
|
|
matrix.translate(m, m, [b.x, b.y]);
|
|
return m;
|
|
};
|
|
BoundingRect.prototype.intersect = function (b, mtv, opt) {
|
|
return BoundingRect.intersect(this, b, mtv, opt);
|
|
};
|
|
BoundingRect.intersect = function (a, b, mtv, opt) {
|
|
if (mtv) {
|
|
Point.set(mtv, 0, 0);
|
|
}
|
|
var outIntersectRect = opt && opt.outIntersectRect || null;
|
|
var clamp = opt && opt.clamp;
|
|
if (outIntersectRect) {
|
|
outIntersectRect.x = outIntersectRect.y = outIntersectRect.width = outIntersectRect.height = NaN;
|
|
}
|
|
if (!a || !b) {
|
|
return false;
|
|
}
|
|
if (!(a instanceof BoundingRect)) {
|
|
a = BoundingRect.set(_tmpIntersectA, a.x, a.y, a.width, a.height);
|
|
}
|
|
if (!(b instanceof BoundingRect)) {
|
|
b = BoundingRect.set(_tmpIntersectB, b.x, b.y, b.width, b.height);
|
|
}
|
|
var useMTV = !!mtv;
|
|
_intersectCtx.reset(opt, useMTV);
|
|
var touchThreshold = _intersectCtx.touchThreshold;
|
|
var ax0 = a.x + touchThreshold;
|
|
var ax1 = a.x + a.width - touchThreshold;
|
|
var ay0 = a.y + touchThreshold;
|
|
var ay1 = a.y + a.height - touchThreshold;
|
|
var bx0 = b.x + touchThreshold;
|
|
var bx1 = b.x + b.width - touchThreshold;
|
|
var by0 = b.y + touchThreshold;
|
|
var by1 = b.y + b.height - touchThreshold;
|
|
if (ax0 > ax1 || ay0 > ay1 || bx0 > bx1 || by0 > by1) {
|
|
return false;
|
|
}
|
|
var overlap = !(ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0);
|
|
if (useMTV || outIntersectRect) {
|
|
_lenMinMax[0] = Infinity;
|
|
_lenMinMax[1] = 0;
|
|
intersectOneDim(ax0, ax1, bx0, bx1, 0, useMTV, outIntersectRect, clamp);
|
|
intersectOneDim(ay0, ay1, by0, by1, 1, useMTV, outIntersectRect, clamp);
|
|
if (useMTV) {
|
|
Point.copy(mtv, overlap
|
|
? (_intersectCtx.useDir ? _intersectCtx.dirMinTv : _minTv)
|
|
: _maxTv);
|
|
}
|
|
}
|
|
return overlap;
|
|
};
|
|
BoundingRect.contain = function (rect, x, y) {
|
|
return x >= rect.x
|
|
&& x <= (rect.x + rect.width)
|
|
&& y >= rect.y
|
|
&& y <= (rect.y + rect.height);
|
|
};
|
|
BoundingRect.prototype.contain = function (x, y) {
|
|
return BoundingRect.contain(this, x, y);
|
|
};
|
|
BoundingRect.prototype.clone = function () {
|
|
return new BoundingRect(this.x, this.y, this.width, this.height);
|
|
};
|
|
BoundingRect.prototype.copy = function (other) {
|
|
BoundingRect.copy(this, other);
|
|
};
|
|
BoundingRect.prototype.plain = function () {
|
|
return {
|
|
x: this.x,
|
|
y: this.y,
|
|
width: this.width,
|
|
height: this.height
|
|
};
|
|
};
|
|
BoundingRect.prototype.isFinite = function () {
|
|
return isFinite(this.x)
|
|
&& isFinite(this.y)
|
|
&& isFinite(this.width)
|
|
&& isFinite(this.height);
|
|
};
|
|
BoundingRect.prototype.isZero = function () {
|
|
return this.width === 0 || this.height === 0;
|
|
};
|
|
BoundingRect.create = function (rect) {
|
|
return new BoundingRect(rect.x, rect.y, rect.width, rect.height);
|
|
};
|
|
BoundingRect.copy = function (target, source) {
|
|
target.x = source.x;
|
|
target.y = source.y;
|
|
target.width = source.width;
|
|
target.height = source.height;
|
|
return target;
|
|
};
|
|
BoundingRect.applyTransform = function (target, source, m) {
|
|
if (!m) {
|
|
if (target !== source) {
|
|
BoundingRect.copy(target, source);
|
|
}
|
|
return;
|
|
}
|
|
if (m[1] < 1e-5 && m[1] > -1e-5 && m[2] < 1e-5 && m[2] > -1e-5) {
|
|
var sx = m[0];
|
|
var sy = m[3];
|
|
var tx = m[4];
|
|
var ty = m[5];
|
|
target.x = source.x * sx + tx;
|
|
target.y = source.y * sy + ty;
|
|
target.width = source.width * sx;
|
|
target.height = source.height * sy;
|
|
if (target.width < 0) {
|
|
target.x += target.width;
|
|
target.width = -target.width;
|
|
}
|
|
if (target.height < 0) {
|
|
target.y += target.height;
|
|
target.height = -target.height;
|
|
}
|
|
return;
|
|
}
|
|
lt.x = lb.x = source.x;
|
|
lt.y = rt.y = source.y;
|
|
rb.x = rt.x = source.x + source.width;
|
|
rb.y = lb.y = source.y + source.height;
|
|
lt.transform(m);
|
|
rt.transform(m);
|
|
rb.transform(m);
|
|
lb.transform(m);
|
|
target.x = mathMin(lt.x, rb.x, lb.x, rt.x);
|
|
target.y = mathMin(lt.y, rb.y, lb.y, rt.y);
|
|
var maxX = mathMax(lt.x, rb.x, lb.x, rt.x);
|
|
var maxY = mathMax(lt.y, rb.y, lb.y, rt.y);
|
|
target.width = maxX - target.x;
|
|
target.height = maxY - target.y;
|
|
};
|
|
return BoundingRect;
|
|
}());
|
|
var _tmpIntersectA = new BoundingRect(0, 0, 0, 0);
|
|
var _tmpIntersectB = new BoundingRect(0, 0, 0, 0);
|
|
function intersectOneDim(a0, a1, b0, b1, updateDimIdx, useMTV, outIntersectRect, clamp) {
|
|
var d0 = mathAbs(a1 - b0);
|
|
var d1 = mathAbs(b1 - a0);
|
|
var d01min = mathMin(d0, d1);
|
|
var updateDim = XY[updateDimIdx];
|
|
var zeroDim = XY[1 - updateDimIdx];
|
|
var wh = WH[updateDimIdx];
|
|
if (a1 < b0 || b1 < a0) {
|
|
if (d0 < d1) {
|
|
if (useMTV) {
|
|
_maxTv[updateDim] = -d0;
|
|
}
|
|
if (clamp) {
|
|
outIntersectRect[updateDim] = a1;
|
|
outIntersectRect[wh] = 0;
|
|
}
|
|
}
|
|
else {
|
|
if (useMTV) {
|
|
_maxTv[updateDim] = d1;
|
|
}
|
|
if (clamp) {
|
|
outIntersectRect[updateDim] = a0;
|
|
outIntersectRect[wh] = 0;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (outIntersectRect) {
|
|
outIntersectRect[updateDim] = mathMax(a0, b0);
|
|
outIntersectRect[wh] = mathMin(a1, b1) - outIntersectRect[updateDim];
|
|
}
|
|
if (useMTV) {
|
|
if (d01min < _lenMinMax[0] || _intersectCtx.useDir) {
|
|
_lenMinMax[0] = mathMin(d01min, _lenMinMax[0]);
|
|
if (d0 < d1 || !_intersectCtx.bidirectional) {
|
|
_minTv[updateDim] = d0;
|
|
_minTv[zeroDim] = 0;
|
|
if (_intersectCtx.useDir) {
|
|
_intersectCtx.calcDirMTV();
|
|
}
|
|
}
|
|
if (d0 >= d1 || !_intersectCtx.bidirectional) {
|
|
_minTv[updateDim] = -d1;
|
|
_minTv[zeroDim] = 0;
|
|
if (_intersectCtx.useDir) {
|
|
_intersectCtx.calcDirMTV();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
export function createIntersectContext() {
|
|
var _direction = 0;
|
|
var _dirCheckVec = new Point();
|
|
var _dirTmp = new Point();
|
|
var _ctx = {
|
|
minTv: new Point(),
|
|
maxTv: new Point(),
|
|
useDir: false,
|
|
dirMinTv: new Point(),
|
|
touchThreshold: 0,
|
|
bidirectional: true,
|
|
negativeSize: false,
|
|
reset: function (opt, useMTV) {
|
|
_ctx.touchThreshold = 0;
|
|
if (opt && opt.touchThreshold != null) {
|
|
_ctx.touchThreshold = mathMax(0, opt.touchThreshold);
|
|
}
|
|
_ctx.negativeSize = false;
|
|
if (!useMTV) {
|
|
return;
|
|
}
|
|
_ctx.minTv.set(Infinity, Infinity);
|
|
_ctx.maxTv.set(0, 0);
|
|
_ctx.useDir = false;
|
|
if (opt && opt.direction != null) {
|
|
_ctx.useDir = true;
|
|
_ctx.dirMinTv.copy(_ctx.minTv);
|
|
_dirTmp.copy(_ctx.minTv);
|
|
_direction = opt.direction;
|
|
_ctx.bidirectional = opt.bidirectional == null || !!opt.bidirectional;
|
|
if (!_ctx.bidirectional) {
|
|
_dirCheckVec.set(Math.cos(_direction), Math.sin(_direction));
|
|
}
|
|
}
|
|
},
|
|
calcDirMTV: function () {
|
|
var minTv = _ctx.minTv;
|
|
var dirMinTv = _ctx.dirMinTv;
|
|
var squareMag = minTv.y * minTv.y + minTv.x * minTv.x;
|
|
var dirSin = Math.sin(_direction);
|
|
var dirCos = Math.cos(_direction);
|
|
var dotProd = dirSin * minTv.y + dirCos * minTv.x;
|
|
if (nearZero(dotProd)) {
|
|
if (nearZero(minTv.x) && nearZero(minTv.y)) {
|
|
dirMinTv.set(0, 0);
|
|
}
|
|
return;
|
|
}
|
|
_dirTmp.x = squareMag * dirCos / dotProd;
|
|
_dirTmp.y = squareMag * dirSin / dotProd;
|
|
if (nearZero(_dirTmp.x) && nearZero(_dirTmp.y)) {
|
|
dirMinTv.set(0, 0);
|
|
return;
|
|
}
|
|
if ((_ctx.bidirectional
|
|
|| _dirCheckVec.dot(_dirTmp) > 0)
|
|
&& _dirTmp.len() < dirMinTv.len()) {
|
|
dirMinTv.copy(_dirTmp);
|
|
}
|
|
}
|
|
};
|
|
function nearZero(val) {
|
|
return mathAbs(val) < 1e-10;
|
|
}
|
|
return _ctx;
|
|
}
|
|
export default BoundingRect;
|