mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-05 23:05:01 +08:00
346 lines
12 KiB
TypeScript
346 lines
12 KiB
TypeScript
/******************************************************************************
|
|
* Spine Runtimes Software License v2.5
|
|
*
|
|
* Copyright (c) 2013-2016, Esoteric Software
|
|
* All rights reserved.
|
|
*
|
|
* You are granted a perpetual, non-exclusive, non-sublicensable, and
|
|
* non-transferable license to use, install, execute, and perform the Spine
|
|
* Runtimes software and derivative works solely for personal or internal
|
|
* use. Without the written permission of Esoteric Software (see Section 2 of
|
|
* the Spine Software License Agreement), you may not (a) modify, translate,
|
|
* adapt, or develop new applications using the Spine Runtimes or otherwise
|
|
* create derivative works or improvements of the Spine Runtimes or (b) remove,
|
|
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
|
|
* or other intellectual property or proprietary rights notices on or in the
|
|
* Software, including any copy thereof. Redistributions in binary or source
|
|
* form must include this license and terms.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*****************************************************************************/
|
|
|
|
module spine.webgl {
|
|
export class ShapeRenderer implements Disposable {
|
|
private context: ManagedWebGLRenderingContext;
|
|
private isDrawing = false;
|
|
private mesh: Mesh;
|
|
private shapeType = ShapeType.Filled;
|
|
private color = new Color(1, 1, 1, 1);
|
|
private shader: Shader;
|
|
private vertexIndex = 0;
|
|
private tmp = new Vector2();
|
|
private srcBlend: number;
|
|
private dstBlend: number;
|
|
|
|
constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, maxVertices: number = 10920) {
|
|
if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
|
|
this.context = context instanceof ManagedWebGLRenderingContext? context : new ManagedWebGLRenderingContext(context);
|
|
this.mesh = new Mesh(context, [new Position2Attribute(), new ColorAttribute()], maxVertices, 0);
|
|
this.srcBlend = this.context.gl.SRC_ALPHA;
|
|
this.dstBlend = this.context.gl.ONE_MINUS_SRC_ALPHA;
|
|
}
|
|
|
|
begin (shader: Shader) {
|
|
if (this.isDrawing) throw new Error("ShapeRenderer.begin() has already been called");
|
|
this.shader = shader;
|
|
this.vertexIndex = 0;
|
|
this.isDrawing = true;
|
|
|
|
let gl = this.context.gl;
|
|
gl.enable(gl.BLEND);
|
|
gl.blendFunc(this.srcBlend, this.dstBlend);
|
|
}
|
|
|
|
setBlendMode (srcBlend: number, dstBlend: number) {
|
|
let gl = this.context.gl;
|
|
this.srcBlend = srcBlend;
|
|
this.dstBlend = dstBlend;
|
|
if (this.isDrawing) {
|
|
this.flush();
|
|
gl.blendFunc(this.srcBlend, this.dstBlend);
|
|
}
|
|
}
|
|
|
|
setColor (color: Color) {
|
|
this.color.setFromColor(color);
|
|
}
|
|
|
|
setColorWith (r: number, g: number, b: number, a: number) {
|
|
this.color.set(r, g, b, a);
|
|
}
|
|
|
|
point (x: number, y: number, color: Color = null) {
|
|
this.check(ShapeType.Point, 1);
|
|
if (color === null) color = this.color;
|
|
this.vertex(x, y, color);
|
|
}
|
|
|
|
line (x: number, y: number, x2: number, y2: number, color: Color = null) {
|
|
this.check(ShapeType.Line, 2);
|
|
let vertices = this.mesh.getVertices();
|
|
let idx = this.vertexIndex;
|
|
if (color === null) color = this.color;
|
|
this.vertex(x, y, color);
|
|
this.vertex(x2, y2, color);
|
|
}
|
|
|
|
triangle (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, color: Color = null, color2: Color = null, color3: Color = null) {
|
|
this.check(filled ? ShapeType.Filled : ShapeType.Line, 3);
|
|
let vertices = this.mesh.getVertices();
|
|
let idx = this.vertexIndex;
|
|
if (color === null) color = this.color;
|
|
if (color2 === null) color2 = this.color;
|
|
if (color3 === null) color3 = this.color;
|
|
if (filled) {
|
|
this.vertex(x, y, color);
|
|
this.vertex(x2, y2, color2);
|
|
this.vertex(x3, y3, color3);
|
|
} else {
|
|
this.vertex(x, y, color);
|
|
this.vertex(x2, y2, color2);
|
|
|
|
this.vertex(x2, y2, color);
|
|
this.vertex(x3, y3, color2);
|
|
|
|
this.vertex(x3, y3, color);
|
|
this.vertex(x, y, color2);
|
|
}
|
|
}
|
|
|
|
quad (filled: boolean, x: number, y: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, color: Color = null, color2: Color = null, color3: Color = null, color4: Color = null) {
|
|
this.check(filled ? ShapeType.Filled : ShapeType.Line, 3);
|
|
let vertices = this.mesh.getVertices();
|
|
let idx = this.vertexIndex;
|
|
if (color === null) color = this.color;
|
|
if (color2 === null) color2 = this.color;
|
|
if (color3 === null) color3 = this.color;
|
|
if (color4 === null) color4 = this.color;
|
|
if (filled) {
|
|
this.vertex(x, y, color); this.vertex(x2, y2, color2); this.vertex(x3, y3, color3);
|
|
this.vertex(x3, y3, color3); this.vertex(x4, y4, color4); this.vertex(x, y, color);
|
|
} else {
|
|
this.vertex(x, y, color); this.vertex(x2, y2, color2);
|
|
this.vertex(x2, y2, color2); this.vertex(x3, y3, color3);
|
|
this.vertex(x3, y3, color3); this.vertex(x4, y4, color4);
|
|
this.vertex(x4, y4, color4); this.vertex(x, y, color);
|
|
}
|
|
}
|
|
|
|
rect (filled: boolean, x: number, y: number, width: number, height: number, color: Color = null) {
|
|
this.quad(filled, x, y, x + width, y, x + width, y + height, x, y + height, color, color, color, color);
|
|
}
|
|
|
|
rectLine (filled: boolean, x1: number, y1: number, x2: number, y2: number, width: number, color: Color = null) {
|
|
this.check(filled ? ShapeType.Filled : ShapeType.Line, 8);
|
|
if (color === null) color = this.color;
|
|
let t = this.tmp.set(y2 - y1, x1 - x2);
|
|
t.normalize();
|
|
width *= 0.5;
|
|
let tx = t.x * width;
|
|
let ty = t.y * width;
|
|
if (!filled) {
|
|
this.vertex(x1 + tx, y1 + ty, color);
|
|
this.vertex(x1 - tx, y1 - ty, color);
|
|
this.vertex(x2 + tx, y2 + ty, color);
|
|
this.vertex(x2 - tx, y2 - ty, color);
|
|
|
|
this.vertex(x2 + tx, y2 + ty, color);
|
|
this.vertex(x1 + tx, y1 + ty, color);
|
|
|
|
this.vertex(x2 - tx, y2 - ty, color);
|
|
this.vertex(x1 - tx, y1 - ty, color);
|
|
} else {
|
|
this.vertex(x1 + tx, y1 + ty, color);
|
|
this.vertex(x1 - tx, y1 - ty, color);
|
|
this.vertex(x2 + tx, y2 + ty, color);
|
|
|
|
this.vertex(x2 - tx, y2 - ty, color);
|
|
this.vertex(x2 + tx, y2 + ty, color);
|
|
this.vertex(x1 - tx, y1 - ty, color);
|
|
}
|
|
}
|
|
|
|
x (x: number, y: number, size: number) {
|
|
this.line(x - size, y - size, x + size, y + size);
|
|
this.line(x - size, y + size, x + size, y - size);
|
|
}
|
|
|
|
polygon (polygonVertices: ArrayLike<number>, offset: number, count: number, color: Color = null) {
|
|
if (count < 3) throw new Error("Polygon must contain at least 3 vertices");
|
|
this.check(ShapeType.Line, count * 2);
|
|
if (color === null) color = this.color;
|
|
let vertices = this.mesh.getVertices();
|
|
let idx = this.vertexIndex;
|
|
|
|
offset <<= 1;
|
|
count <<= 1;
|
|
|
|
let firstX = polygonVertices[offset];
|
|
let firstY = polygonVertices[offset + 1];
|
|
let last = offset + count;
|
|
|
|
for (let i = offset, n = offset + count - 2; i < n; i += 2) {
|
|
let x1 = polygonVertices[i];
|
|
let y1 = polygonVertices[i+1];
|
|
|
|
let x2 = 0;
|
|
let y2 = 0;
|
|
|
|
if (i + 2 >= last) {
|
|
x2 = firstX;
|
|
y2 = firstY;
|
|
} else {
|
|
x2 = polygonVertices[i + 2];
|
|
y2 = polygonVertices[i + 3];
|
|
}
|
|
|
|
this.vertex(x1, y1, color);
|
|
this.vertex(x2, y2, color);
|
|
}
|
|
}
|
|
|
|
circle (filled: boolean, x: number, y: number, radius: number, color: Color = null, segments: number = 0) {
|
|
if (segments === 0) segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0);
|
|
if (segments <= 0) throw new Error("segments must be > 0.");
|
|
if (color === null) color = this.color;
|
|
let angle = 2 * MathUtils.PI / segments;
|
|
let cos = Math.cos(angle);
|
|
let sin = Math.sin(angle);
|
|
let cx = radius, cy = 0;
|
|
if (!filled) {
|
|
this.check(ShapeType.Line, segments * 2 + 2);
|
|
for (let i = 0; i < segments; i++) {
|
|
this.vertex(x + cx, y + cy, color);
|
|
let temp = cx;
|
|
cx = cos * cx - sin * cy;
|
|
cy = sin * temp + cos * cy;
|
|
this.vertex(x + cx, y + cy, color);
|
|
}
|
|
// Ensure the last segment is identical to the first.
|
|
this.vertex(x + cx, y + cy, color);
|
|
} else {
|
|
this.check(ShapeType.Filled, segments * 3 + 3);
|
|
segments--;
|
|
for (let i = 0; i < segments; i++) {
|
|
this.vertex(x, y, color);
|
|
this.vertex(x + cx, y + cy, color);
|
|
let temp = cx;
|
|
cx = cos * cx - sin * cy;
|
|
cy = sin * temp + cos * cy;
|
|
this.vertex(x + cx, y + cy, color);
|
|
}
|
|
// Ensure the last segment is identical to the first.
|
|
this.vertex(x, y, color);
|
|
this.vertex(x + cx, y + cy, color);
|
|
}
|
|
|
|
let temp = cx;
|
|
cx = radius;
|
|
cy = 0;
|
|
this.vertex(x + cx, y + cy, color);
|
|
}
|
|
|
|
curve (x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, segments: number, color: Color = null) {
|
|
this.check(ShapeType.Line, segments * 2 + 2);
|
|
if (color === null) color = this.color;
|
|
|
|
// Algorithm from: http://www.antigrain.com/research/bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION
|
|
let subdiv_step = 1 / segments;
|
|
let subdiv_step2 = subdiv_step * subdiv_step;
|
|
let subdiv_step3 = subdiv_step * subdiv_step * subdiv_step;
|
|
|
|
let pre1 = 3 * subdiv_step;
|
|
let pre2 = 3 * subdiv_step2;
|
|
let pre4 = 6 * subdiv_step2;
|
|
let pre5 = 6 * subdiv_step3;
|
|
|
|
let tmp1x = x1 - cx1 * 2 + cx2;
|
|
let tmp1y = y1 - cy1 * 2 + cy2;
|
|
|
|
let tmp2x = (cx1 - cx2) * 3 - x1 + x2;
|
|
let tmp2y = (cy1 - cy2) * 3 - y1 + y2;
|
|
|
|
let fx = x1;
|
|
let fy = y1;
|
|
|
|
let dfx = (cx1 - x1) * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3;
|
|
let dfy = (cy1 - y1) * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3;
|
|
|
|
let ddfx = tmp1x * pre4 + tmp2x * pre5;
|
|
let ddfy = tmp1y * pre4 + tmp2y * pre5;
|
|
|
|
let dddfx = tmp2x * pre5;
|
|
let dddfy = tmp2y * pre5;
|
|
|
|
while (segments-- > 0) {
|
|
this.vertex(fx, fy, color);
|
|
fx += dfx;
|
|
fy += dfy;
|
|
dfx += ddfx;
|
|
dfy += ddfy;
|
|
ddfx += dddfx;
|
|
ddfy += dddfy;
|
|
this.vertex(fx, fy, color);
|
|
}
|
|
this.vertex(fx, fy, color);
|
|
this.vertex(x2, y2, color);
|
|
}
|
|
|
|
private vertex (x: number, y: number, color: Color) {
|
|
let idx = this.vertexIndex;
|
|
let vertices = this.mesh.getVertices();
|
|
vertices[idx++] = x;
|
|
vertices[idx++] = y;
|
|
vertices[idx++] = color.r;
|
|
vertices[idx++] = color.g;
|
|
vertices[idx++] = color.b;
|
|
vertices[idx++] = color.a;
|
|
this.vertexIndex = idx;
|
|
}
|
|
|
|
end () {
|
|
if (!this.isDrawing) throw new Error("ShapeRenderer.begin() has not been called");
|
|
this.flush();
|
|
this.context.gl.disable(this.context.gl.BLEND);
|
|
this.isDrawing = false;
|
|
}
|
|
|
|
private flush () {
|
|
if (this.vertexIndex == 0) return;
|
|
this.mesh.setVerticesLength(this.vertexIndex);
|
|
this.mesh.draw(this.shader, this.shapeType);
|
|
this.vertexIndex = 0;
|
|
}
|
|
|
|
private check(shapeType: ShapeType, numVertices: number) {
|
|
if (!this.isDrawing) throw new Error("ShapeRenderer.begin() has not been called");
|
|
if (this.shapeType == shapeType) {
|
|
if (this.mesh.maxVertices() - this.mesh.numVertices() < numVertices) this.flush();
|
|
else return;
|
|
} else {
|
|
this.flush();
|
|
this.shapeType = shapeType;
|
|
}
|
|
}
|
|
|
|
dispose () {
|
|
this.mesh.dispose();
|
|
}
|
|
}
|
|
|
|
export enum ShapeType {
|
|
Point = 0x0000,
|
|
Line = 0x0001,
|
|
Filled = 0x0004
|
|
}
|
|
}
|