mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
431 lines
13 KiB
Haxe
431 lines
13 KiB
Haxe
/******************************************************************************
|
|
* Spine Runtimes License Agreement
|
|
* Last updated April 5, 2025. Replaces all prior versions.
|
|
*
|
|
* Copyright (c) 2013-2025, 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;
|
|
|
|
import spine.attachments.ClippingAttachment;
|
|
|
|
class SkeletonClipping {
|
|
private var triangulator:Triangulator = new Triangulator();
|
|
private var clippingPolygon:Array<Float> = new Array<Float>();
|
|
private var clipOutput:Array<Float> = new Array<Float>();
|
|
|
|
public var clippedVertices:Array<Float> = new Array<Float>();
|
|
public var clippedUvs:Array<Float> = new Array<Float>();
|
|
public var clippedTriangles:Array<Int> = new Array<Int>();
|
|
|
|
private var scratch:Array<Float> = new Array<Float>();
|
|
|
|
private var clipAttachment:ClippingAttachment;
|
|
private var clippingPolygons:Array<Array<Float>>;
|
|
|
|
public function new() {}
|
|
|
|
public function clipStart(slot:Slot, clip:ClippingAttachment):Int {
|
|
if (clipAttachment != null)
|
|
return 0;
|
|
clipAttachment = clip;
|
|
clippingPolygon.resize(clip.worldVerticesLength);
|
|
clip.computeWorldVertices(slot, 0, clippingPolygon.length, clippingPolygon, 0, 2);
|
|
SkeletonClipping.makeClockwise(clippingPolygon);
|
|
clippingPolygons = triangulator.decompose(clippingPolygon, triangulator.triangulate(clippingPolygon));
|
|
for (polygon in clippingPolygons) {
|
|
SkeletonClipping.makeClockwise(polygon);
|
|
polygon.push(polygon[0]);
|
|
polygon.push(polygon[1]);
|
|
}
|
|
return clippingPolygons.length;
|
|
}
|
|
|
|
public function clipEndWithSlot(slot:Slot):Void {
|
|
if (clipAttachment != null && clipAttachment.endSlot == slot.data)
|
|
clipEnd();
|
|
}
|
|
|
|
public function clipEnd():Void {
|
|
if (clipAttachment == null)
|
|
return;
|
|
clipAttachment = null;
|
|
clippingPolygons = null;
|
|
clippedVertices.resize(0);
|
|
clippedUvs.resize(0);
|
|
clippedTriangles.resize(0);
|
|
clippingPolygon.resize(0);
|
|
clipOutput.resize(0);
|
|
}
|
|
|
|
public function isClipping():Bool {
|
|
return clipAttachment != null;
|
|
}
|
|
|
|
private function clipTrianglesNoRender(vertices:Array<Float>, triangles:Array<Int>, trianglesLength:Float):Void {
|
|
var polygonsCount:Int = clippingPolygons.length;
|
|
var index:Int = 0;
|
|
clippedVertices.resize(0);
|
|
clippedTriangles.resize(0);
|
|
var i:Int = 0;
|
|
while (i < trianglesLength) {
|
|
var vertexOffset:Int = triangles[i] << 1;
|
|
var x1:Float = vertices[vertexOffset],
|
|
y1:Float = vertices[vertexOffset + 1];
|
|
|
|
vertexOffset = triangles[i + 1] << 1;
|
|
var x2:Float = vertices[vertexOffset],
|
|
y2:Float = vertices[vertexOffset + 1];
|
|
|
|
vertexOffset = triangles[i + 2] << 1;
|
|
var x3:Float = vertices[vertexOffset],
|
|
y3:Float = vertices[vertexOffset + 1];
|
|
|
|
for (p in 0...polygonsCount) {
|
|
var s:Int = clippedVertices.length;
|
|
var clippedVerticesItems:Array<Float>;
|
|
var clippedTrianglesItems:Array<Int>;
|
|
if (this.clip(x1, y1, x2, y2, x3, y3, clippingPolygons[p], clipOutput)) {
|
|
var clipOutputLength:Int = clipOutput.length;
|
|
if (clipOutputLength == 0)
|
|
continue;
|
|
|
|
var clipOutputCount:Int = clipOutputLength >> 1;
|
|
var clipOutputItems:Array<Float> = clipOutput;
|
|
clippedVerticesItems = clippedVertices;
|
|
clippedVerticesItems.resize(s + clipOutputLength);
|
|
var ii:Int = 0;
|
|
while (ii < clipOutputLength) {
|
|
var x:Float = clipOutputItems[ii],
|
|
y:Float = clipOutputItems[ii + 1];
|
|
clippedVerticesItems[s] = x;
|
|
clippedVerticesItems[s + 1] = y;
|
|
s += 2;
|
|
ii += 2;
|
|
}
|
|
|
|
s = clippedTriangles.length;
|
|
clippedTrianglesItems = clippedTriangles;
|
|
clippedTrianglesItems.resize(s + 3 * (clipOutputCount - 2));
|
|
clipOutputCount--;
|
|
for (ii in 1...clipOutputCount) {
|
|
clippedTrianglesItems[s] = index;
|
|
clippedTrianglesItems[s + 1] = (index + ii);
|
|
clippedTrianglesItems[s + 2] = (index + ii + 1);
|
|
s += 3;
|
|
}
|
|
index += clipOutputCount + 1;
|
|
} else {
|
|
clippedVerticesItems = clippedVertices;
|
|
clippedVerticesItems.resize(s + 3 * 2);
|
|
clippedVerticesItems[s] = x1;
|
|
clippedVerticesItems[s + 1] = y1;
|
|
clippedVerticesItems[s + 2] = x2;
|
|
clippedVerticesItems[s + 3] = y2;
|
|
clippedVerticesItems[s + 4] = x3;
|
|
clippedVerticesItems[s + 5] = y3;
|
|
|
|
s = clippedTriangles.length;
|
|
clippedTrianglesItems = clippedTriangles;
|
|
clippedTrianglesItems.resize(s + 3);
|
|
clippedTrianglesItems[s] = index;
|
|
clippedTrianglesItems[s + 1] = (index + 1);
|
|
clippedTrianglesItems[s + 2] = (index + 2);
|
|
index += 3;
|
|
break;
|
|
}
|
|
}
|
|
|
|
i += 3;
|
|
}
|
|
}
|
|
|
|
public function clipTriangles(vertices:Array<Float>, triangles:Array<Int>, trianglesLength:Float, uvs:Array<Float> = null):Void {
|
|
if (uvs == null) {
|
|
clipTrianglesNoRender(vertices, triangles, trianglesLength);
|
|
return;
|
|
}
|
|
|
|
var polygonsCount:Int = clippingPolygons.length;
|
|
var index:Int = 0;
|
|
clippedVertices.resize(0);
|
|
clippedUvs.resize(0);
|
|
clippedTriangles.resize(0);
|
|
var i:Int = 0;
|
|
while (i < trianglesLength) {
|
|
var vertexOffset:Int = triangles[i] << 1;
|
|
var x1:Float = vertices[vertexOffset],
|
|
y1:Float = vertices[vertexOffset + 1];
|
|
var u1:Float = uvs[vertexOffset], v1:Float = uvs[vertexOffset + 1];
|
|
|
|
vertexOffset = triangles[i + 1] << 1;
|
|
var x2:Float = vertices[vertexOffset],
|
|
y2:Float = vertices[vertexOffset + 1];
|
|
var u2:Float = uvs[vertexOffset], v2:Float = uvs[vertexOffset + 1];
|
|
|
|
vertexOffset = triangles[i + 2] << 1;
|
|
var x3:Float = vertices[vertexOffset],
|
|
y3:Float = vertices[vertexOffset + 1];
|
|
var u3:Float = uvs[vertexOffset], v3:Float = uvs[vertexOffset + 1];
|
|
|
|
for (p in 0...polygonsCount) {
|
|
var s:Int = clippedVertices.length;
|
|
var clippedVerticesItems:Array<Float>;
|
|
var clippedUvsItems:Array<Float>;
|
|
var clippedTrianglesItems:Array<Int>;
|
|
if (this.clip(x1, y1, x2, y2, x3, y3, clippingPolygons[p], clipOutput)) {
|
|
var clipOutputLength:Int = clipOutput.length;
|
|
if (clipOutputLength == 0)
|
|
continue;
|
|
var d0:Float = y2 - y3,
|
|
d1:Float = x3 - x2,
|
|
d2:Float = x1 - x3,
|
|
d4:Float = y3 - y1;
|
|
var d:Float = 1 / (d0 * d2 + d1 * (y1 - y3));
|
|
|
|
var clipOutputCount:Int = clipOutputLength >> 1;
|
|
var clipOutputItems:Array<Float> = clipOutput;
|
|
clippedVerticesItems = clippedVertices;
|
|
clippedVerticesItems.resize(s + clipOutputLength);
|
|
clippedUvsItems = clippedUvs;
|
|
clippedUvsItems.resize(s + clipOutputLength);
|
|
|
|
var ii:Int = 0;
|
|
while (ii < clipOutputLength) {
|
|
var x:Float = clipOutputItems[ii],
|
|
y:Float = clipOutputItems[ii + 1];
|
|
clippedVerticesItems[s] = x;
|
|
clippedVerticesItems[s + 1] = y;
|
|
var c0:Float = x - x3, c1:Float = y - y3;
|
|
var a:Float = (d0 * c0 + d1 * c1) * d;
|
|
var b:Float = (d4 * c0 + d2 * c1) * d;
|
|
var c:Float = 1 - a - b;
|
|
clippedUvsItems[s] = u1 * a + u2 * b + u3 * c;
|
|
clippedUvsItems[s + 1] = v1 * a + v2 * b + v3 * c;
|
|
s += 2;
|
|
|
|
ii += 2;
|
|
}
|
|
|
|
s = clippedTriangles.length;
|
|
clippedTrianglesItems = clippedTriangles;
|
|
clippedTrianglesItems.resize(s + 3 * (clipOutputCount - 2));
|
|
clipOutputCount--;
|
|
for (ii in 1...clipOutputCount) {
|
|
clippedTrianglesItems[s] = index;
|
|
clippedTrianglesItems[s + 1] = (index + ii);
|
|
clippedTrianglesItems[s + 2] = (index + ii + 1);
|
|
s += 3;
|
|
}
|
|
index += clipOutputCount + 1;
|
|
} else {
|
|
clippedVerticesItems = clippedVertices;
|
|
clippedVerticesItems.resize(s + 3 * 2);
|
|
clippedVerticesItems[s] = x1;
|
|
clippedVerticesItems[s + 1] = y1;
|
|
clippedVerticesItems[s + 2] = x2;
|
|
clippedVerticesItems[s + 3] = y2;
|
|
clippedVerticesItems[s + 4] = x3;
|
|
clippedVerticesItems[s + 5] = y3;
|
|
|
|
clippedUvsItems = clippedUvs;
|
|
clippedUvsItems.resize(s + 3 * 2);
|
|
clippedUvsItems[s] = u1;
|
|
clippedUvsItems[s + 1] = v1;
|
|
clippedUvsItems[s + 2] = u2;
|
|
clippedUvsItems[s + 3] = v2;
|
|
clippedUvsItems[s + 4] = u3;
|
|
clippedUvsItems[s + 5] = v3;
|
|
|
|
s = clippedTriangles.length;
|
|
clippedTrianglesItems = clippedTriangles;
|
|
clippedTrianglesItems.resize(s + 3);
|
|
clippedTrianglesItems[s] = index;
|
|
clippedTrianglesItems[s + 1] = (index + 1);
|
|
clippedTrianglesItems[s + 2] = (index + 2);
|
|
index += 3;
|
|
break;
|
|
}
|
|
}
|
|
|
|
i += 3;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping
|
|
* area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list.
|
|
*/
|
|
public function clip(x1:Float, y1:Float, x2:Float, y2:Float, x3:Float, y3:Float, clippingArea:Array<Float>, output:Array<Float>):Bool {
|
|
var originalOutput:Array<Float> = output;
|
|
var clipped:Bool = false;
|
|
|
|
// Avoid copy at the end.
|
|
var input:Array<Float> = null;
|
|
if (clippingArea.length % 4 >= 2) {
|
|
input = output;
|
|
output = scratch;
|
|
} else {
|
|
input = scratch;
|
|
}
|
|
|
|
input.resize(0);
|
|
input.push(x1);
|
|
input.push(y1);
|
|
input.push(x2);
|
|
input.push(y2);
|
|
input.push(x3);
|
|
input.push(y3);
|
|
input.push(x1);
|
|
input.push(y1);
|
|
output.resize(0);
|
|
|
|
var clippingVerticesLast:Int = clippingArea.length - 4;
|
|
var clippingVertices:Array<Float> = clippingArea;
|
|
var ix:Float, iy:Float, t:Float;
|
|
var i:Int = 0;
|
|
var n:Int = 0;
|
|
while (true) {
|
|
var edgeX:Float = clippingVertices[i],
|
|
edgeY:Float = clippingVertices[i + 1];
|
|
var ex:Float = edgeX - clippingVertices[i + 2],
|
|
ey:Float = edgeY - clippingVertices[i + 3];
|
|
|
|
var outputStart:Int = output.length;
|
|
var inputVertices:Array<Float> = input;
|
|
var ii:Int = 0;
|
|
var nn:Int = input.length - 2;
|
|
while (ii < nn) {
|
|
var inputX:Float = inputVertices[ii],
|
|
inputY:Float = inputVertices[ii + 1];
|
|
ii += 2;
|
|
var inputX2:Float = inputVertices[ii],
|
|
inputY2:Float = inputVertices[ii + 1];
|
|
var s2:Bool = ey * (edgeX - inputX2) > ex * (edgeY - inputY2);
|
|
var s1:Float = ey * (edgeX - inputX) - ex * (edgeY - inputY);
|
|
if (s1 > 0) {
|
|
if (s2) {
|
|
// v1 inside, v2 inside
|
|
output.push(inputX2);
|
|
output.push(inputY2);
|
|
continue;
|
|
}
|
|
// v1 inside, v2 outside
|
|
ix = inputX2 - inputX;
|
|
iy = inputY2 - inputY;
|
|
t = s1 / (ix * ey - iy * ex);
|
|
if (t >= 0 && t <= 1) {
|
|
output.push(inputX + ix * t);
|
|
output.push(inputY + iy * t);
|
|
} else {
|
|
output.push(inputX2);
|
|
output.push(inputY2);
|
|
continue;
|
|
}
|
|
} else if (s2) {
|
|
// v1 outside, v2 inside
|
|
ix = inputX2 - inputX;
|
|
iy = inputY2 - inputY;
|
|
t = s1 / (ix * ey - iy * ex);
|
|
if (t >= 0 && t <= 1) {
|
|
output.push(inputX + ix * t);
|
|
output.push(inputY + iy * t);
|
|
output.push(inputX2);
|
|
output.push(inputY2);
|
|
} else {
|
|
output.push(inputX2);
|
|
output.push(inputY2);
|
|
continue;
|
|
}
|
|
}
|
|
clipped = true;
|
|
}
|
|
|
|
if (outputStart == output.length) {
|
|
// All edges outside.
|
|
originalOutput.resize(0);
|
|
return true;
|
|
}
|
|
|
|
output.push(output[0]);
|
|
output.push(output[1]);
|
|
|
|
if (i == clippingVerticesLast)
|
|
break;
|
|
var temp:Array<Float> = output;
|
|
output = input;
|
|
output.resize(0);
|
|
input = temp;
|
|
|
|
i += 2;
|
|
}
|
|
|
|
if (originalOutput != output) {
|
|
originalOutput.resize(0);
|
|
n = output.length - 2;
|
|
for (i in 0...n) {
|
|
originalOutput[i] = output[i];
|
|
}
|
|
} else {
|
|
originalOutput.resize(originalOutput.length - 2);
|
|
}
|
|
|
|
return clipped;
|
|
}
|
|
|
|
public static function makeClockwise(polygon:Array<Float>):Void {
|
|
var vertices:Array<Float> = polygon;
|
|
var verticeslength:Int = polygon.length;
|
|
|
|
var area:Float = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1];
|
|
var p1x:Float = 0, p1y:Float = 0, p2x:Float = 0, p2y:Float = 0;
|
|
var i:Int = 0;
|
|
var n:Int = verticeslength - 3;
|
|
while (i < n) {
|
|
p1x = vertices[i];
|
|
p1y = vertices[i + 1];
|
|
p2x = vertices[i + 2];
|
|
p2y = vertices[i + 3];
|
|
area += p1x * p2y - p2x * p1y;
|
|
i += 2;
|
|
}
|
|
if (area < 0)
|
|
return;
|
|
|
|
i = 0;
|
|
n = verticeslength >> 1;
|
|
var lastX:Int = verticeslength - 2;
|
|
while (i < n) {
|
|
var x:Float = vertices[i], y:Float = vertices[i + 1];
|
|
var other:Int = lastX - i;
|
|
vertices[i] = vertices[other];
|
|
vertices[i + 1] = vertices[other + 1];
|
|
vertices[other] = x;
|
|
vertices[other + 1] = y;
|
|
i += 2;
|
|
}
|
|
}
|
|
}
|