mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
311 lines
10 KiB
Haxe
311 lines
10 KiB
Haxe
/******************************************************************************
|
|
* Spine Runtimes License Agreement
|
|
* Last updated July 28, 2023. Replaces all prior versions.
|
|
*
|
|
* Copyright (c) 2013-2023, Esoteric Software LLC
|
|
*
|
|
* Integration of the Spine Runtimes into software or otherwise creating
|
|
* derivative works of the Spine Runtimes is permitted under the terms and
|
|
* conditions of Section 2 of the Spine Editor License Agreement:
|
|
* http://esotericsoftware.com/spine-editor-license
|
|
*
|
|
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
|
|
* otherwise create derivative works of the Spine Runtimes (collectively,
|
|
* "Products"), provided that each user of the Products must obtain their own
|
|
* Spine Editor license and redistribution of the Products in any form must
|
|
* include this license and copyright notice.
|
|
*
|
|
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
|
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
|
|
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*****************************************************************************/
|
|
|
|
package spine;
|
|
|
|
class Triangulator {
|
|
private var convexPolygons:Array<Array<Float>> = new Array<Array<Float>>();
|
|
private var convexPolygonsIndices:Array<Array<Int>> = new Array<Array<Int>>();
|
|
private var indicesArray:Array<Int> = new Array<Int>();
|
|
private var isConcaveArray:Array<Bool> = new Array<Bool>();
|
|
private var triangles:Array<Int> = new Array<Int>();
|
|
private var polygonPool:Pool<Array<Float>> = new Pool(function():Dynamic {
|
|
return new Array<Float>();
|
|
});
|
|
private var polygonIndicesPool:Pool<Array<Int>> = new Pool(function():Dynamic {
|
|
return new Array<Int>();
|
|
});
|
|
|
|
public function new() {}
|
|
|
|
public function triangulate(vertices:Array<Float>):Array<Int> {
|
|
var vertexCount:Int = vertices.length >> 1;
|
|
|
|
indicesArray.resize(0);
|
|
for (i in 0...vertexCount) {
|
|
indicesArray.push(i);
|
|
}
|
|
|
|
isConcaveArray.resize(0);
|
|
for (i in 0...vertexCount) {
|
|
isConcaveArray.push(isConcave(i, vertexCount, vertices, indicesArray));
|
|
}
|
|
|
|
triangles.resize(0);
|
|
|
|
while (vertexCount > 3) {
|
|
// Find ear tip.
|
|
var previous:Int = vertexCount - 1, next:Int = 1;
|
|
var i:Int = 0;
|
|
while (true) {
|
|
if (!isConcaveArray[i]) {
|
|
var p1:Int = indicesArray[previous] << 1,
|
|
p2:Int = indicesArray[i] << 1,
|
|
p3:Int = indicesArray[next] << 1;
|
|
var p1x:Float = vertices[p1], p1y:Float = vertices[p1 + 1];
|
|
var p2x:Float = vertices[p2], p2y:Float = vertices[p2 + 1];
|
|
var p3x:Float = vertices[p3], p3y:Float = vertices[p3 + 1];
|
|
var ii:Int = (next + 1) % vertexCount;
|
|
while (ii != previous) {
|
|
if (!isConcaveArray[ii]) {
|
|
ii = (ii + 1) % vertexCount;
|
|
continue;
|
|
}
|
|
var v:Int = indicesArray[ii] << 1;
|
|
var vx:Float = vertices[v];
|
|
var vy:Float = vertices[v + 1];
|
|
if (positiveArea(p3x, p3y, p1x, p1y, vx, vy)) {
|
|
if (positiveArea(p1x, p1y, p2x, p2y, vx, vy)) {
|
|
if (positiveArea(p2x, p2y, p3x, p3y, vx, vy)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
ii = (ii + 1) % vertexCount;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (next == 0) {
|
|
do {
|
|
if (!isConcaveArray[i])
|
|
break;
|
|
i--;
|
|
} while (i > 0);
|
|
break;
|
|
}
|
|
|
|
previous = i;
|
|
i = next;
|
|
next = (next + 1) % vertexCount;
|
|
}
|
|
|
|
// Cut ear tip.
|
|
triangles.push(indicesArray[(vertexCount + i - 1) % vertexCount]);
|
|
triangles.push(indicesArray[i]);
|
|
triangles.push(indicesArray[(i + 1) % vertexCount]);
|
|
indicesArray.splice(i, 1);
|
|
isConcaveArray.splice(i, 1);
|
|
vertexCount--;
|
|
|
|
var previousIndex:Int = (vertexCount + i - 1) % vertexCount;
|
|
var nextIndex:Int = i == vertexCount ? 0 : i;
|
|
isConcaveArray[previousIndex] = isConcave(previousIndex, vertexCount, vertices, indicesArray);
|
|
isConcaveArray[nextIndex] = isConcave(nextIndex, vertexCount, vertices, indicesArray);
|
|
}
|
|
|
|
if (vertexCount == 3) {
|
|
triangles.push(indicesArray[2]);
|
|
triangles.push(indicesArray[0]);
|
|
triangles.push(indicesArray[1]);
|
|
}
|
|
|
|
return triangles;
|
|
}
|
|
|
|
public function decompose(vertices:Array<Float>, triangles:Array<Int>):Array<Array<Float>> {
|
|
for (i in 0...convexPolygons.length) {
|
|
this.polygonPool.free(convexPolygons[i]);
|
|
}
|
|
convexPolygons.resize(0);
|
|
|
|
for (i in 0...convexPolygonsIndices.length) {
|
|
this.polygonIndicesPool.free(convexPolygonsIndices[i]);
|
|
}
|
|
convexPolygonsIndices.resize(0);
|
|
|
|
var polygonIndices:Array<Int> = polygonIndicesPool.obtain();
|
|
polygonIndices.resize(0);
|
|
|
|
var polygon:Array<Float> = polygonPool.obtain();
|
|
polygon.resize(0);
|
|
|
|
// Merge subsequent triangles if they form a triangle fan.
|
|
var fanBaseIndex:Int = -1, lastWinding:Int = 0;
|
|
var x1:Float, y1:Float, x2:Float, y2:Float, x3:Float, y3:Float;
|
|
var winding1:Int, winding2:Int, o:Int;
|
|
var i:Int = 0;
|
|
while (i < triangles.length) {
|
|
var t1:Int = triangles[i] << 1,
|
|
t2:Int = triangles[i + 1] << 1,
|
|
t3:Int = triangles[i + 2] << 1;
|
|
x1 = vertices[t1];
|
|
y1 = vertices[t1 + 1];
|
|
x2 = vertices[t2];
|
|
y2 = vertices[t2 + 1];
|
|
x3 = vertices[t3];
|
|
y3 = vertices[t3 + 1];
|
|
|
|
// If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan).
|
|
var merged:Bool = false;
|
|
if (fanBaseIndex == t1) {
|
|
o = polygon.length - 4;
|
|
winding1 = Triangulator.winding(polygon[o], polygon[o + 1], polygon[o + 2], polygon[o + 3], x3, y3);
|
|
winding2 = Triangulator.winding(x3, y3, polygon[0], polygon[1], polygon[2], polygon[3]);
|
|
if (winding1 == lastWinding && winding2 == lastWinding) {
|
|
polygon.push(x3);
|
|
polygon.push(y3);
|
|
polygonIndices.push(t3);
|
|
merged = true;
|
|
}
|
|
}
|
|
|
|
// Otherwise make this triangle the new base.
|
|
if (!merged) {
|
|
if (polygon.length > 0) {
|
|
convexPolygons.push(polygon);
|
|
convexPolygonsIndices.push(polygonIndices);
|
|
} else {
|
|
polygonPool.free(polygon);
|
|
polygonIndicesPool.free(polygonIndices);
|
|
}
|
|
polygon = polygonPool.obtain();
|
|
polygon.resize(0);
|
|
polygon.push(x1);
|
|
polygon.push(y1);
|
|
polygon.push(x2);
|
|
polygon.push(y2);
|
|
polygon.push(x3);
|
|
polygon.push(y3);
|
|
polygonIndices = polygonIndicesPool.obtain();
|
|
polygonIndices.resize(0);
|
|
polygonIndices.push(t1);
|
|
polygonIndices.push(t2);
|
|
polygonIndices.push(t3);
|
|
lastWinding = Triangulator.winding(x1, y1, x2, y2, x3, y3);
|
|
fanBaseIndex = t1;
|
|
}
|
|
|
|
i += 3;
|
|
}
|
|
|
|
if (polygon.length > 0) {
|
|
convexPolygons.push(polygon);
|
|
convexPolygonsIndices.push(polygonIndices);
|
|
}
|
|
|
|
// Go through the list of polygons and try to merge the remaining triangles with the found triangle fans.
|
|
i = 0;
|
|
var n:Int = convexPolygons.length;
|
|
while (i < n) {
|
|
polygonIndices = convexPolygonsIndices[i];
|
|
if (polygonIndices.length == 0) {
|
|
i++;
|
|
continue;
|
|
}
|
|
var firstIndex:Int = polygonIndices[0];
|
|
var lastIndex:Int = polygonIndices[polygonIndices.length - 1];
|
|
|
|
polygon = convexPolygons[i];
|
|
o = polygon.length - 4;
|
|
var prevPrevX:Float = polygon[o], prevPrevY:Float = polygon[o + 1];
|
|
var prevX:Float = polygon[o + 2], prevY:Float = polygon[o + 3];
|
|
var firstX:Float = polygon[0], firstY:Float = polygon[1];
|
|
var secondX:Float = polygon[2], secondY:Float = polygon[3];
|
|
var currWinding:Int = Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY);
|
|
|
|
var ii:Int = 0;
|
|
while (ii < n) {
|
|
if (ii == i) {
|
|
ii++;
|
|
continue;
|
|
}
|
|
var otherIndices:Array<Int> = convexPolygonsIndices[ii];
|
|
if (otherIndices.length != 3) {
|
|
ii++;
|
|
continue;
|
|
}
|
|
var otherFirstIndex:Int = otherIndices[0];
|
|
var otherSecondIndex:Int = otherIndices[1];
|
|
var otherLastIndex:Int = otherIndices[2];
|
|
|
|
var otherPoly:Array<Float> = convexPolygons[ii];
|
|
x3 = otherPoly[otherPoly.length - 2];
|
|
y3 = otherPoly[otherPoly.length - 1];
|
|
|
|
if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) {
|
|
ii++;
|
|
continue;
|
|
}
|
|
winding1 = Triangulator.winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3);
|
|
winding2 = Triangulator.winding(x3, y3, firstX, firstY, secondX, secondY);
|
|
if (winding1 == currWinding && winding2 == currWinding) {
|
|
otherPoly.resize(0);
|
|
otherIndices.resize(0);
|
|
polygon.push(x3);
|
|
polygon.push(y3);
|
|
polygonIndices.push(otherLastIndex);
|
|
prevPrevX = prevX;
|
|
prevPrevY = prevY;
|
|
prevX = x3;
|
|
prevY = y3;
|
|
ii = 0;
|
|
}
|
|
|
|
ii++;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
// Remove empty polygons that resulted from the merge step above.
|
|
i = convexPolygons.length - 1;
|
|
while (i >= 0) {
|
|
polygon = convexPolygons[i];
|
|
if (polygon.length == 0) {
|
|
convexPolygons.splice(i, 1);
|
|
this.polygonPool.free(polygon);
|
|
polygonIndices = convexPolygonsIndices[i];
|
|
convexPolygonsIndices.splice(i, 1);
|
|
this.polygonIndicesPool.free(polygonIndices);
|
|
}
|
|
|
|
i--;
|
|
}
|
|
|
|
return convexPolygons;
|
|
}
|
|
|
|
private static function isConcave(index:Int, vertexCount:Int, vertices:Array<Float>, indices:Array<Int>):Bool {
|
|
var previous:Int = indices[(vertexCount + index - 1) % vertexCount] << 1;
|
|
var current:Int = indices[index] << 1;
|
|
var next:Int = indices[(index + 1) % vertexCount] << 1;
|
|
return !positiveArea(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1], vertices[next], vertices[next + 1]);
|
|
}
|
|
|
|
private static function positiveArea(p1x:Float, p1y:Float, p2x:Float, p2y:Float, p3x:Float, p3y:Float):Bool {
|
|
return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0;
|
|
}
|
|
|
|
private static function winding(p1x:Float, p1y:Float, p2x:Float, p2y:Float, p3x:Float, p3y:Float):Int {
|
|
var px:Float = p2x - p1x, py:Float = p2y - p1y;
|
|
return p3x * py - p3y * px + px * p1y - p1x * py >= 0 ? 1 : -1;
|
|
}
|
|
}
|