/*
|
* Licensed to the Apache Software Foundation (ASF) under one
|
* or more contributor license agreements. See the NOTICE file
|
* distributed with this work for additional information
|
* regarding copyright ownership. The ASF licenses this file
|
* to you under the Apache License, Version 2.0 (the
|
* "License"); you may not use this file except in compliance
|
* with the License. You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing,
|
* software distributed under the License is distributed on an
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
* KIND, either express or implied. See the License for the
|
* specific language governing permissions and limitations
|
* under the License.
|
*/
|
|
import Point, { PointLike } from './Point';
|
import BoundingRect from './BoundingRect';
|
import { MatrixArray } from './matrix';
|
|
const extent = [0, 0];
|
const extent2 = [0, 0];
|
|
const minTv = new Point();
|
const maxTv = new Point();
|
|
class OrientedBoundingRect {
|
|
// lt, rt, rb, lb
|
private _corners: Point[] = [];
|
|
private _axes: Point[] = [];
|
|
private _origin: number[] = [0, 0];
|
|
constructor(rect?: BoundingRect, transform?: MatrixArray) {
|
for (let i = 0; i < 4; i++) {
|
this._corners[i] = new Point();
|
}
|
for (let i = 0; i < 2; i++) {
|
this._axes[i] = new Point();
|
}
|
|
if (rect) {
|
this.fromBoundingRect(rect, transform);
|
}
|
}
|
|
fromBoundingRect(rect: BoundingRect, transform?: MatrixArray) {
|
const corners = this._corners;
|
const axes = this._axes;
|
const x = rect.x;
|
const y = rect.y;
|
const x2 = x + rect.width;
|
const y2 = y + rect.height;
|
corners[0].set(x, y);
|
corners[1].set(x2, y);
|
corners[2].set(x2, y2);
|
corners[3].set(x, y2);
|
|
if (transform) {
|
for (let i = 0; i < 4; i++) {
|
corners[i].transform(transform);
|
}
|
}
|
|
// Calculate axes
|
Point.sub(axes[0], corners[1], corners[0]);
|
Point.sub(axes[1], corners[3], corners[0]);
|
axes[0].normalize();
|
axes[1].normalize();
|
|
// Calculate projected origin
|
for (let i = 0; i < 2; i++) {
|
this._origin[i] = axes[i].dot(corners[0]);
|
}
|
}
|
|
/**
|
* If intersect with another OBB
|
* @param other Bounding rect to be intersected with
|
* @param mtv Calculated .
|
* If it's not overlapped. it means needs to move given rect with Maximum Translation Vector to be overlapped.
|
* Else it means needs to move given rect with Minimum Translation Vector to be not overlapped.
|
*/
|
intersect(other: OrientedBoundingRect, mtv?: PointLike): boolean {
|
// OBB collision with SAT method
|
|
let overlapped = true;
|
const noMtv = !mtv;
|
minTv.set(Infinity, Infinity);
|
maxTv.set(0, 0);
|
// Check two axes for both two obb.
|
if (!this._intersectCheckOneSide(this, other, minTv, maxTv, noMtv, 1)) {
|
overlapped = false;
|
if (noMtv) {
|
// Early return if no need to calculate mtv
|
return overlapped;
|
}
|
}
|
if (!this._intersectCheckOneSide(other, this, minTv, maxTv, noMtv, -1)) {
|
overlapped = false;
|
if (noMtv) {
|
return overlapped;
|
}
|
}
|
|
if (!noMtv) {
|
Point.copy(mtv, overlapped ? minTv : maxTv);
|
}
|
|
return overlapped;
|
}
|
|
|
private _intersectCheckOneSide(
|
self: OrientedBoundingRect,
|
other: OrientedBoundingRect,
|
minTv: Point,
|
maxTv: Point,
|
noMtv: boolean,
|
inverse: 1 | -1
|
): boolean {
|
let overlapped = true;
|
for (let i = 0; i < 2; i++) {
|
const axis = this._axes[i];
|
this._getProjMinMaxOnAxis(i, self._corners, extent);
|
this._getProjMinMaxOnAxis(i, other._corners, extent2);
|
|
// Not overlap on the any axis.
|
if (extent[1] < extent2[0] || extent[0] > extent2[1]) {
|
overlapped = false;
|
if (noMtv) {
|
return overlapped;
|
}
|
const dist0 = Math.abs(extent2[0] - extent[1]);
|
const dist1 = Math.abs(extent[0] - extent2[1]);
|
|
// Find longest distance of all axes.
|
if (Math.min(dist0, dist1) > maxTv.len()) {
|
if (dist0 < dist1) {
|
Point.scale(maxTv, axis, -dist0 * inverse);
|
}
|
else {
|
Point.scale(maxTv, axis, dist1 * inverse);
|
}
|
}
|
}
|
else if (minTv) {
|
const dist0 = Math.abs(extent2[0] - extent[1]);
|
const dist1 = Math.abs(extent[0] - extent2[1]);
|
|
if (Math.min(dist0, dist1) < minTv.len()) {
|
if (dist0 < dist1) {
|
Point.scale(minTv, axis, dist0 * inverse);
|
}
|
else {
|
Point.scale(minTv, axis, -dist1 * inverse);
|
}
|
}
|
}
|
}
|
return overlapped;
|
}
|
|
private _getProjMinMaxOnAxis(dim: number, corners: Point[], out: number[]) {
|
const axis = this._axes[dim];
|
const origin = this._origin;
|
const proj = corners[0].dot(axis) + origin[dim];
|
let min = proj;
|
let max = proj;
|
|
for (let i = 1; i < corners.length; i++) {
|
const proj = corners[i].dot(axis) + origin[dim];
|
min = Math.min(proj, min);
|
max = Math.max(proj, max);
|
}
|
|
out[0] = min;
|
out[1] = max;
|
}
|
}
|
|
export default OrientedBoundingRect;
|