网站页面
No files matched your search
@@ -0,0 +1,26 @@
|
||||
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:
|
||||
https://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.
|
||||
@@ -0,0 +1,12 @@
|
||||
// 官方 Raptor 示例(Spine 4.3.75-beta 导出)的 JS 内嵌副本。
|
||||
// 用途:以 file:// 直接打开页面时,浏览器不允许 fetch 本地文本,本地图片也会污染画布,
|
||||
// 这里预先内嵌 JSON、Atlas 与贴图的 data URL,保证离线与 file:// 场景下同样可以渲染。
|
||||
// 数据来源:Esoteric Software spine-runtimes 仓库 examples/raptor/export
|
||||
// 固定提交:ba17cf88b198c6531c6c51d8d4e1e4b3c3ab47dd(Spine Runtimes License,见 LICENSE.txt)。
|
||||
window.SPINE_DEMO_DATA = {
|
||||
name: "Raptor",
|
||||
version: "4.3.75-beta",
|
||||
data: {"skeleton":{"hash":"OKE722Fi+rc","spine":"4.3.75-beta","x":-809.163,"y":-73.5388,"width":1287.646,"height":1039.443,"images":"./images/","audio":null},"bones":[{"name":"root"},{"name":"hip","parent":"root","rotation":8.95755,"x":-122.52013,"y":392.648,"color":"fbff00ff"},{"name":"torso1","parent":"hip","length":126.26,"rotation":-4.98,"x":30.04,"y":-0.4,"color":"eaff00ff"},{"name":"saddle","parent":"torso1","length":50.92,"rotation":91.8,"x":25.310295,"y":70.64713,"color":"ff7300ff"},{"name":"spineboy-hip","parent":"saddle","length":0.53,"rotation":90.02,"x":81.88,"y":2.69,"color":"e8ff00ff"},{"name":"spineboy-torso","parent":"spineboy-hip","length":122.45,"rotation":-75.86,"x":1.05,"y":-2.11,"color":"e8ff00ff"},{"name":"torso2","parent":"torso1","length":121.2,"rotation":39.85,"x":126.26,"y":-0.38,"color":"e9ff00ff"},{"name":"neck","parent":"torso2","length":70.6,"rotation":41.38,"x":121.2,"y":0.35,"color":"eaff00ff"},{"name":"head","parent":"neck","length":105.51,"rotation":9.83,"x":70.6,"y":0.04,"color":"eaff00ff"},{"name":"horn-back","parent":"head","length":73.78,"rotation":44.32,"x":104.76395,"y":-242.01355,"color":"e07800ff"},{"name":"spineboy-back-arm-target","parent":"horn-back","rotation":-133.55,"x":232.68,"y":245.85,"inherit":"noScale","color":"ff3f00ff"},{"name":"back-arm","parent":"spineboy-torso","length":67.21,"rotation":-120.9,"x":96.33,"y":-38.47,"color":"e07800ff"},{"name":"back-bracer","parent":"back-arm","length":43.69,"rotation":17.48,"x":67.22,"y":-0.32,"color":"e07800ff"},{"name":"back-arm1","parent":"torso2","length":109.56,"rotation":-124.72,"x":83.67854,"y":-83.23534,"color":"e07800ff"},{"name":"back-arm2","parent":"back-arm1","length":85.8,"rotation":123.56,"x":109.57,"y":-0.01,"color":"e07800ff"},{"name":"back-foot-target","parent":"root","x":33.44,"y":30.82,"color":"ff3f00ff","icon":"ik"},{"name":"back-leg-target","parent":"back-foot-target","x":-127.51,"y":75.99,"color":"ff3f00ff","icon":"ik"},{"name":"back-leg1","parent":"hip","length":226.28,"rotation":-54.76,"x":55.19,"y":-71.25,"color":"e07800ff"},{"name":"back-leg2","parent":"back-leg1","length":172.59,"rotation":-92.25,"x":226.32,"y":0.23,"color":"e07800ff"},{"name":"back-leg3","parent":"back-leg2","length":103.05,"rotation":82.82,"x":172.32,"y":2.21,"color":"e07800ff"},{"name":"back-foot1","parent":"back-leg3","length":84.51,"rotation":75.43,"x":102.38,"y":-0.03,"color":"e07800ff"},{"name":"back-foot2","parent":"back-foot1","length":102.31,"rotation":-6.14,"x":84.5,"y":-0.35,"inherit":"noRotationOrReflection","color":"e07800ff"},{"name":"back-hand","parent":"back-arm2","length":45.81,"rotation":-76.28,"x":85.8,"y":0.11,"color":"e07800ff"},{"name":"back-hand2","parent":"spineboy-back-arm-target","length":41.98,"rotation":27.77738,"x":-0.02857208,"y":0.05377197,"inherit":"noRotationOrReflection","color":"e07800ff"},{"name":"spineboy-back-foot-target","parent":"saddle","x":-30.44,"y":-100.08,"color":"ff3f00ff"},{"name":"back-thigh","parent":"spineboy-hip","length":71.16,"rotation":160.75,"x":-9.57,"y":2.32,"color":"e07800ff"},{"name":"back-knee","parent":"back-thigh","length":97.17,"rotation":-54.98,"x":71.16,"y":-0.28,"color":"e07800ff"},{"name":"neck2","parent":"spineboy-torso","length":32.05,"rotation":-45.23,"x":113.44,"y":-15.22,"color":"e8ff00ff"},{"name":"head2","parent":"neck2","length":167.1939,"rotation":11.66,"x":25.68,"y":-0.77,"inherit":"noScale","color":"e7ff00ff"},{"name":"bone","parent":"head2","length":39.91506,"rotation":-35.22991,"x":166.09326,"y":-79.27026,"color":"e7ff00ff"},{"name":"bone2","parent":"bone","length":47.4192,"rotation":51.7975,"x":39.9151,"color":"e7ff00ff"},{"name":"bone3","parent":"head2","length":45.40962,"rotation":-12.343472,"x":179.80286,"y":-20.90898,"color":"e7ff00ff"},{"name":"bone4","parent":"bone3","length":43.31327,"rotation":42.0083,"x":44.63544,"y":0.303009,"color":"e7ff00ff"},{"name":"bone5","parent":"bone4","length":44.87501,"rotation":48.80142,"x":43.31311,"color":"e7ff00ff"},{"name":"horn-front","parent":"head","length":87.48,"rotation":49.36,"x":87.97336,"y":-235.14723,"color":"15ff00ff"},{"name":"spineboy-front-arm-target","parent":"horn-front","rotation":-138.6,"x":294.58,"y":234.18,"color":"ff3f00ff"},{"name":"front-arm","parent":"spineboy-torso","length":74.52,"rotation":-118.17,"x":101.38,"y":9.79,"color":"14ff00ff"},{"name":"front-bracer","parent":"front-arm","length":39.85,"rotation":20.31,"x":74.52,"y":-0.42,"color":"14ff00ff"},{"name":"front-arm1","parent":"torso2","length":109.99,"rotation":224.54,"x":73.00035,"y":-72.46397,"color":"15ff00ff"},{"name":"front-arm2","parent":"front-arm1","length":86.33,"rotation":105.24,"x":109.99,"y":0.2,"color":"15ff00ff"},{"name":"front-foot-target","parent":"root","rotation":-6.956577,"x":-45.8,"y":-28.67,"color":"ff3f00ff","icon":"ik"},{"name":"front-leg-target","parent":"front-foot-target","x":-106.06,"y":115.58,"color":"ff3f00ff","icon":"ik"},{"name":"front-leg1","parent":"hip","length":251.75,"rotation":-51.51,Line truncated
|
||||
atlas: "raptor.png\n\tsize: 1024, 512\n\tfilter: Linear, Linear\n\tscale: 0.5\nback-arm\n\tbounds: 206, 49, 46, 25\nback-bracer\n\tbounds: 498, 42, 39, 28\nback-hand\n\tbounds: 400, 55, 36, 34\nback-knee\n\tbounds: 498, 72, 49, 67\nback-thigh\n\tbounds: 254, 40, 39, 24\neyes-open\n\tbounds: 232, 328, 47, 45\nfront-arm\n\tbounds: 350, 63, 48, 26\nfront-bracer\n\tbounds: 163, 56, 41, 29\nfront-hand\n\tbounds: 956, 231, 41, 38\nfront-open-hand\n\tbounds: 770, 329, 43, 44\nfront-thigh\n\tbounds: 897, 240, 57, 29\ngun\n\tbounds: 915, 106, 107, 103\ngun-nohand\n\tbounds: 916, 271, 105, 102\nhead\n\tbounds: 626, 81, 136, 149\nlower-leg\n\tbounds: 551, 42, 73, 98\nmouth-grind\n\tbounds: 2, 64, 47, 30\nmouth-smile\n\tbounds: 841, 127, 47, 30\nneck\n\tbounds: 281, 352, 18, 21\nraptor-back-arm\n\tbounds: 80, 11, 82, 86\nraptor-body\n\tbounds: 2, 140, 632, 233\nraptor-front-arm\n\tbounds: 911, 2, 81, 102\nraptor-front-leg\n\tbounds: 524, 182, 191, 257\n\trotate: 90\nraptor-hindleg-back\n\tbounds: 745, 158, 169, 215\n\trotate: 180\nraptor-horn\n\tbounds: 2, 293, 182, 80\nraptor-horn-back\n\tbounds: 174, 123, 176, 77\nraptor-jaw\n\tbounds: 772, 35, 126, 138\n\trotate: 270\nraptor-jaw-tooth\n\tbounds: 686, 242, 37, 48\nraptor-mouth-inside\n\tbounds: 755, 39, 36, 41\nraptor-saddle-strap-back\n\tbounds: 626, 26, 54, 74\nraptor-saddle-strap-front\n\tbounds: 2, 96, 57, 95\nraptor-saddle-w-shadow\n\tbounds: 2, 88, 162, 171\n\trotate: 90\nraptor-tail-shadow\n\tbounds: 159, 58, 189, 63\nraptor-tongue\n\tbounds: 685, 33, 86, 64\nstirrup-back\n\tbounds: 783, 204, 44, 35\nstirrup-front\n\tbounds: 185, 323, 45, 50\nstirrup-strap\n\tbounds: 636, 217, 49, 46\ntorso\n\tbounds: 442, 48, 54, 91\nvisor\n\tbounds: 356, 91, 131, 84\n\trotate: 90\n",
|
||||
texture: "data:image/png;base64,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 truncated
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
raptor.png
|
||||
size: 1024, 512
|
||||
filter: Linear, Linear
|
||||
scale: 0.5
|
||||
back-arm
|
||||
bounds: 206, 49, 46, 25
|
||||
back-bracer
|
||||
bounds: 498, 42, 39, 28
|
||||
back-hand
|
||||
bounds: 400, 55, 36, 34
|
||||
back-knee
|
||||
bounds: 498, 72, 49, 67
|
||||
back-thigh
|
||||
bounds: 254, 40, 39, 24
|
||||
eyes-open
|
||||
bounds: 232, 328, 47, 45
|
||||
front-arm
|
||||
bounds: 350, 63, 48, 26
|
||||
front-bracer
|
||||
bounds: 163, 56, 41, 29
|
||||
front-hand
|
||||
bounds: 956, 231, 41, 38
|
||||
front-open-hand
|
||||
bounds: 770, 329, 43, 44
|
||||
front-thigh
|
||||
bounds: 897, 240, 57, 29
|
||||
gun
|
||||
bounds: 915, 106, 107, 103
|
||||
gun-nohand
|
||||
bounds: 916, 271, 105, 102
|
||||
head
|
||||
bounds: 626, 81, 136, 149
|
||||
lower-leg
|
||||
bounds: 551, 42, 73, 98
|
||||
mouth-grind
|
||||
bounds: 2, 64, 47, 30
|
||||
mouth-smile
|
||||
bounds: 841, 127, 47, 30
|
||||
neck
|
||||
bounds: 281, 352, 18, 21
|
||||
raptor-back-arm
|
||||
bounds: 80, 11, 82, 86
|
||||
raptor-body
|
||||
bounds: 2, 140, 632, 233
|
||||
raptor-front-arm
|
||||
bounds: 911, 2, 81, 102
|
||||
raptor-front-leg
|
||||
bounds: 524, 182, 191, 257
|
||||
rotate: 90
|
||||
raptor-hindleg-back
|
||||
bounds: 745, 158, 169, 215
|
||||
rotate: 180
|
||||
raptor-horn
|
||||
bounds: 2, 293, 182, 80
|
||||
raptor-horn-back
|
||||
bounds: 174, 123, 176, 77
|
||||
raptor-jaw
|
||||
bounds: 772, 35, 126, 138
|
||||
rotate: 270
|
||||
raptor-jaw-tooth
|
||||
bounds: 686, 242, 37, 48
|
||||
raptor-mouth-inside
|
||||
bounds: 755, 39, 36, 41
|
||||
raptor-saddle-strap-back
|
||||
bounds: 626, 26, 54, 74
|
||||
raptor-saddle-strap-front
|
||||
bounds: 2, 96, 57, 95
|
||||
raptor-saddle-w-shadow
|
||||
bounds: 2, 88, 162, 171
|
||||
rotate: 90
|
||||
raptor-tail-shadow
|
||||
bounds: 159, 58, 189, 63
|
||||
raptor-tongue
|
||||
bounds: 685, 33, 86, 64
|
||||
stirrup-back
|
||||
bounds: 783, 204, 44, 35
|
||||
stirrup-front
|
||||
bounds: 185, 323, 45, 50
|
||||
stirrup-strap
|
||||
bounds: 636, 217, 49, 46
|
||||
torso
|
||||
bounds: 442, 48, 54, 91
|
||||
visor
|
||||
bounds: 356, 91, 131, 84
|
||||
rotate: 90
|
||||
|
After Width: | Height: | Size: 408 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
const patterns = new WeakMap();
|
||||
|
||||
// Called with the same world-to-canvas transform used to draw the skeleton.
|
||||
function drawCheckerboard(ctx, width, height) {
|
||||
let pattern = patterns.get(ctx);
|
||||
if (!pattern) {
|
||||
const tile = ctx.canvas.ownerDocument.createElement('canvas');
|
||||
tile.width = tile.height = 100;
|
||||
const paint = tile.getContext('2d');
|
||||
paint.fillStyle = '#505052'; paint.fillRect(0, 0, 100, 100);
|
||||
paint.fillStyle = '#5a5a5c'; paint.fillRect(0, 0, 50, 50); paint.fillRect(50, 50, 50, 50);
|
||||
pattern = ctx.createPattern(tile, 'repeat'); patterns.set(ctx, pattern);
|
||||
}
|
||||
const m = ctx.getTransform().inverse();
|
||||
const corners = [[0,0], [width,0], [width,height], [0,height]].map(([x,y]) => [m.a*x+m.c*y+m.e, m.b*x+m.d*y+m.f]);
|
||||
const xs = corners.map(p => p[0]), ys = corners.map(p => p[1]);
|
||||
const x = Math.min(...xs), y = Math.min(...ys);
|
||||
if (!Number.isFinite(x + y)) return;
|
||||
ctx.save();
|
||||
try { ctx.imageSmoothingEnabled = false; ctx.fillStyle = pattern; ctx.fillRect(x, y, Math.max(...xs)-x, Math.max(...ys)-y); }
|
||||
finally { ctx.restore(); }
|
||||
}
|
||||
|
||||
module.exports = { drawCheckerboard };
|
||||
@@ -0,0 +1,243 @@
|
||||
'use strict';
|
||||
|
||||
// The official spine-canvas SkeletonRenderer does not process clipping
|
||||
// attachments. Keep its triangle rasterizer, but feed it geometry produced by
|
||||
// SkeletonClipping so the viewer matches Spine's draw order and clipping rules.
|
||||
|
||||
const QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
|
||||
const BLEND_MODES = ['source-over', 'lighter', 'multiply', 'screen'];
|
||||
const unpremultipliedImages = new WeakMap();
|
||||
|
||||
function rendererConstructor(spine) {
|
||||
return (spine.canvas && spine.canvas.SkeletonRenderer) || spine.SkeletonRenderer;
|
||||
}
|
||||
|
||||
function imageFromAttachment(attachment) {
|
||||
const region = attachment && attachment.region;
|
||||
// 4.0 stores the texture on the atlas page, while the other bundled
|
||||
// runtimes expose it directly on the attachment region.
|
||||
const renderRegion = region && region.renderObject;
|
||||
const texture = region && (region.texture
|
||||
|| (region.page && region.page.texture)
|
||||
|| (renderRegion && renderRegion.texture)
|
||||
|| (renderRegion && renderRegion.page && renderRegion.page.texture));
|
||||
return texture && typeof texture.getImage === 'function' ? texture.getImage() : null;
|
||||
}
|
||||
|
||||
// Canvas 2D always applies source-alpha blending. A Spine texture exported
|
||||
// with PMA already has RGB multiplied by alpha, so drawing it directly would
|
||||
// multiply translucent edge pixels a second time. Convert it once to straight
|
||||
// alpha and cache the result; Canvas can then blend it correctly.
|
||||
function imageForCanvas(image, context, premultipliedAlpha) {
|
||||
if (!premultipliedAlpha || !image || (typeof image !== 'object' && typeof image !== 'function')) return image;
|
||||
const cached = unpremultipliedImages.get(image);
|
||||
if (cached) return cached;
|
||||
try {
|
||||
const ownerDocument = context.canvas && context.canvas.ownerDocument;
|
||||
const doc = ownerDocument || (typeof document !== 'undefined' ? document : null);
|
||||
if (!image.width || !image.height) return image;
|
||||
let canvas;
|
||||
if (doc) canvas = doc.createElement('canvas');
|
||||
else {
|
||||
try { canvas = require('@napi-rs/canvas').createCanvas(image.width, image.height); }
|
||||
catch (e) { return image; }
|
||||
}
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
ctx.drawImage(image, 0, 0);
|
||||
const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const data = pixels.data;
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
const alpha = data[i + 3];
|
||||
if (alpha > 0 && alpha < 255) {
|
||||
const factor = 255 / alpha;
|
||||
data[i] = Math.min(255, Math.round(data[i] * factor));
|
||||
data[i + 1] = Math.min(255, Math.round(data[i + 1] * factor));
|
||||
data[i + 2] = Math.min(255, Math.round(data[i + 2] * factor));
|
||||
}
|
||||
}
|
||||
ctx.putImageData(pixels, 0, 0);
|
||||
unpremultipliedImages.set(image, canvas);
|
||||
return canvas;
|
||||
} catch (e) {
|
||||
// If pixel access is unavailable, retain the original texture so preview
|
||||
// remains usable instead of failing the whole frame.
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
function unpackVertices(vertices, count) {
|
||||
const positions = new Array(count * 2);
|
||||
const uvs = new Array(count * 2);
|
||||
for (let i = 0, p = 0; i < count; i++, p += 2) {
|
||||
const source = i * 8;
|
||||
positions[p] = vertices[source];
|
||||
positions[p + 1] = vertices[source + 1];
|
||||
uvs[p] = vertices[source + 6];
|
||||
uvs[p + 1] = vertices[source + 7];
|
||||
}
|
||||
return { positions, uvs };
|
||||
}
|
||||
|
||||
function drawInterleaved(base, image, vertices, triangles, darkColor) {
|
||||
for (let i = 0; i < triangles.length; i += 3) {
|
||||
const a = triangles[i] * 8;
|
||||
const b = triangles[i + 1] * 8;
|
||||
const c = triangles[i + 2] * 8;
|
||||
base.drawTriangle(image,
|
||||
vertices[a], vertices[a + 1], vertices[a + 6], vertices[a + 7],
|
||||
vertices[b], vertices[b + 1], vertices[b + 6], vertices[b + 7],
|
||||
vertices[c], vertices[c + 1], vertices[c + 6], vertices[c + 7],
|
||||
[...vertices.slice(a+2,a+6), ...vertices.slice(b+2,b+6), ...vertices.slice(c+2,c+6)],
|
||||
darkColor ? Array.from({length:3}, () => [darkColor.r,darkColor.g,darkColor.b,0]).flat() : undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function createLegacyRenderer(spine, context) {
|
||||
const Renderer = rendererConstructor(spine);
|
||||
if (!Renderer || !spine.SkeletonClipping) throw new Error('Spine Canvas 渲染器不完整');
|
||||
const base = new Renderer(context);
|
||||
base.triangleRendering = true;
|
||||
base.premultipliedAlpha = false;
|
||||
const clipper = new spine.SkeletonClipping();
|
||||
const dark = new spine.Color(0, 0, 0, 0);
|
||||
|
||||
base.draw = function drawWithClipping(skeleton) {
|
||||
const composite = context.globalCompositeOperation;
|
||||
const drawOrder = skeleton.drawOrder;
|
||||
const pma = !!base.premultipliedAlpha;
|
||||
try {
|
||||
for (let i = 0; i < drawOrder.length; i++) {
|
||||
const slot = drawOrder[i];
|
||||
if (slot.bone && slot.bone.active === false) {
|
||||
clipper.clipEndWithSlot(slot);
|
||||
continue;
|
||||
}
|
||||
const attachment = slot.getAttachment();
|
||||
if (attachment instanceof spine.ClippingAttachment) {
|
||||
clipper.clipStart(slot, attachment);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!attachment) continue;
|
||||
let vertices;
|
||||
let triangles;
|
||||
let vertexCount;
|
||||
if (attachment instanceof spine.RegionAttachment) {
|
||||
vertices = base.computeRegionVertices(slot, attachment, false);
|
||||
triangles = Renderer.QUAD_TRIANGLES || QUAD_TRIANGLES;
|
||||
vertexCount = 4;
|
||||
} else if (attachment instanceof spine.MeshAttachment) {
|
||||
vertices = base.computeMeshVertices(slot, attachment, false);
|
||||
triangles = attachment.triangles;
|
||||
vertexCount = attachment.worldVerticesLength >> 1;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sourceImage = imageFromAttachment(attachment);
|
||||
const image = base.gpuDrawing ? sourceImage : imageForCanvas(sourceImage, context, pma);
|
||||
if (!image) continue;
|
||||
context.globalAlpha = base.tempColor.a;
|
||||
context.globalCompositeOperation = BLEND_MODES[slot.data.blendMode] || 'source-over';
|
||||
if (clipper.isClipping()) {
|
||||
const unpacked = unpackVertices(vertices, vertexCount);
|
||||
clipper.clipTriangles(unpacked.positions, unpacked.positions.length,
|
||||
triangles, triangles.length, unpacked.uvs, base.tempColor, dark, false);
|
||||
drawInterleaved(base, image, clipper.clippedVertices, clipper.clippedTriangles, slot.darkColor);
|
||||
} else {
|
||||
drawInterleaved(base, image, vertices, triangles, slot.darkColor);
|
||||
}
|
||||
} finally {
|
||||
clipper.clipEndWithSlot(slot);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
clipper.clipEnd();
|
||||
context.globalAlpha = 1;
|
||||
context.globalCompositeOperation = composite;
|
||||
}
|
||||
};
|
||||
base.clipper = clipper;
|
||||
return base;
|
||||
}
|
||||
|
||||
// 3.4/3.5 predate clipping attachments and compute packed vertices on attachments.
|
||||
function create34Renderer(spine, context) {
|
||||
const base = new (rendererConstructor(spine))(context);
|
||||
base.triangleRendering = true;
|
||||
base.premultipliedAlpha = false;
|
||||
base.draw = skeleton => {
|
||||
const composite = context.globalCompositeOperation;
|
||||
try {
|
||||
for (const slot of skeleton.drawOrder) {
|
||||
const attachment = slot.getAttachment();
|
||||
if (!(attachment instanceof spine.RegionAttachment) && !(attachment instanceof spine.MeshAttachment)) continue;
|
||||
const vertices = attachment.updateWorldVertices(slot, false);
|
||||
const sourceImage = imageFromAttachment(attachment);
|
||||
const image = base.gpuDrawing ? sourceImage : imageForCanvas(sourceImage, context, base.premultipliedAlpha);
|
||||
if (!image) continue;
|
||||
context.globalAlpha = vertices[5];
|
||||
context.globalCompositeOperation = BLEND_MODES[slot.data.blendMode] || 'source-over';
|
||||
drawInterleaved(base, image, vertices, attachment instanceof spine.MeshAttachment ? attachment.triangles : QUAD_TRIANGLES);
|
||||
}
|
||||
} finally { context.globalAlpha = 1; context.globalCompositeOperation = composite; }
|
||||
};
|
||||
return base;
|
||||
}
|
||||
|
||||
function create43Renderer(spine, context) {
|
||||
const Renderer = rendererConstructor(spine);
|
||||
if (!Renderer || !spine.SkeletonRendererCore) throw new Error('Spine 4.3 Canvas 渲染器不完整');
|
||||
const base = new Renderer(context);
|
||||
base.triangleRendering = true;
|
||||
base.premultipliedAlpha = false;
|
||||
const core = new spine.SkeletonRendererCore();
|
||||
|
||||
base.draw = function drawWithCoreClipping(skeleton) {
|
||||
const composite = context.globalCompositeOperation;
|
||||
try {
|
||||
const pma = !!base.premultipliedAlpha;
|
||||
for (let command = core.render(skeleton, false); command; command = command.next) {
|
||||
const sourceImage = command.texture && command.texture.getImage && command.texture.getImage();
|
||||
const image = base.gpuDrawing ? sourceImage : imageForCanvas(sourceImage, context, pma);
|
||||
if (!image) continue;
|
||||
context.globalAlpha = ((command.colors[0] >>> 24) & 255) / 255;
|
||||
context.globalCompositeOperation = BLEND_MODES[command.blendMode] || 'source-over';
|
||||
const positions = command.positions;
|
||||
const uvs = command.uvs;
|
||||
const indices = command.indices;
|
||||
for (let i = 0; i < command.numIndices; i += 3) {
|
||||
const a = indices[i] * 2;
|
||||
const b = indices[i + 1] * 2;
|
||||
const c = indices[i + 2] * 2;
|
||||
const unpack = packed => [((packed>>>16)&255)/255,((packed>>>8)&255)/255,(packed&255)/255,((packed>>>24)&255)/255];
|
||||
const colors = [a,b,c].flatMap(index => unpack(command.colors[index/2]));
|
||||
const darkColors = [a,b,c].flatMap(index => unpack(command.darkColors[index/2]));
|
||||
base.drawTriangle(image,
|
||||
positions[a], positions[a + 1], uvs[a], uvs[a + 1],
|
||||
positions[b], positions[b + 1], uvs[b], uvs[b + 1],
|
||||
positions[c], positions[c + 1], uvs[c], uvs[c + 1], colors, darkColors);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
context.globalAlpha = 1;
|
||||
context.globalCompositeOperation = composite;
|
||||
}
|
||||
};
|
||||
base.clipper = core.clipping;
|
||||
return base;
|
||||
}
|
||||
|
||||
function createRenderer(spine, context, runtimeDir) {
|
||||
const renderer = (runtimeDir === '3.4' || runtimeDir === '3.5') ? create34Renderer(spine, context) : runtimeDir === '4.3'
|
||||
? create43Renderer(spine, context)
|
||||
: createLegacyRenderer(spine, context);
|
||||
// Explicitly disable debug drawing in every supported runtime.
|
||||
renderer.debugRendering = false;
|
||||
return require('./spine-webgl-rasterizer.js').attach(renderer, context);
|
||||
}
|
||||
|
||||
module.exports = { createRenderer };
|
||||
@@ -0,0 +1,177 @@
|
||||
'use strict';
|
||||
|
||||
// Decode file bytes without the Canvas 2D premultiply/unpremultiply round trip.
|
||||
// In low-alpha PMA pixels that round trip destroys RGB precision before blending.
|
||||
const preparedTextures = new WeakMap();
|
||||
async function prepareTexture(image, blob) {
|
||||
if (typeof createImageBitmap !== 'function') return;
|
||||
const options = { colorSpaceConversion: 'none' };
|
||||
const [pma, straight] = await Promise.all([
|
||||
createImageBitmap(blob, { ...options, premultiplyAlpha: 'none' }),
|
||||
createImageBitmap(blob, { ...options, premultiplyAlpha: 'premultiply' })
|
||||
]);
|
||||
preparedTextures.set(image, { pma, straight });
|
||||
}
|
||||
|
||||
// Rasterize adjacent mesh triangles together, with GPU edge ownership rules.
|
||||
// Canvas clip()+drawImage() anti-aliases each triangle separately and exposes
|
||||
// the background along shared edges, which looks like a wireframe overlay.
|
||||
function createRasterizer(context) {
|
||||
const document = context.canvas && context.canvas.ownerDocument;
|
||||
if (!document || typeof context.getTransform !== 'function') return null;
|
||||
const canvas = document.createElement('canvas');
|
||||
const gl = canvas.getContext('webgl', {
|
||||
alpha: true, premultipliedAlpha: true, antialias: true,
|
||||
preserveDrawingBuffer: true, depth: false, stencil: false
|
||||
});
|
||||
if (!gl) return null;
|
||||
function shader(type, source) {
|
||||
const shader = gl.createShader(type);
|
||||
gl.shaderSource(shader, source); gl.compileShader(shader);
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(shader));
|
||||
return shader;
|
||||
}
|
||||
const vertex = shader(gl.VERTEX_SHADER, `
|
||||
attribute vec2 position;
|
||||
attribute vec2 uv;
|
||||
attribute vec4 light;
|
||||
attribute vec4 dark;
|
||||
uniform vec2 resolution;
|
||||
varying vec2 textureUV;
|
||||
varying vec4 lightColor;
|
||||
varying vec4 darkColor;
|
||||
void main() {
|
||||
vec2 p = position / resolution * 2.0 - 1.0;
|
||||
gl_Position = vec4(p.x, -p.y, 0.0, 1.0);
|
||||
textureUV = uv;
|
||||
lightColor = light; darkColor = dark;
|
||||
}`);
|
||||
const fragment = shader(gl.FRAGMENT_SHADER, `
|
||||
precision mediump float;
|
||||
uniform sampler2D image;
|
||||
varying vec2 textureUV;
|
||||
varying vec4 lightColor;
|
||||
varying vec4 darkColor;
|
||||
void main() {
|
||||
vec4 color = texture2D(image, textureUV);
|
||||
float alpha = color.a * lightColor.a;
|
||||
// Textures are PMA in GPU memory. Light/dark colors arrive straight.
|
||||
vec3 rgb = (color.rgb * lightColor.rgb + (color.a - color.rgb) * darkColor.rgb) * lightColor.a;
|
||||
gl_FragColor = vec4(rgb, alpha);
|
||||
}`);
|
||||
const program = gl.createProgram(); gl.attachShader(program, vertex); gl.attachShader(program, fragment); gl.linkProgram(program);
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program));
|
||||
gl.deleteShader(vertex); gl.deleteShader(fragment);
|
||||
const buffer = gl.createBuffer();
|
||||
const position = gl.getAttribLocation(program, 'position'), uv = gl.getAttribLocation(program, 'uv');
|
||||
const resolution = gl.getUniformLocation(program, 'resolution');
|
||||
const light = gl.getAttribLocation(program, 'light'), dark = gl.getAttribLocation(program, 'dark');
|
||||
const textures = new Map();
|
||||
let coordinates = [], currentImage = null, currentAlpha = 1, currentMode = 'source-over', transform;
|
||||
let disposed = false, lost = false;
|
||||
let premultipliedAlpha = false;
|
||||
canvas.addEventListener('webglcontextlost', event => { event.preventDefault(); lost = true; });
|
||||
function texture(image) {
|
||||
const pma = image !== context.canvas && premultipliedAlpha;
|
||||
let pair = textures.get(image);
|
||||
if (!pair) { pair = {}; textures.set(image, pair); }
|
||||
const key = pma ? 'pma' : 'straight';
|
||||
if (pair[key] && image !== context.canvas) return pair[key];
|
||||
const t = pair[key] || gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, t);
|
||||
// Premultiply before interpolation so translucent edges do not darken.
|
||||
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, !pma);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
const prepared = preparedTextures.get(image);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, prepared ? prepared[key] : image);
|
||||
pair[key] = t; return t;
|
||||
}
|
||||
function flush() {
|
||||
if (!coordinates.length) return;
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture(currentImage));
|
||||
// Premultiplied RGB factors; keep source-over alpha independently.
|
||||
const factors = currentMode === 'lighter' ? [gl.ONE, gl.ONE]
|
||||
: currentMode === 'multiply' ? [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA]
|
||||
: currentMode === 'screen' ? [gl.ONE, gl.ONE_MINUS_SRC_COLOR]
|
||||
: [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
|
||||
gl.blendFuncSeparate(factors[0], factors[1], gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(coordinates), gl.STREAM_DRAW);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, coordinates.length / 12);
|
||||
coordinates.length = 0;
|
||||
}
|
||||
return {
|
||||
begin(pma = false) {
|
||||
if (disposed || lost || gl.isContextLost()) return false;
|
||||
premultipliedAlpha = pma;
|
||||
const target = context.canvas;
|
||||
if (canvas.width !== target.width) canvas.width = target.width;
|
||||
if (canvas.height !== target.height) canvas.height = target.height;
|
||||
transform = context.getTransform(); coordinates.length = 0; currentImage = null;
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
gl.useProgram(program); gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.enableVertexAttribArray(position); gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 48, 0);
|
||||
gl.enableVertexAttribArray(uv); gl.vertexAttribPointer(uv, 2, gl.FLOAT, false, 48, 8);
|
||||
gl.enableVertexAttribArray(light); gl.vertexAttribPointer(light, 4, gl.FLOAT, false, 48, 16);
|
||||
gl.enableVertexAttribArray(dark); gl.vertexAttribPointer(dark, 4, gl.FLOAT, false, 48, 32);
|
||||
gl.uniform2f(resolution, canvas.width, canvas.height);
|
||||
gl.activeTexture(gl.TEXTURE0); gl.uniform1i(gl.getUniformLocation(program, 'image'), 0);
|
||||
gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
// Seed the same framebuffer with the actual Canvas pixels (solid or
|
||||
// checkerboard background). CSS backgrounds cannot participate in blending.
|
||||
currentImage = target; currentAlpha = 1; currentMode = 'source-over';
|
||||
const w = canvas.width, h = canvas.height;
|
||||
for (const vertex of [[0,0,0,0],[w,0,1,0],[w,h,1,1],[0,0,0,0],[w,h,1,1],[0,h,0,1]])
|
||||
coordinates.push(...vertex, 1,1,1,1, 0,0,0,0);
|
||||
flush(); currentImage = null;
|
||||
return true;
|
||||
},
|
||||
triangle(image, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2, colors, darkColors) {
|
||||
const alpha = context.globalAlpha;
|
||||
const mode = context.globalCompositeOperation || 'source-over';
|
||||
if (image !== currentImage || alpha !== currentAlpha || mode !== currentMode) { flush(); currentImage = image; currentAlpha = alpha; currentMode = mode; }
|
||||
const t = transform;
|
||||
const points = [[x0,y0,u0,v0],[x1,y1,u1,v1],[x2,y2,u2,v2]];
|
||||
for (let i=0; i<3; i++) {
|
||||
const [x,y,u,v] = points[i], j=i*4;
|
||||
coordinates.push(t.a*x+t.c*y+t.e, t.b*x+t.d*y+t.f, u,v,
|
||||
...(colors ? colors.slice(j,j+4) : [1,1,1,alpha]),
|
||||
...(darkColors ? darkColors.slice(j,j+4) : [0,0,0,0]));
|
||||
}
|
||||
},
|
||||
end() {
|
||||
flush();
|
||||
context.save();
|
||||
try { context.setTransform(1, 0, 0, 1, 0, 0); context.globalAlpha = 1; context.globalCompositeOperation = 'copy'; context.drawImage(canvas, 0, 0); }
|
||||
finally { context.restore(); }
|
||||
},
|
||||
dispose() {
|
||||
if (disposed) return; disposed = true;
|
||||
for (const pair of textures.values()) for (const value of Object.values(pair)) gl.deleteTexture(value);
|
||||
textures.clear(); gl.deleteBuffer(buffer); gl.deleteProgram(program);
|
||||
const extension = gl.getExtension('WEBGL_lose_context'); if (extension) extension.loseContext();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function attach(renderer, context) {
|
||||
let rasterizer;
|
||||
try { rasterizer = createRasterizer(context); } catch (e) { return renderer; }
|
||||
if (!rasterizer) return renderer;
|
||||
const draw = renderer.draw.bind(renderer), triangle = renderer.drawTriangle.bind(renderer);
|
||||
renderer.draw = skeleton => {
|
||||
if (!rasterizer.begin(!!renderer.premultipliedAlpha)) return draw(skeleton);
|
||||
renderer.gpuDrawing = true;
|
||||
renderer.drawTriangle = rasterizer.triangle;
|
||||
try { draw(skeleton); rasterizer.end(); }
|
||||
finally { renderer.drawTriangle = triangle; renderer.gpuDrawing = false; }
|
||||
};
|
||||
renderer.dispose = rasterizer.dispose;
|
||||
renderer.rasterization = 'webgl';
|
||||
return renderer;
|
||||
}
|
||||
|
||||
module.exports = { createRasterizer, attach, prepareTexture };
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 339 KiB |