dragonbones-runtime
Version:
the tools to build dragonbones file for diffrent framework
749 lines (677 loc) • 27.6 kB
text/typescript
/**
* The MIT License (MIT)
*
* Copyright (c) 2012-2017 DragonBones team and other contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace dragonBones {
/**
* - The base class of bounding box data.
* @see dragonBones.RectangleData
* @see dragonBones.EllipseData
* @see dragonBones.PolygonData
* @version DragonBones 5.0
* @language en_US
*/
/**
* - 边界框数据基类。
* @see dragonBones.RectangleData
* @see dragonBones.EllipseData
* @see dragonBones.PolygonData
* @version DragonBones 5.0
* @language zh_CN
*/
export abstract class BoundingBoxData extends BaseObject {
/**
* - The bounding box type.
* @version DragonBones 5.0
* @language en_US
*/
/**
* - 边界框类型。
* @version DragonBones 5.0
* @language zh_CN
*/
public type: BoundingBoxType;
/**
* @private
*/
public color: number;
/**
* @private
*/
public width: number;
/**
* @private
*/
public height: number;
/**
* @private
*/
protected _onClear(): void {
this.color = 0x000000;
this.width = 0.0;
this.height = 0.0;
}
/**
* - Check whether the bounding box contains a specific point. (Local coordinate system)
* @version DragonBones 5.0
* @language en_US
*/
/**
* - 检查边界框是否包含特定点。(本地坐标系)
* @version DragonBones 5.0
* @language zh_CN
*/
public abstract containsPoint(pX: number, pY: number): boolean;
/**
* - Check whether the bounding box intersects a specific segment. (Local coordinate system)
* @version DragonBones 5.0
* @language en_US
*/
/**
* - 检查边界框是否与特定线段相交。(本地坐标系)
* @version DragonBones 5.0
* @language zh_CN
*/
public abstract intersectsSegment(
xA: number, yA: number, xB: number, yB: number,
intersectionPointA: { x: number, y: number } | null,
intersectionPointB: { x: number, y: number } | null,
normalRadians: { x: number, y: number } | null
): number;
}
/**
* - Cohen–Sutherland algorithm https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
* ----------------------
* | 0101 | 0100 | 0110 |
* ----------------------
* | 0001 | 0000 | 0010 |
* ----------------------
* | 1001 | 1000 | 1010 |
* ----------------------
*/
const enum OutCode {
InSide = 0, // 0000
Left = 1, // 0001
Right = 2, // 0010
Top = 4, // 0100
Bottom = 8 // 1000
}
/**
* - The rectangle bounding box data.
* @version DragonBones 5.1
* @language en_US
*/
/**
* - 矩形边界框数据。
* @version DragonBones 5.1
* @language zh_CN
*/
export class RectangleBoundingBoxData extends BoundingBoxData {
public static toString(): string {
return "[class dragonBones.RectangleBoundingBoxData]";
}
/**
* - Compute the bit code for a point (x, y) using the clip rectangle
*/
private static _computeOutCode(x: number, y: number, xMin: number, yMin: number, xMax: number, yMax: number): number {
let code = OutCode.InSide; // initialised as being inside of [[clip window]]
if (x < xMin) { // to the left of clip window
code |= OutCode.Left;
}
else if (x > xMax) { // to the right of clip window
code |= OutCode.Right;
}
if (y < yMin) { // below the clip window
code |= OutCode.Top;
}
else if (y > yMax) { // above the clip window
code |= OutCode.Bottom;
}
return code;
}
/**
* @private
*/
public static rectangleIntersectsSegment(
xA: number, yA: number, xB: number, yB: number,
xMin: number, yMin: number, xMax: number, yMax: number,
intersectionPointA: { x: number, y: number } | null = null,
intersectionPointB: { x: number, y: number } | null = null,
normalRadians: { x: number, y: number } | null = null
): number {
const inSideA = xA > xMin && xA < xMax && yA > yMin && yA < yMax;
const inSideB = xB > xMin && xB < xMax && yB > yMin && yB < yMax;
if (inSideA && inSideB) {
return -1;
}
let intersectionCount = 0;
let outcode0 = RectangleBoundingBoxData._computeOutCode(xA, yA, xMin, yMin, xMax, yMax);
let outcode1 = RectangleBoundingBoxData._computeOutCode(xB, yB, xMin, yMin, xMax, yMax);
while (true) {
if ((outcode0 | outcode1) === 0) { // Bitwise OR is 0. Trivially accept and get out of loop
intersectionCount = 2;
break;
}
else if ((outcode0 & outcode1) !== 0) { // Bitwise AND is not 0. Trivially reject and get out of loop
break;
}
// failed both tests, so calculate the line segment to clip
// from an outside point to an intersection with clip edge
let x = 0.0;
let y = 0.0;
let normalRadian = 0.0;
// At least one endpoint is outside the clip rectangle; pick it.
const outcodeOut = outcode0 !== 0 ? outcode0 : outcode1;
// Now find the intersection point;
if ((outcodeOut & OutCode.Top) !== 0) { // point is above the clip rectangle
x = xA + (xB - xA) * (yMin - yA) / (yB - yA);
y = yMin;
if (normalRadians !== null) {
normalRadian = -Math.PI * 0.5;
}
}
else if ((outcodeOut & OutCode.Bottom) !== 0) { // point is below the clip rectangle
x = xA + (xB - xA) * (yMax - yA) / (yB - yA);
y = yMax;
if (normalRadians !== null) {
normalRadian = Math.PI * 0.5;
}
}
else if ((outcodeOut & OutCode.Right) !== 0) { // point is to the right of clip rectangle
y = yA + (yB - yA) * (xMax - xA) / (xB - xA);
x = xMax;
if (normalRadians !== null) {
normalRadian = 0;
}
}
else if ((outcodeOut & OutCode.Left) !== 0) { // point is to the left of clip rectangle
y = yA + (yB - yA) * (xMin - xA) / (xB - xA);
x = xMin;
if (normalRadians !== null) {
normalRadian = Math.PI;
}
}
// Now we move outside point to intersection point to clip
// and get ready for next pass.
if (outcodeOut === outcode0) {
xA = x;
yA = y;
outcode0 = RectangleBoundingBoxData._computeOutCode(xA, yA, xMin, yMin, xMax, yMax);
if (normalRadians !== null) {
normalRadians.x = normalRadian;
}
}
else {
xB = x;
yB = y;
outcode1 = RectangleBoundingBoxData._computeOutCode(xB, yB, xMin, yMin, xMax, yMax);
if (normalRadians !== null) {
normalRadians.y = normalRadian;
}
}
}
if (intersectionCount) {
if (inSideA) {
intersectionCount = 2; // 10
if (intersectionPointA !== null) {
intersectionPointA.x = xB;
intersectionPointA.y = yB;
}
if (intersectionPointB !== null) {
intersectionPointB.x = xB;
intersectionPointB.y = xB;
}
if (normalRadians !== null) {
normalRadians.x = normalRadians.y + Math.PI;
}
}
else if (inSideB) {
intersectionCount = 1; // 01
if (intersectionPointA !== null) {
intersectionPointA.x = xA;
intersectionPointA.y = yA;
}
if (intersectionPointB !== null) {
intersectionPointB.x = xA;
intersectionPointB.y = yA;
}
if (normalRadians !== null) {
normalRadians.y = normalRadians.x + Math.PI;
}
}
else {
intersectionCount = 3; // 11
if (intersectionPointA !== null) {
intersectionPointA.x = xA;
intersectionPointA.y = yA;
}
if (intersectionPointB !== null) {
intersectionPointB.x = xB;
intersectionPointB.y = yB;
}
}
}
return intersectionCount;
}
/**
* @inheritDoc
* @private
*/
protected _onClear(): void {
super._onClear();
this.type = BoundingBoxType.Rectangle;
}
/**
* @inheritDoc
*/
public containsPoint(pX: number, pY: number): boolean {
const widthH = this.width * 0.5;
if (pX >= -widthH && pX <= widthH) {
const heightH = this.height * 0.5;
if (pY >= -heightH && pY <= heightH) {
return true;
}
}
return false;
}
/**
* @inheritDoc
*/
public intersectsSegment(
xA: number, yA: number, xB: number, yB: number,
intersectionPointA: { x: number, y: number } | null = null,
intersectionPointB: { x: number, y: number } | null = null,
normalRadians: { x: number, y: number } | null = null
): number {
const widthH = this.width * 0.5;
const heightH = this.height * 0.5;
const intersectionCount = RectangleBoundingBoxData.rectangleIntersectsSegment(
xA, yA, xB, yB,
-widthH, -heightH, widthH, heightH,
intersectionPointA, intersectionPointB, normalRadians
);
return intersectionCount;
}
}
/**
* - The ellipse bounding box data.
* @version DragonBones 5.1
* @language en_US
*/
/**
* - 椭圆边界框数据。
* @version DragonBones 5.1
* @language zh_CN
*/
export class EllipseBoundingBoxData extends BoundingBoxData {
public static toString(): string {
return "[class dragonBones.EllipseData]";
}
/**
* @private
*/
public static ellipseIntersectsSegment(
xA: number, yA: number, xB: number, yB: number,
xC: number, yC: number, widthH: number, heightH: number,
intersectionPointA: { x: number, y: number } | null = null,
intersectionPointB: { x: number, y: number } | null = null,
normalRadians: { x: number, y: number } | null = null
): number {
const d = widthH / heightH;
const dd = d * d;
yA *= d;
yB *= d;
const dX = xB - xA;
const dY = yB - yA;
const lAB = Math.sqrt(dX * dX + dY * dY);
const xD = dX / lAB;
const yD = dY / lAB;
const a = (xC - xA) * xD + (yC - yA) * yD;
const aa = a * a;
const ee = xA * xA + yA * yA;
const rr = widthH * widthH;
const dR = rr - ee + aa;
let intersectionCount = 0;
if (dR >= 0.0) {
const dT = Math.sqrt(dR);
const sA = a - dT;
const sB = a + dT;
const inSideA = sA < 0.0 ? -1 : (sA <= lAB ? 0 : 1);
const inSideB = sB < 0.0 ? -1 : (sB <= lAB ? 0 : 1);
const sideAB = inSideA * inSideB;
if (sideAB < 0) {
return -1;
}
else if (sideAB === 0) {
if (inSideA === -1) {
intersectionCount = 2; // 10
xB = xA + sB * xD;
yB = (yA + sB * yD) / d;
if (intersectionPointA !== null) {
intersectionPointA.x = xB;
intersectionPointA.y = yB;
}
if (intersectionPointB !== null) {
intersectionPointB.x = xB;
intersectionPointB.y = yB;
}
if (normalRadians !== null) {
normalRadians.x = Math.atan2(yB / rr * dd, xB / rr);
normalRadians.y = normalRadians.x + Math.PI;
}
}
else if (inSideB === 1) {
intersectionCount = 1; // 01
xA = xA + sA * xD;
yA = (yA + sA * yD) / d;
if (intersectionPointA !== null) {
intersectionPointA.x = xA;
intersectionPointA.y = yA;
}
if (intersectionPointB !== null) {
intersectionPointB.x = xA;
intersectionPointB.y = yA;
}
if (normalRadians !== null) {
normalRadians.x = Math.atan2(yA / rr * dd, xA / rr);
normalRadians.y = normalRadians.x + Math.PI;
}
}
else {
intersectionCount = 3; // 11
if (intersectionPointA !== null) {
intersectionPointA.x = xA + sA * xD;
intersectionPointA.y = (yA + sA * yD) / d;
if (normalRadians !== null) {
normalRadians.x = Math.atan2(intersectionPointA.y / rr * dd, intersectionPointA.x / rr);
}
}
if (intersectionPointB !== null) {
intersectionPointB.x = xA + sB * xD;
intersectionPointB.y = (yA + sB * yD) / d;
if (normalRadians !== null) {
normalRadians.y = Math.atan2(intersectionPointB.y / rr * dd, intersectionPointB.x / rr);
}
}
}
}
}
return intersectionCount;
}
/**
* @inheritDoc
* @private
*/
protected _onClear(): void {
super._onClear();
this.type = BoundingBoxType.Ellipse;
}
/**
* @inheritDoc
*/
public containsPoint(pX: number, pY: number): boolean {
const widthH = this.width * 0.5;
if (pX >= -widthH && pX <= widthH) {
const heightH = this.height * 0.5;
if (pY >= -heightH && pY <= heightH) {
pY *= widthH / heightH;
return Math.sqrt(pX * pX + pY * pY) <= widthH;
}
}
return false;
}
/**
* @inheritDoc
*/
public intersectsSegment(
xA: number, yA: number, xB: number, yB: number,
intersectionPointA: { x: number, y: number } | null = null,
intersectionPointB: { x: number, y: number } | null = null,
normalRadians: { x: number, y: number } | null = null
): number {
const intersectionCount = EllipseBoundingBoxData.ellipseIntersectsSegment(
xA, yA, xB, yB,
0.0, 0.0, this.width * 0.5, this.height * 0.5,
intersectionPointA, intersectionPointB, normalRadians
);
return intersectionCount;
}
}
/**
* - The polygon bounding box data.
* @version DragonBones 5.1
* @language en_US
*/
/**
* - 多边形边界框数据。
* @version DragonBones 5.1
* @language zh_CN
*/
export class PolygonBoundingBoxData extends BoundingBoxData {
public static toString(): string {
return "[class dragonBones.PolygonBoundingBoxData]";
}
/**
* @private
*/
public static polygonIntersectsSegment(
xA: number, yA: number, xB: number, yB: number,
vertices: Array<number>,
intersectionPointA: { x: number, y: number } | null = null,
intersectionPointB: { x: number, y: number } | null = null,
normalRadians: { x: number, y: number } | null = null
): number {
if (xA === xB) {
xA = xB + 0.000001;
}
if (yA === yB) {
yA = yB + 0.000001;
}
const count = vertices.length;
const dXAB = xA - xB;
const dYAB = yA - yB;
const llAB = xA * yB - yA * xB;
let intersectionCount = 0;
let xC = vertices[count - 2];
let yC = vertices[count - 1];
let dMin = 0.0;
let dMax = 0.0;
let xMin = 0.0;
let yMin = 0.0;
let xMax = 0.0;
let yMax = 0.0;
for (let i = 0; i < count; i += 2) {
const xD = vertices[i];
const yD = vertices[i + 1];
if (xC === xD) {
xC = xD + 0.0001;
}
if (yC === yD) {
yC = yD + 0.0001;
}
const dXCD = xC - xD;
const dYCD = yC - yD;
const llCD = xC * yD - yC * xD;
const ll = dXAB * dYCD - dYAB * dXCD;
const x = (llAB * dXCD - dXAB * llCD) / ll;
if (((x >= xC && x <= xD) || (x >= xD && x <= xC)) && (dXAB === 0.0 || (x >= xA && x <= xB) || (x >= xB && x <= xA))) {
const y = (llAB * dYCD - dYAB * llCD) / ll;
if (((y >= yC && y <= yD) || (y >= yD && y <= yC)) && (dYAB === 0.0 || (y >= yA && y <= yB) || (y >= yB && y <= yA))) {
if (intersectionPointB !== null) {
let d = x - xA;
if (d < 0.0) {
d = -d;
}
if (intersectionCount === 0) {
dMin = d;
dMax = d;
xMin = x;
yMin = y;
xMax = x;
yMax = y;
if (normalRadians !== null) {
normalRadians.x = Math.atan2(yD - yC, xD - xC) - Math.PI * 0.5;
normalRadians.y = normalRadians.x;
}
}
else {
if (d < dMin) {
dMin = d;
xMin = x;
yMin = y;
if (normalRadians !== null) {
normalRadians.x = Math.atan2(yD - yC, xD - xC) - Math.PI * 0.5;
}
}
if (d > dMax) {
dMax = d;
xMax = x;
yMax = y;
if (normalRadians !== null) {
normalRadians.y = Math.atan2(yD - yC, xD - xC) - Math.PI * 0.5;
}
}
}
intersectionCount++;
}
else {
xMin = x;
yMin = y;
xMax = x;
yMax = y;
intersectionCount++;
if (normalRadians !== null) {
normalRadians.x = Math.atan2(yD - yC, xD - xC) - Math.PI * 0.5;
normalRadians.y = normalRadians.x;
}
break;
}
}
}
xC = xD;
yC = yD;
}
if (intersectionCount === 1) {
if (intersectionPointA !== null) {
intersectionPointA.x = xMin;
intersectionPointA.y = yMin;
}
if (intersectionPointB !== null) {
intersectionPointB.x = xMin;
intersectionPointB.y = yMin;
}
if (normalRadians !== null) {
normalRadians.y = normalRadians.x + Math.PI;
}
}
else if (intersectionCount > 1) {
intersectionCount++;
if (intersectionPointA !== null) {
intersectionPointA.x = xMin;
intersectionPointA.y = yMin;
}
if (intersectionPointB !== null) {
intersectionPointB.x = xMax;
intersectionPointB.y = yMax;
}
}
return intersectionCount;
}
/**
* @private
*/
public x: number;
/**
* @private
*/
public y: number;
/**
* - The polygon vertices.
* @version DragonBones 5.1
* @language en_US
*/
/**
* - 多边形顶点。
* @version DragonBones 5.1
* @language zh_CN
*/
public readonly vertices: Array<number> = [];
/**
* @private
*/
public weight: WeightData | null = null; // Initial value.
/**
* @inheritDoc
* @private
*/
protected _onClear(): void {
super._onClear();
if (this.weight !== null) {
this.weight.returnToPool();
}
this.type = BoundingBoxType.Polygon;
this.x = 0.0;
this.y = 0.0;
this.vertices.length = 0;
this.weight = null;
}
/**
* @inheritDoc
*/
public containsPoint(pX: number, pY: number): boolean {
let isInSide = false;
if (pX >= this.x && pX <= this.width && pY >= this.y && pY <= this.height) {
for (let i = 0, l = this.vertices.length, iP = l - 2; i < l; i += 2) {
const yA = this.vertices[iP + 1];
const yB = this.vertices[i + 1];
if ((yB < pY && yA >= pY) || (yA < pY && yB >= pY)) {
const xA = this.vertices[iP];
const xB = this.vertices[i];
if ((pY - yB) * (xA - xB) / (yA - yB) + xB < pX) {
isInSide = !isInSide;
}
}
iP = i;
}
}
return isInSide;
}
/**
* @inheritDoc
*/
public intersectsSegment(
xA: number, yA: number, xB: number, yB: number,
intersectionPointA: { x: number, y: number } | null = null,
intersectionPointB: { x: number, y: number } | null = null,
normalRadians: { x: number, y: number } | null = null
): number {
let intersectionCount = 0;
if (RectangleBoundingBoxData.rectangleIntersectsSegment(xA, yA, xB, yB, this.x, this.y, this.x + this.width, this.y + this.height, null, null, null) !== 0) {
intersectionCount = PolygonBoundingBoxData.polygonIntersectsSegment(
xA, yA, xB, yB,
this.vertices,
intersectionPointA, intersectionPointB, normalRadians
);
}
return intersectionCount;
}
}
}