mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-12 01:58:45 +08:00
Merge branch '4.1' into 4.2-beta
This commit is contained in:
commit
102f3e044b
1
.gitignore
vendored
1
.gitignore
vendored
@ -145,6 +145,7 @@ spine-ts/spine-canvas/dist
|
||||
spine-ts/spine-webgl/dist
|
||||
spine-ts/spine-player/dist
|
||||
spine-ts/spine-threejs/dist
|
||||
spine-ts/spine-pixi/dist
|
||||
spine-libgdx/gradle
|
||||
spine-libgdx/gradlew
|
||||
spine-libgdx/gradlew.bat
|
||||
|
||||
@ -357,6 +357,11 @@ cp -f ../spineboy/export/spineboy-pro.skel "$ROOT/spine-ts/spine-player/example/
|
||||
cp -f ../spineboy/export/spineboy-pma.atlas "$ROOT/spine-ts/spine-player/example/assets/"
|
||||
cp -f ../spineboy/export/spineboy-pma.png "$ROOT/spine-ts/spine-player/example/assets/"
|
||||
|
||||
cp -f ../spineboy/export/spineboy-pro.json "$ROOT/spine-ts/spine-pixi/example/assets/"
|
||||
cp -f ../spineboy/export/spineboy-pro.skel "$ROOT/spine-ts/spine-pixi/example/assets/"
|
||||
cp -f ../spineboy/export/spineboy.atlas "$ROOT/spine-ts/spine-pixi/example/assets/"
|
||||
cp -f ../spineboy/export/spineboy.png "$ROOT/spine-ts/spine-pixi/example/assets/"
|
||||
|
||||
rm "$ROOT/spine-ts/spine-phaser/example/assets/"*
|
||||
cp -f ../raptor/export/raptor-pro.json "$ROOT/spine-ts/spine-phaser/example/assets/"
|
||||
cp -f ../raptor/export/raptor-pma.atlas "$ROOT/spine-ts/spine-phaser/example/assets/"
|
||||
|
||||
@ -20,6 +20,7 @@ then
|
||||
spine-webgl/dist/iife/* \
|
||||
spine-player/dist/iife/* \
|
||||
spine-threejs/dist/iife/* \
|
||||
spine-pixi/dist/iife/* \
|
||||
spine-player/css/spine-player.css
|
||||
curl -f -F "file=@spine-ts.zip" "$TS_UPDATE_URL$BRANCH"
|
||||
else
|
||||
|
||||
236
spine-ts/package-lock.json
generated
236
spine-ts/package-lock.json
generated
@ -14,6 +14,7 @@
|
||||
"spine-phaser",
|
||||
"spine-player",
|
||||
"spine-threejs",
|
||||
"spine-pixi",
|
||||
"spine-webgl"
|
||||
],
|
||||
"devDependencies": {
|
||||
@ -56,6 +57,10 @@
|
||||
"resolved": "spine-phaser",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@esotericsoftware/spine-pixi": {
|
||||
"resolved": "spine-pixi",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@esotericsoftware/spine-player": {
|
||||
"resolved": "spine-player",
|
||||
"link": true
|
||||
@ -68,6 +73,178 @@
|
||||
"resolved": "spine-webgl",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@pixi/assets": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/assets/-/assets-7.2.4.tgz",
|
||||
"integrity": "sha512-7199re3wvMAlVqXLaCyAr8IkJSXqkeVAxcYyB2rBu4Id5m2hhlGX1dQsdMBiCXLwu6/LLVqDvJggSNVQBzL6ZQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/css-font-loading-module": "^0.0.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pixi/core": "7.2.4",
|
||||
"@pixi/utils": "7.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/color": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.2.4.tgz",
|
||||
"integrity": "sha512-B/+9JRcXe2uE8wQfsueFRPZVayF2VEMRB7XGeRAsWCryOX19nmWhv0Nt3nOU2rvzI0niz9XgugJXsB6vVmDFSg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"colord": "^2.9.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/constants": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.2.4.tgz",
|
||||
"integrity": "sha512-hKuHBWR6N4Q0Sf5MGF3/9l+POg/G5rqhueHfzofiuelnKg7aBs3BVjjZ+6hZbd6M++vOUmxYelEX/NEFBxrheA==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@pixi/core": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.2.4.tgz",
|
||||
"integrity": "sha512-0XtvrfxHlS2T+beBBSpo7GI8+QLyyTqMVQpNmPqB4woYxzrOEJ9JaUFBaBfCvycLeUkfVih1u6HAbtF+2d1EjQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@pixi/color": "7.2.4",
|
||||
"@pixi/constants": "7.2.4",
|
||||
"@pixi/extensions": "7.2.4",
|
||||
"@pixi/math": "7.2.4",
|
||||
"@pixi/runner": "7.2.4",
|
||||
"@pixi/settings": "7.2.4",
|
||||
"@pixi/ticker": "7.2.4",
|
||||
"@pixi/utils": "7.2.4",
|
||||
"@types/offscreencanvas": "^2019.6.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/pixijs"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/display": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/display/-/display-7.2.4.tgz",
|
||||
"integrity": "sha512-w5tqb8cWEO5qIDaO9GEqRvxYhL0iMk0Wsngw23bbLm1gLEQmrFkB2tpJlRAqd7H82C3DrDDeWvkrrxW6+m4apg==",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@pixi/core": "7.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/extensions": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.2.4.tgz",
|
||||
"integrity": "sha512-Mnqv9scbL1ARD3QFKfOWs2aSVJJfP1dL8g5UiqGImYO3rZbz/9QCzXOeMVIZ5n3iaRyKMNhFFr84/zUja2H7Dw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@pixi/graphics": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.2.4.tgz",
|
||||
"integrity": "sha512-3A2EumTjWJgXlDLOyuBrl9b6v1Za/E+/IjOGUIX843HH4NYaf1a2sfDfljx6r3oiDvy+VhuBFmgynRcV5IyA0Q==",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@pixi/core": "7.2.4",
|
||||
"@pixi/display": "7.2.4",
|
||||
"@pixi/sprite": "7.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/math": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.2.4.tgz",
|
||||
"integrity": "sha512-LJB+mozyEPllxa0EssFZrKNfVwysfaBun4b2dJKQQInp0DafgbA0j7A+WVg0oe51KhFULTJMpDqbLn/ITFc41A==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@pixi/mesh": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/mesh/-/mesh-7.2.4.tgz",
|
||||
"integrity": "sha512-wiALIqcRKib2BqeH9kOA5fOKWN352nqAspgbDa8gA7OyWzmNwqIedIlElixd0oLFOrIN5jOZAdzeKnoYQlt9Aw==",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@pixi/core": "7.2.4",
|
||||
"@pixi/display": "7.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/runner": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.2.4.tgz",
|
||||
"integrity": "sha512-YtyqPk1LA+0guEFKSFx6t/YSvbEQwajFwi4Ft8iDhioa6VK2MmTir1GjWwy7JQYLcDmYSAcQjnmFtVTZohyYSw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@pixi/settings": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.2.4.tgz",
|
||||
"integrity": "sha512-ZPKRar9EwibijGmH8EViu4Greq1I/O7V/xQx2rNqN23XA7g09Qo6yfaeQpufu5xl8+/lZrjuHtQSnuY7OgG1CA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@pixi/constants": "7.2.4",
|
||||
"@types/css-font-loading-module": "^0.0.7",
|
||||
"ismobilejs": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/sprite": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/sprite/-/sprite-7.2.4.tgz",
|
||||
"integrity": "sha512-DhR1B+/d0eXpxHIesJMXcVPrKFwQ+zRA1LvEIFfzewqfaRN3X6PMIuoKX8SIb6tl+Hq8Ba9Pe28zI7d2rmRzrA==",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@pixi/core": "7.2.4",
|
||||
"@pixi/display": "7.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/text": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/text/-/text-7.2.4.tgz",
|
||||
"integrity": "sha512-DGu7ktpe+zHhqR2sG9NsJt4mgvSObv5EqXTtUxD4Z0li1gmqF7uktpLyn5I6vSg1TTEL4TECClRDClVDGiykWw==",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@pixi/core": "7.2.4",
|
||||
"@pixi/sprite": "7.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/ticker": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.2.4.tgz",
|
||||
"integrity": "sha512-hQQHIHvGeFsP4GNezZqjzuhUgNQEVgCH9+qU05UX1Mc5UHC9l6OJnY4VTVhhcHxZjA6RnyaY+1zBxCnoXuazpg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@pixi/extensions": "7.2.4",
|
||||
"@pixi/settings": "7.2.4",
|
||||
"@pixi/utils": "7.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/utils": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.2.4.tgz",
|
||||
"integrity": "sha512-VUGQHBOINIS4ePzoqafwxaGPVRTa3oM/mEutIIHbNGI3b+QvSO+1Dnk40M0zcH6Bo+MxQZbOZK5X/wO9oU5+LQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@pixi/color": "7.2.4",
|
||||
"@pixi/constants": "7.2.4",
|
||||
"@pixi/settings": "7.2.4",
|
||||
"@types/earcut": "^2.1.0",
|
||||
"earcut": "^2.2.4",
|
||||
"eventemitter3": "^4.0.0",
|
||||
"url": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/utils/node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/css-font-loading-module": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz",
|
||||
"integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/earcut": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.1.tgz",
|
||||
"integrity": "sha512-w8oigUCDjElRHRRrMvn/spybSMyX8MTkKA5Dv+tS1IE/TgmNZPqUYtvYBXGY8cieSE66gm+szeK+bnbxC2xHTQ==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/offscreencanvas": {
|
||||
"version": "2019.7.0",
|
||||
"dev": true,
|
||||
@ -499,6 +676,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colord": {
|
||||
"version": "2.9.3",
|
||||
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
|
||||
"integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/colors": {
|
||||
"version": "1.4.0",
|
||||
"dev": true,
|
||||
@ -733,6 +916,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/earcut": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
|
||||
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"dev": true,
|
||||
@ -1407,6 +1596,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ismobilejs": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz",
|
||||
"integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/isobject": {
|
||||
"version": "3.0.1",
|
||||
"dev": true,
|
||||
@ -1823,6 +2018,22 @@
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
||||
"integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/querystring": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
"integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==",
|
||||
"deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"dev": true,
|
||||
@ -2586,6 +2797,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/url": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
|
||||
"integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"punycode": "1.3.2",
|
||||
"querystring": "0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use": {
|
||||
"version": "3.1.1",
|
||||
"dev": true,
|
||||
@ -2729,6 +2950,21 @@
|
||||
"@esotericsoftware/spine-webgl": "4.2.14"
|
||||
}
|
||||
},
|
||||
"spine-pixi": {
|
||||
"version": "4.1.31",
|
||||
"license": "LicenseRef-LICENSE",
|
||||
"dependencies": {
|
||||
"@esotericsoftware/spine-core": "4.1.31"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pixi/assets": "^7.2.4",
|
||||
"@pixi/core": "^7.2.4",
|
||||
"@pixi/display": "^7.2.4",
|
||||
"@pixi/graphics": "^7.2.4",
|
||||
"@pixi/mesh": "^7.2.4",
|
||||
"@pixi/text": "^7.2.4"
|
||||
}
|
||||
},
|
||||
"spine-player": {
|
||||
"name": "@esotericsoftware/spine-player",
|
||||
"version": "4.2.14",
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
],
|
||||
"scripts": {
|
||||
"prepublish": "npm run clean && npm run build",
|
||||
"clean": "npx rimraf spine-core/dist spine-canvas/dist spine-webgl/dist spine-phaser/dist spine-player/dist spine-threejs/dist",
|
||||
"build": "npm run clean && npm run build:modules && concurrently \"npm run build:core\" \"npm run build:canvas\" \"npm run build:webgl\" \"npm run build:phaser\" \"npm run build:player\" \"npm run build:threejs\"",
|
||||
"clean": "npx rimraf spine-core/dist spine-canvas/dist spine-webgl/dist spine-phaser/dist spine-player/dist spine-threejs/dist spine-pixi/dist",
|
||||
"build": "npm run clean && npm run build:modules && concurrently \"npm run build:core\" \"npm run build:canvas\" \"npm run build:webgl\" \"npm run build:phaser\" \"npm run build:player\" \"npm run build:threejs\" \"npm run build:pixi\"",
|
||||
"postbuild": "npm run minify",
|
||||
"build:modules": "npx tsc -b -clean && npx tsc -b",
|
||||
"build:core": "npx esbuild --bundle spine-core/src/index.ts --tsconfig=spine-core/tsconfig.json --sourcemap --outfile=spine-core/dist/iife/spine-core.js --format=iife --global-name=spine",
|
||||
@ -17,14 +17,22 @@
|
||||
"build:player": "npx copyfiles -f spine-player/css/spine-player.css spine-player/dist/ && npx npx esbuild --bundle spine-player/src/index.ts --tsconfig=spine-player/tsconfig.json --sourcemap --outfile=spine-player/dist/iife/spine-player.js --format=iife --global-name=spine",
|
||||
"build:phaser": "npx esbuild --bundle spine-phaser/src/index.ts --tsconfig=spine-phaser/tsconfig.json --sourcemap --outfile=spine-phaser/dist/iife/spine-phaser.js --external:Phaser --alias:phaser=Phaser --format=iife --global-name=spine",
|
||||
"build:threejs": "npx esbuild --bundle spine-threejs/src/index.ts --tsconfig=spine-threejs/tsconfig.json --sourcemap --outfile=spine-threejs/dist/iife/spine-threejs.js --external:three --format=iife --global-name=spine",
|
||||
"minify": "npx esbuild --minify spine-core/dist/iife/spine-core.js --outfile=spine-core/dist/iife/spine-core.min.js && npx esbuild --minify spine-canvas/dist/iife/spine-canvas.js --outfile=spine-canvas/dist/iife/spine-canvas.min.js && npx esbuild --minify spine-player/dist/iife/spine-player.js --outfile=spine-player/dist/iife/spine-player.min.js && npx esbuild --minify spine-phaser/dist/iife/spine-phaser.js --outfile=spine-phaser/dist/iife/spine-phaser.min.js && npx esbuild --minify spine-webgl/dist/iife/spine-webgl.js --outfile=spine-webgl/dist/iife/spine-webgl.min.js && npx esbuild --minify spine-threejs/dist/iife/spine-threejs.js --outfile=spine-threejs/dist/iife/spine-threejs.min.js",
|
||||
"dev": "concurrently \"npx live-server\" \"npm run dev:canvas\" \"npm run dev:webgl\" \"npm run dev:phaser\" \"npm run dev:player\" \"npm run dev:threejs\"",
|
||||
|
||||
|
||||
|
||||
"build:pixi": "npx esbuild --bundle spine-pixi/src/index.ts --tsconfig=spine-pixi/tsconfig.json --sourcemap --outfile=spine-pixi/dist/iife/spine-pixi.js --external:@pixi/* --format=iife --global-name=spine",
|
||||
|
||||
|
||||
|
||||
"minify": "npx esbuild --minify spine-core/dist/iife/spine-core.js --outfile=spine-core/dist/iife/spine-core.min.js && npx esbuild --minify spine-canvas/dist/iife/spine-canvas.js --outfile=spine-canvas/dist/iife/spine-canvas.min.js && npx esbuild --minify spine-player/dist/iife/spine-player.js --outfile=spine-player/dist/iife/spine-player.min.js && npx esbuild --minify spine-phaser/dist/iife/spine-phaser.js --outfile=spine-phaser/dist/iife/spine-phaser.min.js && npx esbuild --minify spine-webgl/dist/iife/spine-webgl.js --outfile=spine-webgl/dist/iife/spine-webgl.min.js && npx esbuild --minify spine-threejs/dist/iife/spine-threejs.js --outfile=spine-threejs/dist/iife/spine-threejs.min.js && npx esbuild --minify spine-pixi/dist/iife/spine-pixi.js --outfile=spine-pixi/dist/iife/spine-pixi.min.js",
|
||||
"dev": "concurrently \"npx live-server\" \"npm run dev:canvas\" \"npm run dev:webgl\" \"npm run dev:phaser\" \"npm run dev:player\" \"npm run dev:threejs\" \"npm run dev:pixi\"",
|
||||
"dev:modules": "npm run build:modules -- --watch",
|
||||
"dev:canvas": "npm run build:canvas -- --watch",
|
||||
"dev:webgl": "npm run build:webgl -- --watch",
|
||||
"dev:phaser": "npm run build:phaser -- --watch",
|
||||
"dev:player": "npm run build:player -- --watch",
|
||||
"dev:threejs": "npm run build:threejs -- --watch"
|
||||
"dev:threejs": "npm run build:threejs -- --watch",
|
||||
"dev:pixi": "npm run build:pixi -- --watch"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -51,6 +59,7 @@
|
||||
"spine-phaser",
|
||||
"spine-player",
|
||||
"spine-threejs",
|
||||
"spine-pixi",
|
||||
"spine-webgl"
|
||||
],
|
||||
"devDependencies": {
|
||||
|
||||
3
spine-ts/spine-pixi/README.md
Normal file
3
spine-ts/spine-pixi/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# spine-ts Pixi.js
|
||||
|
||||
Please see the top-level [README.md](../README.md) for more information.
|
||||
117
spine-ts/spine-pixi/example/assets/spineboy-polypack.atlas
Normal file
117
spine-ts/spine-pixi/example/assets/spineboy-polypack.atlas
Normal file
@ -0,0 +1,117 @@
|
||||
spineboy-polypack.png
|
||||
size:2048,512
|
||||
filter:Linear,Linear
|
||||
pma:true
|
||||
crosshair
|
||||
bounds:1895,128,89,89
|
||||
eye-indifferent
|
||||
bounds:1863,217,93,89
|
||||
rotate:90
|
||||
eye-surprised
|
||||
bounds:1696,48,93,89
|
||||
front-bracer
|
||||
bounds:1192,4,58,80
|
||||
rotate:90
|
||||
front-fist-closed
|
||||
bounds:430,4,75,82
|
||||
rotate:90
|
||||
front-fist-open
|
||||
bounds:1895,42,86,87
|
||||
rotate:90
|
||||
front-foot
|
||||
bounds:1066,22,126,69
|
||||
rotate:180
|
||||
front-shin
|
||||
bounds:0,46,82,184
|
||||
rotate:90
|
||||
front-thigh
|
||||
bounds:1484,20,45,112
|
||||
front-upper-arm
|
||||
bounds:791,11,46,97
|
||||
rotate:90
|
||||
goggles
|
||||
bounds:1320,116,261,166
|
||||
gun
|
||||
bounds:1653,137,210,203
|
||||
head
|
||||
bounds:1025,121,271,298
|
||||
rotate:90
|
||||
hoverboard-board
|
||||
bounds:1297,240,492,152
|
||||
rotate:180
|
||||
hoverboard-thruster
|
||||
bounds:1529,0,60,64
|
||||
rotate:90
|
||||
hoverglow-small
|
||||
bounds:528,63,274,75
|
||||
mouth-grind
|
||||
bounds:698,4,93,59
|
||||
mouth-oooo
|
||||
bounds:605,4,93,59
|
||||
mouth-smile
|
||||
bounds:512,4,93,59
|
||||
muzzle-glow
|
||||
bounds:87,2,50,50
|
||||
muzzle-ring
|
||||
bounds:317,79,49,209
|
||||
rotate:90
|
||||
muzzle01
|
||||
bounds:184,49,133,79
|
||||
muzzle02
|
||||
bounds:802,57,135,81
|
||||
offsets:0,3,135,84
|
||||
muzzle03
|
||||
bounds:1536,60,160,98
|
||||
offsets:4,5,166,106
|
||||
muzzle04
|
||||
bounds:1807,68,149,88
|
||||
offsets:0,2,149,90
|
||||
rotate:90
|
||||
muzzle05
|
||||
bounds:1351,43,133,73
|
||||
offsets:0,1,135,75
|
||||
neck
|
||||
bounds:137,9,35,41
|
||||
offsets:0,0,36,41
|
||||
portal-bg
|
||||
bounds:264,128,264,264
|
||||
offsets:1,1,266,266
|
||||
portal-flare1
|
||||
bounds:1984,108,109,58
|
||||
offsets:1,1,111,60
|
||||
rotate:90
|
||||
portal-flare2
|
||||
bounds:1952,217,112,59
|
||||
offsets:1,1,114,61
|
||||
rotate:90
|
||||
portal-flare3
|
||||
bounds:317,22,113,57
|
||||
offsets:1,1,115,59
|
||||
portal-shade
|
||||
bounds:0,128,264,264
|
||||
offsets:1,1,266,266
|
||||
portal-streaks1
|
||||
bounds:528,138,250,254
|
||||
offsets:1,1,252,256
|
||||
portal-streaks2
|
||||
bounds:778,138,248,247
|
||||
offsets:1,1,250,249
|
||||
rotate:90
|
||||
rear-bracer
|
||||
bounds:1272,7,55,70
|
||||
offsets:0,2,56,72
|
||||
rotate:90
|
||||
rear-foot
|
||||
bounds:1743,5,113,60
|
||||
rotate:180
|
||||
rear-shin
|
||||
bounds:937,46,75,178
|
||||
rotate:90
|
||||
rear-thigh
|
||||
bounds:1982,14,55,94
|
||||
rear-upper-arm
|
||||
bounds:0,7,40,87
|
||||
rotate:90
|
||||
torso
|
||||
bounds:1171,62,98,180
|
||||
rotate:270
|
||||
BIN
spine-ts/spine-pixi/example/assets/spineboy-polypack.png
Normal file
BIN
spine-ts/spine-pixi/example/assets/spineboy-polypack.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 858 KiB |
8707
spine-ts/spine-pixi/example/assets/spineboy-pro.json
Normal file
8707
spine-ts/spine-pixi/example/assets/spineboy-pro.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
spine-ts/spine-pixi/example/assets/spineboy-pro.skel
Normal file
BIN
spine-ts/spine-pixi/example/assets/spineboy-pro.skel
Normal file
Binary file not shown.
101
spine-ts/spine-pixi/example/assets/spineboy.atlas
Normal file
101
spine-ts/spine-pixi/example/assets/spineboy.atlas
Normal file
@ -0,0 +1,101 @@
|
||||
spineboy.png
|
||||
size: 1024, 256
|
||||
filter: Linear, Linear
|
||||
scale: 0.5
|
||||
crosshair
|
||||
bounds: 813, 160, 45, 45
|
||||
eye-indifferent
|
||||
bounds: 569, 2, 47, 45
|
||||
eye-surprised
|
||||
bounds: 643, 7, 47, 45
|
||||
rotate: 90
|
||||
front-bracer
|
||||
bounds: 811, 51, 29, 40
|
||||
front-fist-closed
|
||||
bounds: 807, 93, 38, 41
|
||||
front-fist-open
|
||||
bounds: 815, 210, 43, 44
|
||||
front-foot
|
||||
bounds: 706, 64, 63, 35
|
||||
rotate: 90
|
||||
front-shin
|
||||
bounds: 80, 11, 41, 92
|
||||
front-thigh
|
||||
bounds: 754, 12, 23, 56
|
||||
front-upper-arm
|
||||
bounds: 618, 5, 23, 49
|
||||
goggles
|
||||
bounds: 214, 20, 131, 83
|
||||
gun
|
||||
bounds: 347, 14, 105, 102
|
||||
rotate: 90
|
||||
head
|
||||
bounds: 80, 105, 136, 149
|
||||
hoverboard-board
|
||||
bounds: 2, 8, 246, 76
|
||||
rotate: 90
|
||||
hoverboard-thruster
|
||||
bounds: 478, 2, 30, 32
|
||||
hoverglow-small
|
||||
bounds: 218, 117, 137, 38
|
||||
rotate: 90
|
||||
mouth-grind
|
||||
bounds: 775, 80, 47, 30
|
||||
rotate: 90
|
||||
mouth-oooo
|
||||
bounds: 779, 31, 47, 30
|
||||
rotate: 90
|
||||
mouth-smile
|
||||
bounds: 783, 207, 47, 30
|
||||
rotate: 90
|
||||
muzzle-glow
|
||||
bounds: 779, 4, 25, 25
|
||||
muzzle-ring
|
||||
bounds: 451, 14, 25, 105
|
||||
muzzle01
|
||||
bounds: 664, 60, 67, 40
|
||||
rotate: 90
|
||||
muzzle02
|
||||
bounds: 580, 56, 68, 42
|
||||
rotate: 90
|
||||
muzzle03
|
||||
bounds: 478, 36, 83, 53
|
||||
rotate: 90
|
||||
muzzle04
|
||||
bounds: 533, 49, 75, 45
|
||||
rotate: 90
|
||||
muzzle05
|
||||
bounds: 624, 56, 68, 38
|
||||
rotate: 90
|
||||
neck
|
||||
bounds: 806, 8, 18, 21
|
||||
portal-bg
|
||||
bounds: 258, 121, 133, 133
|
||||
portal-flare1
|
||||
bounds: 690, 2, 56, 30
|
||||
rotate: 90
|
||||
portal-flare2
|
||||
bounds: 510, 3, 57, 31
|
||||
portal-flare3
|
||||
bounds: 722, 4, 58, 30
|
||||
rotate: 90
|
||||
portal-shade
|
||||
bounds: 393, 121, 133, 133
|
||||
portal-streaks1
|
||||
bounds: 528, 126, 126, 128
|
||||
portal-streaks2
|
||||
bounds: 656, 129, 125, 125
|
||||
rear-bracer
|
||||
bounds: 826, 13, 28, 36
|
||||
rear-foot
|
||||
bounds: 743, 70, 57, 30
|
||||
rotate: 90
|
||||
rear-shin
|
||||
bounds: 174, 14, 38, 89
|
||||
rear-thigh
|
||||
bounds: 783, 158, 28, 47
|
||||
rear-upper-arm
|
||||
bounds: 783, 136, 20, 44
|
||||
rotate: 90
|
||||
torso
|
||||
bounds: 123, 13, 49, 90
|
||||
BIN
spine-ts/spine-pixi/example/assets/spineboy.png
Normal file
BIN
spine-ts/spine-pixi/example/assets/spineboy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 239 KiB |
140
spine-ts/spine-pixi/example/index.html
Normal file
140
spine-ts/spine-pixi/example/index.html
Normal file
@ -0,0 +1,140 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>spine-pixi</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/pixi.js@7.2.4/dist/pixi.min.js"></script>
|
||||
<script src="../dist/iife/spine-pixi.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/tweakpane@3.1.9/dist/tweakpane.min.js"></script>
|
||||
</head>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
height: 100%
|
||||
}
|
||||
|
||||
canvas {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
(async function () {
|
||||
var app = new PIXI.Application({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
resolution: window.devicePixelRatio || 1,
|
||||
autoDensity: true,
|
||||
resizeTo: window,
|
||||
backgroundColor: 0x2c3e50,
|
||||
hello:true
|
||||
});
|
||||
document.body.appendChild(app.view);
|
||||
|
||||
// Feel free to mix and match the binary skeleton, the json skeleton, the rect atlas and the polypack atlas
|
||||
// You only need one skeleton and one atlas, the rest is just to show how to load different formats
|
||||
PIXI.Assets.add("spineboySkeletonJson", "./assets/spineboy-pro.json");
|
||||
PIXI.Assets.add("spineboySkeletonBinary", "./assets/spineboy-pro.skel");
|
||||
PIXI.Assets.add("spineboyAtlas", "./assets/spineboy.atlas");
|
||||
PIXI.Assets.add("spineboyAtlasPolypack", "./assets/spineboy-polypack.atlas");
|
||||
|
||||
await PIXI.Assets.load([
|
||||
"spineboySkeletonJson",
|
||||
"spineboySkeletonBinary",
|
||||
"spineboyAtlas",
|
||||
"spineboyAtlasPolypack"
|
||||
]);
|
||||
|
||||
// Create the spine display object
|
||||
const spineBoy = spine.Spine.from("spineboySkeletonJson", "spineboyAtlas", { scale: 0.5 });
|
||||
|
||||
// .from(...) is a shortcut + cache for creating the skeleton data at a certain scale
|
||||
// Here would be the "long way" of doing it (without cache):
|
||||
|
||||
// const skeletonAsset = Assets.get(skeletonAssetName);
|
||||
// const atlasAsset = Assets.get(atlasAssetName);
|
||||
// const attachmentLoader = new AtlasAttachmentLoader(atlasAsset);
|
||||
// let parser; // You can skip this guessing step if you know the type of the skeleton asset
|
||||
// if (skeletonAsset instanceof Uint8Array) {
|
||||
// parser = new SkeletonBinary(attachmentLoader);
|
||||
// } else {
|
||||
// parser = new SkeletonJson(attachmentLoader);
|
||||
// }
|
||||
// parser.scale = options?.scale ?? 1;
|
||||
// skeletonData = parser.readSkeletonData(skeletonAsset);
|
||||
// onst spineBoy = new spine.Spine(skeletonData, options);
|
||||
|
||||
|
||||
// Set the position
|
||||
spineBoy.x = window.innerWidth / 2;
|
||||
spineBoy.y = window.innerHeight * 0.9;
|
||||
|
||||
// start an animation. AutoUpdate is on by default, we don't need a manual rAF loop
|
||||
spineBoy.state.setAnimation(0, "run", true);
|
||||
|
||||
// add to stage
|
||||
app.stage.addChild(spineBoy);
|
||||
|
||||
// do we want debug? we can have debug!
|
||||
const spinedebugger = new spine.SpineDebugRenderer();
|
||||
spineBoy.debug = spinedebugger;
|
||||
|
||||
// End of spine setup. The rest is the tweakpane on the right to play with the spineboy
|
||||
|
||||
const pane = new Tweakpane.Pane({ title: 'spine pixi.js' });
|
||||
|
||||
// spineboy position on screen
|
||||
pane.addInput(spineBoy, 'position', {
|
||||
x: { min: 0, max: window.innerWidth },
|
||||
y: { min: 0, max: window.innerHeight },
|
||||
});
|
||||
|
||||
// Interesting example on how to get the pixi global position of a bone, and how to set a bone to a pixi global position
|
||||
// spine's "global" position is local to the spine display object. It's not the same as pixi's global position
|
||||
const aux = {aimPosition:spineBoy.toGlobal(spineBoy.getBonePosition("crosshair"))};
|
||||
const aimControl = pane.addInput(aux, 'aimPosition', {
|
||||
x: { min: 0, max: window.innerWidth },
|
||||
y: { min: 0, max: window.innerHeight },
|
||||
}).on("change", (e) => {
|
||||
spineBoy.setBonePosition("crosshair", spineBoy.toLocal(e.value));
|
||||
})
|
||||
aimControl.hidden = true;
|
||||
|
||||
// anim changer
|
||||
pane.addBlade({
|
||||
view: 'list',
|
||||
label: 'animation',
|
||||
options: spineBoy.skeleton.data.animations.map(a => ({ text: a.name, value: a.name })),
|
||||
value: 'run',
|
||||
}).on("change", (e) => {
|
||||
spineBoy.state.setAnimation(0, e.value, true);
|
||||
aimControl.hidden = !(e.value == "aim")
|
||||
})
|
||||
|
||||
// turn on or off debug draws
|
||||
const debugFolder = pane.addFolder({
|
||||
title: 'Debug options',
|
||||
});
|
||||
|
||||
debugFolder.addInput(spinedebugger, 'drawMeshHull');
|
||||
debugFolder.addInput(spinedebugger, 'drawMeshTriangles');
|
||||
debugFolder.addInput(spinedebugger, 'drawBones');
|
||||
debugFolder.addInput(spinedebugger, 'drawPaths');
|
||||
debugFolder.addInput(spinedebugger, 'drawBoundingBoxes');
|
||||
debugFolder.addInput(spinedebugger, 'drawClipping');
|
||||
debugFolder.addInput(spinedebugger, 'drawRegionAttachments');
|
||||
debugFolder.addInput(spinedebugger, 'drawEvents');
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
43
spine-ts/spine-pixi/package.json
Normal file
43
spine-ts/spine-pixi/package.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@esotericsoftware/spine-pixi",
|
||||
"version": "4.1.31",
|
||||
"description": "The official Spine Runtimes for the web.",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"scripts": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/esotericsoftware/spine-runtimes.git"
|
||||
},
|
||||
"keywords": [
|
||||
"gamedev",
|
||||
"animations",
|
||||
"2d",
|
||||
"spine",
|
||||
"game-dev",
|
||||
"runtimes",
|
||||
"skeletal"
|
||||
],
|
||||
"author": "Esoteric Software LLC",
|
||||
"license": "LicenseRef-LICENSE",
|
||||
"bugs": {
|
||||
"url": "https://github.com/esotericsoftware/spine-runtimes/issues"
|
||||
},
|
||||
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
|
||||
"dependencies": {
|
||||
"@esotericsoftware/spine-core": "4.1.31"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pixi/core": "^7.2.4",
|
||||
"@pixi/display": "^7.2.4",
|
||||
"@pixi/graphics": "^7.2.4",
|
||||
"@pixi/text": "^7.2.4",
|
||||
"@pixi/assets": "^7.2.4",
|
||||
"@pixi/mesh": "^7.2.4"
|
||||
}
|
||||
}
|
||||
88
spine-ts/spine-pixi/src/DarkSlotMesh.ts
Normal file
88
spine-ts/spine-pixi/src/DarkSlotMesh.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { SpineTexture } from "./SpineTexture";
|
||||
import type { BlendMode, NumberArrayLike } from "@esotericsoftware/spine-core";
|
||||
import { DarkTintMesh } from "./darkTintMesh/DarkTintMesh";
|
||||
import type { ISlotMesh } from "./Spine";
|
||||
|
||||
export class DarkSlotMesh extends DarkTintMesh implements ISlotMesh {
|
||||
public name: string = "";
|
||||
|
||||
private static auxColor = [0, 0, 0, 0];
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
}
|
||||
public updateFromSpineData (
|
||||
slotTexture: SpineTexture,
|
||||
slotBlendMode: BlendMode,
|
||||
slotName: string,
|
||||
finalVertices: NumberArrayLike,
|
||||
finalVerticesLength: number,
|
||||
finalIndices: NumberArrayLike,
|
||||
finalIndicesLength: number,
|
||||
darkTint: boolean
|
||||
): void {
|
||||
this.texture = slotTexture.texture;
|
||||
|
||||
const vertLenght = (finalVerticesLength / (darkTint ? 12 : 8)) * 2;
|
||||
|
||||
if (this.geometry.getBuffer("aTextureCoord").data?.length !== vertLenght) {
|
||||
this.geometry.getBuffer("aTextureCoord").data = new Float32Array(vertLenght);
|
||||
}
|
||||
|
||||
if (this.geometry.getBuffer("aVertexPosition").data?.length !== vertLenght) {
|
||||
this.geometry.getBuffer("aVertexPosition").data = new Float32Array(vertLenght);
|
||||
}
|
||||
|
||||
let vertIndex = 0;
|
||||
|
||||
for (let i = 0; i < finalVerticesLength; i += darkTint ? 12 : 8) {
|
||||
let auxi = i;
|
||||
|
||||
this.geometry.getBuffer("aVertexPosition").data[vertIndex] = finalVertices[auxi++];
|
||||
this.geometry.getBuffer("aVertexPosition").data[vertIndex + 1] = finalVertices[auxi++];
|
||||
|
||||
auxi += 4; // color
|
||||
|
||||
this.geometry.getBuffer("aTextureCoord").data[vertIndex] = finalVertices[auxi++];
|
||||
this.geometry.getBuffer("aTextureCoord").data[vertIndex + 1] = finalVertices[auxi++];
|
||||
|
||||
vertIndex += 2;
|
||||
}
|
||||
|
||||
if (darkTint) {
|
||||
DarkSlotMesh.auxColor[0] = finalVertices[8];
|
||||
DarkSlotMesh.auxColor[1] = finalVertices[9];
|
||||
DarkSlotMesh.auxColor[2] = finalVertices[10];
|
||||
DarkSlotMesh.auxColor[3] = finalVertices[11];
|
||||
this.darkTint = DarkSlotMesh.auxColor;
|
||||
|
||||
DarkSlotMesh.auxColor[0] = finalVertices[2];
|
||||
DarkSlotMesh.auxColor[1] = finalVertices[3];
|
||||
DarkSlotMesh.auxColor[2] = finalVertices[4];
|
||||
DarkSlotMesh.auxColor[3] = finalVertices[5];
|
||||
this.tint = DarkSlotMesh.auxColor;
|
||||
} else {
|
||||
DarkSlotMesh.auxColor[0] = finalVertices[2];
|
||||
DarkSlotMesh.auxColor[1] = finalVertices[3];
|
||||
DarkSlotMesh.auxColor[2] = finalVertices[4];
|
||||
DarkSlotMesh.auxColor[3] = finalVertices[5];
|
||||
|
||||
this.tint = DarkSlotMesh.auxColor;
|
||||
}
|
||||
this.blendMode = SpineTexture.toPixiBlending(slotBlendMode);
|
||||
|
||||
if (this.geometry.indexBuffer.data.length !== finalIndices.length) {
|
||||
this.geometry.indexBuffer.data = new Uint32Array(finalIndices);
|
||||
} else {
|
||||
for (let i = 0; i < finalIndicesLength; i++) {
|
||||
this.geometry.indexBuffer.data[i] = finalIndices[i];
|
||||
}
|
||||
}
|
||||
|
||||
this.name = slotName;
|
||||
|
||||
this.geometry.getBuffer("aVertexPosition").update();
|
||||
this.geometry.getBuffer("aTextureCoord").update();
|
||||
this.geometry.indexBuffer.update();
|
||||
}
|
||||
}
|
||||
89
spine-ts/spine-pixi/src/SlotMesh.ts
Normal file
89
spine-ts/spine-pixi/src/SlotMesh.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { SpineTexture } from "./SpineTexture";
|
||||
import type { BlendMode, NumberArrayLike } from "@esotericsoftware/spine-core";
|
||||
import type { ISlotMesh } from "./Spine";
|
||||
import { Mesh, MeshGeometry, MeshMaterial } from "@pixi/mesh";
|
||||
import { Texture } from "@pixi/core";
|
||||
|
||||
export class SlotMesh extends Mesh implements ISlotMesh {
|
||||
public name: string = "";
|
||||
|
||||
private static readonly auxColor = [0, 0, 0, 0];
|
||||
private warnedTwoTint: boolean = false;
|
||||
|
||||
constructor () {
|
||||
const geometry = new MeshGeometry();
|
||||
|
||||
geometry.getBuffer("aVertexPosition").static = false;
|
||||
geometry.getBuffer("aTextureCoord").static = false;
|
||||
|
||||
const meshMaterial = new MeshMaterial(Texture.EMPTY);
|
||||
super(geometry, meshMaterial);
|
||||
}
|
||||
public updateFromSpineData (
|
||||
slotTexture: SpineTexture,
|
||||
slotBlendMode: BlendMode,
|
||||
slotName: string,
|
||||
finalVertices: NumberArrayLike,
|
||||
finalVerticesLength: number,
|
||||
finalIndices: NumberArrayLike,
|
||||
finalIndicesLength: number,
|
||||
darkTint: boolean
|
||||
): void {
|
||||
this.texture = slotTexture.texture;
|
||||
|
||||
const vertLenght = (finalVerticesLength / (darkTint ? 12 : 8)) * 2;
|
||||
|
||||
if (this.geometry.getBuffer("aTextureCoord").data?.length !== vertLenght) {
|
||||
this.geometry.getBuffer("aTextureCoord").data = new Float32Array(vertLenght);
|
||||
}
|
||||
|
||||
if (this.geometry.getBuffer("aVertexPosition").data?.length !== vertLenght) {
|
||||
this.geometry.getBuffer("aVertexPosition").data = new Float32Array(vertLenght);
|
||||
}
|
||||
|
||||
let vertIndex = 0;
|
||||
|
||||
for (let i = 0; i < finalVerticesLength; i += darkTint ? 12 : 8) {
|
||||
let auxi = i;
|
||||
|
||||
this.geometry.getBuffer("aVertexPosition").data[vertIndex] = finalVertices[auxi++];
|
||||
this.geometry.getBuffer("aVertexPosition").data[vertIndex + 1] = finalVertices[auxi++];
|
||||
|
||||
auxi += 4; // color
|
||||
|
||||
this.geometry.getBuffer("aTextureCoord").data[vertIndex] = finalVertices[auxi++];
|
||||
this.geometry.getBuffer("aTextureCoord").data[vertIndex + 1] = finalVertices[auxi++];
|
||||
|
||||
vertIndex += 2;
|
||||
}
|
||||
|
||||
// console.log(vertLenght, auxVert.length);
|
||||
|
||||
if (darkTint && !this.warnedTwoTint) {
|
||||
console.warn("DarkTint is not enabled by default. To enable use a DarkSlotMesh factory while creating the Spine object.");
|
||||
this.warnedTwoTint = true;
|
||||
}
|
||||
|
||||
SlotMesh.auxColor[0] = finalVertices[2];
|
||||
SlotMesh.auxColor[1] = finalVertices[3];
|
||||
SlotMesh.auxColor[2] = finalVertices[4];
|
||||
SlotMesh.auxColor[3] = finalVertices[5];
|
||||
|
||||
this.tint = SlotMesh.auxColor;
|
||||
this.blendMode = SpineTexture.toPixiBlending(slotBlendMode);
|
||||
|
||||
if (this.geometry.indexBuffer.data.length !== finalIndices.length) {
|
||||
this.geometry.indexBuffer.data = new Uint32Array(finalIndices);
|
||||
} else {
|
||||
for (let i = 0; i < finalIndicesLength; i++) {
|
||||
this.geometry.indexBuffer.data[i] = finalIndices[i];
|
||||
}
|
||||
}
|
||||
|
||||
this.name = slotName;
|
||||
|
||||
this.geometry.getBuffer("aVertexPosition").update();
|
||||
this.geometry.getBuffer("aTextureCoord").update();
|
||||
this.geometry.indexBuffer.update();
|
||||
}
|
||||
}
|
||||
365
spine-ts/spine-pixi/src/Spine.ts
Normal file
365
spine-ts/spine-pixi/src/Spine.ts
Normal file
@ -0,0 +1,365 @@
|
||||
import type { BlendMode, Bone, Event, NumberArrayLike, SkeletonData, Slot, TextureAtlas, TrackEntry } from "@esotericsoftware/spine-core";
|
||||
import {
|
||||
AnimationState,
|
||||
AnimationStateData,
|
||||
AtlasAttachmentLoader,
|
||||
ClippingAttachment,
|
||||
Color,
|
||||
MeshAttachment,
|
||||
RegionAttachment,
|
||||
Skeleton,
|
||||
SkeletonBinary,
|
||||
SkeletonClipping,
|
||||
SkeletonJson,
|
||||
Utils,
|
||||
Vector2,
|
||||
} from "@esotericsoftware/spine-core";
|
||||
import type { SpineTexture } from "./SpineTexture";
|
||||
import { SlotMesh } from "./SlotMesh";
|
||||
import type { ISpineDebugRenderer } from "./SpineDebugRenderer";
|
||||
import { Assets } from "@pixi/assets";
|
||||
import type { IPointData } from "@pixi/core";
|
||||
import { Ticker, utils } from "@pixi/core";
|
||||
import type { IDestroyOptions, DisplayObject } from "@pixi/display";
|
||||
import { Container } from "@pixi/display";
|
||||
|
||||
export interface ISpineOptions {
|
||||
autoUpdate?: boolean;
|
||||
slotMeshFactory?: () => ISlotMesh;
|
||||
}
|
||||
|
||||
export interface SpineEvents {
|
||||
complete: [trackEntry: TrackEntry];
|
||||
dispose: [trackEntry: TrackEntry];
|
||||
end: [trackEntry: TrackEntry];
|
||||
event: [trackEntry: TrackEntry, event: Event];
|
||||
interrupt: [trackEntry: TrackEntry];
|
||||
start: [trackEntry: TrackEntry];
|
||||
}
|
||||
|
||||
export class Spine extends Container {
|
||||
public skeleton: Skeleton;
|
||||
public state: AnimationState;
|
||||
|
||||
private _debug?: ISpineDebugRenderer | undefined = undefined;
|
||||
public get debug (): ISpineDebugRenderer | undefined {
|
||||
return this._debug;
|
||||
}
|
||||
public set debug (value: ISpineDebugRenderer | undefined) {
|
||||
if (this._debug) {
|
||||
this._debug.unregisterSpine(this);
|
||||
}
|
||||
if (value) {
|
||||
value.registerSpine(this);
|
||||
}
|
||||
this._debug = value;
|
||||
}
|
||||
|
||||
protected slotMeshFactory: () => ISlotMesh;
|
||||
|
||||
private autoUpdateWarned: boolean = false;
|
||||
private _autoUpdate: boolean = true;
|
||||
public get autoUpdate (): boolean {
|
||||
return this._autoUpdate;
|
||||
}
|
||||
public set autoUpdate (value: boolean) {
|
||||
if (value) {
|
||||
Ticker.shared.add(this.internalUpdate, this);
|
||||
this.autoUpdateWarned = false;
|
||||
} else {
|
||||
Ticker.shared.remove(this.internalUpdate, this);
|
||||
}
|
||||
this._autoUpdate = value;
|
||||
}
|
||||
|
||||
private meshesCache = new Map<Slot, ISlotMesh>();
|
||||
|
||||
private static vectorAux: Vector2 = new Vector2();
|
||||
private static clipper: SkeletonClipping = new SkeletonClipping();
|
||||
|
||||
private static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
|
||||
private static VERTEX_SIZE = 2 + 2 + 4;
|
||||
private static DARK_VERTEX_SIZE = 2 + 2 + 4 + 4;
|
||||
|
||||
private lightColor = new Color();
|
||||
private darkColor = new Color();
|
||||
|
||||
|
||||
constructor (skeletonData: SkeletonData, options?: ISpineOptions) {
|
||||
super();
|
||||
|
||||
this.skeleton = new Skeleton(skeletonData);
|
||||
const animData = new AnimationStateData(skeletonData);
|
||||
this.state = new AnimationState(animData);
|
||||
this.autoUpdate = options?.autoUpdate ?? true;
|
||||
this.slotMeshFactory = options?.slotMeshFactory ?? ((): ISlotMesh => new SlotMesh());
|
||||
|
||||
|
||||
/**
|
||||
* This is locked behind https://github.com/pixijs/pixijs/issues/8957
|
||||
* I don't want to make a custom event emitter and do `this.spineEvents.on` because that's just as "far" as `this.state.addListener`
|
||||
* So, until pixi fixes the custom event system, I'll stick to spine native events. - @miltoncandelero
|
||||
|
||||
this.spineListeners = {
|
||||
complete: (trackEntry) => this.emit("complete", trackEntry),
|
||||
dispose: (trackEntry) => this.emit("dispose", trackEntry),
|
||||
end: (trackEntry) => this.emit("end", trackEntry),
|
||||
event: (trackEntry, event) => this.emit("event", trackEntry, event),
|
||||
interrupt: (trackEntry) => this.emit("interrupt", trackEntry),
|
||||
start: (trackEntry) => this.emit("start", trackEntry),
|
||||
};
|
||||
this.state.addListener(this.spineListeners);
|
||||
*/
|
||||
}
|
||||
|
||||
public update (deltaSeconds: number): void {
|
||||
if (this.autoUpdate && !this.autoUpdateWarned) {
|
||||
console.warn("You are calling update on a Spine instance that has autoUpdate set to true. This is probably not what you want.");
|
||||
this.autoUpdateWarned = true;
|
||||
}
|
||||
this.internalUpdate(0, deltaSeconds);
|
||||
}
|
||||
|
||||
protected internalUpdate (_deltaFrame: number, deltaSeconds?: number): void {
|
||||
// Because reasons, pixi uses deltaFrames at 60fps. We ignore the default deltaFrames and use the deltaSeconds from pixi ticker.
|
||||
this.state.update(deltaSeconds ?? Ticker.shared.deltaMS / 1000);
|
||||
}
|
||||
|
||||
public override updateTransform (): void {
|
||||
this.updateSpineTransform();
|
||||
this.debug?.renderDebug(this);
|
||||
super.updateTransform();
|
||||
}
|
||||
|
||||
protected updateSpineTransform (): void {
|
||||
// if I ever create the linked spines, this will be useful.
|
||||
|
||||
this.state.apply(this.skeleton);
|
||||
this.skeleton.updateWorldTransform();
|
||||
this.updateGeometry();
|
||||
this.sortChildren();
|
||||
}
|
||||
|
||||
public override destroy (options?: boolean | IDestroyOptions | undefined): void {
|
||||
for (const [, mesh] of this.meshesCache) {
|
||||
mesh?.destroy();
|
||||
}
|
||||
this.state.clearListeners();
|
||||
this.debug = undefined;
|
||||
this.meshesCache.clear();
|
||||
super.destroy(options);
|
||||
}
|
||||
|
||||
private resetMeshes (): void {
|
||||
for (const [, mesh] of this.meshesCache) {
|
||||
mesh.zIndex = -1;
|
||||
mesh.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If you want to manually handle which meshes go on which slot and how you cache, overwrite this method.
|
||||
*/
|
||||
protected getMeshForSlot (slot: Slot): ISlotMesh {
|
||||
if (!this.meshesCache.has(slot)) {
|
||||
let mesh = this.slotMeshFactory();
|
||||
this.addChild(mesh);
|
||||
this.meshesCache.set(slot, mesh);
|
||||
return mesh;
|
||||
} else {
|
||||
let mesh = this.meshesCache.get(slot)!;
|
||||
mesh.visible = true;
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
|
||||
private verticesCache: NumberArrayLike = Utils.newFloatArray(1024);
|
||||
|
||||
private updateGeometry (): void {
|
||||
this.resetMeshes();
|
||||
|
||||
let triangles: Array<number> | null = null;
|
||||
let uvs: NumberArrayLike | null = null;
|
||||
const drawOrder = this.skeleton.drawOrder;
|
||||
|
||||
for (let i = 0, n = drawOrder.length; i < n; i++) {
|
||||
const slot = drawOrder[i];
|
||||
const useDarkColor = slot.darkColor != null;
|
||||
const vertexSize = Spine.clipper.isClipping() ? 2 : useDarkColor ? Spine.DARK_VERTEX_SIZE : Spine.VERTEX_SIZE;
|
||||
if (!slot.bone.active) {
|
||||
Spine.clipper.clipEndWithSlot(slot);
|
||||
continue;
|
||||
}
|
||||
const attachment = slot.getAttachment();
|
||||
let attachmentColor: Color | null;
|
||||
let texture: SpineTexture | null;
|
||||
let numFloats = 0;
|
||||
if (attachment instanceof RegionAttachment) {
|
||||
const region = attachment;
|
||||
attachmentColor = region.color;
|
||||
numFloats = vertexSize * 4;
|
||||
region.computeWorldVertices(slot, this.verticesCache, 0, vertexSize);
|
||||
triangles = Spine.QUAD_TRIANGLES;
|
||||
uvs = region.uvs;
|
||||
texture = <SpineTexture>region.region?.texture;
|
||||
} else if (attachment instanceof MeshAttachment) {
|
||||
const mesh = attachment;
|
||||
attachmentColor = mesh.color;
|
||||
numFloats = (mesh.worldVerticesLength >> 1) * vertexSize;
|
||||
if (numFloats > this.verticesCache.length) {
|
||||
this.verticesCache = Utils.newFloatArray(numFloats);
|
||||
}
|
||||
mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, this.verticesCache, 0, vertexSize);
|
||||
triangles = mesh.triangles;
|
||||
uvs = mesh.uvs;
|
||||
texture = <SpineTexture>mesh.region?.texture;
|
||||
} else if (attachment instanceof ClippingAttachment) {
|
||||
Spine.clipper.clipStart(slot, attachment);
|
||||
continue;
|
||||
} else {
|
||||
Spine.clipper.clipEndWithSlot(slot);
|
||||
continue;
|
||||
}
|
||||
if (texture != null) {
|
||||
const skeleton = slot.bone.skeleton;
|
||||
const skeletonColor = skeleton.color;
|
||||
const slotColor = slot.color;
|
||||
const alpha = skeletonColor.a * slotColor.a * attachmentColor.a;
|
||||
this.lightColor.set(
|
||||
skeletonColor.r * slotColor.r * attachmentColor.r,
|
||||
skeletonColor.g * slotColor.g * attachmentColor.g,
|
||||
skeletonColor.b * slotColor.b * attachmentColor.b,
|
||||
alpha
|
||||
);
|
||||
if (slot.darkColor != null) {
|
||||
this.darkColor.setFromColor(slot.darkColor);
|
||||
} else {
|
||||
this.darkColor.set(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
let finalVertices: NumberArrayLike;
|
||||
let finalVerticesLength: number;
|
||||
let finalIndices: NumberArrayLike;
|
||||
let finalIndicesLength: number;
|
||||
|
||||
if (Spine.clipper.isClipping()) {
|
||||
Spine.clipper.clipTriangles(this.verticesCache, numFloats, triangles, triangles.length, uvs, this.lightColor, this.darkColor, useDarkColor);
|
||||
|
||||
finalVertices = Spine.clipper.clippedVertices;
|
||||
finalVerticesLength = finalVertices.length;
|
||||
|
||||
finalIndices = Spine.clipper.clippedTriangles;
|
||||
finalIndicesLength = finalIndices.length;
|
||||
} else {
|
||||
const verts = this.verticesCache;
|
||||
for (let v = 2, u = 0, n = numFloats; v < n; v += vertexSize, u += 2) {
|
||||
let tempV = v;
|
||||
verts[tempV++] = this.lightColor.r;
|
||||
verts[tempV++] = this.lightColor.g;
|
||||
verts[tempV++] = this.lightColor.b;
|
||||
verts[tempV++] = this.lightColor.a;
|
||||
|
||||
verts[tempV++] = uvs[u];
|
||||
verts[tempV++] = uvs[u + 1];
|
||||
|
||||
if (useDarkColor) {
|
||||
verts[tempV++] = this.darkColor.r;
|
||||
verts[tempV++] = this.darkColor.g;
|
||||
verts[tempV++] = this.darkColor.b;
|
||||
}
|
||||
}
|
||||
finalVertices = this.verticesCache;
|
||||
finalVerticesLength = numFloats;
|
||||
finalIndices = triangles;
|
||||
finalIndicesLength = triangles.length;
|
||||
}
|
||||
|
||||
if (finalVerticesLength == 0 || finalIndicesLength == 0) {
|
||||
Spine.clipper.clipEndWithSlot(slot);
|
||||
continue;
|
||||
}
|
||||
|
||||
const mesh = this.getMeshForSlot(slot);
|
||||
mesh.zIndex = i;
|
||||
mesh.updateFromSpineData(texture, slot.data.blendMode, slot.data.name, finalVertices, finalVerticesLength, finalIndices, finalIndicesLength, useDarkColor);
|
||||
}
|
||||
|
||||
Spine.clipper.clipEndWithSlot(slot);
|
||||
}
|
||||
Spine.clipper.clipEnd();
|
||||
}
|
||||
|
||||
public setBonePosition (bone: string | Bone, position: IPointData): void {
|
||||
const boneAux = bone;
|
||||
if (typeof bone === "string") {
|
||||
bone = this.skeleton.findBone(bone)!;
|
||||
}
|
||||
|
||||
if (!bone) throw Error(`Cant set bone position, bone ${String(boneAux)} not found`);
|
||||
Spine.vectorAux.set(position.x, position.y);
|
||||
|
||||
if (bone.parent) {
|
||||
const aux = bone.parent.worldToLocal(Spine.vectorAux);
|
||||
bone.x = aux.x;
|
||||
bone.y = aux.y;
|
||||
}
|
||||
else {
|
||||
bone.x = Spine.vectorAux.x;
|
||||
bone.y = Spine.vectorAux.y;
|
||||
}
|
||||
}
|
||||
|
||||
public getBonePosition (bone: string | Bone, outPos?: IPointData): IPointData | undefined {
|
||||
const boneAux = bone;
|
||||
if (typeof bone === "string") {
|
||||
bone = this.skeleton.findBone(bone)!;
|
||||
}
|
||||
|
||||
if (!bone) {
|
||||
console.error(`Cant set bone position! Bone ${String(boneAux)} not found`);
|
||||
return outPos;
|
||||
}
|
||||
|
||||
if (!outPos) {
|
||||
outPos = { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
outPos.x = bone.worldX;
|
||||
outPos.y = bone.worldY;
|
||||
return outPos;
|
||||
}
|
||||
|
||||
public static readonly skeletonCache: Record<string, SkeletonData> = Object.create(null);
|
||||
|
||||
public static from (skeletonAssetName: string, atlasAssetName: string, options?: ISpineOptions & { scale?: number }): Spine {
|
||||
const cacheKey = `${skeletonAssetName}-${atlasAssetName}-${options?.scale ?? 1}`;
|
||||
let skeletonData = Spine.skeletonCache[cacheKey];
|
||||
if (skeletonData) {
|
||||
return new Spine(skeletonData, options);
|
||||
}
|
||||
const skeletonAsset = Assets.get<any | Uint8Array>(skeletonAssetName);
|
||||
const atlasAsset = Assets.get<TextureAtlas>(atlasAssetName);
|
||||
const attachmentLoader = new AtlasAttachmentLoader(atlasAsset);
|
||||
let parser = skeletonAsset instanceof Uint8Array ? new SkeletonBinary(attachmentLoader) : new SkeletonJson(attachmentLoader);
|
||||
parser.scale = options?.scale ?? 1;
|
||||
skeletonData = parser.readSkeletonData(skeletonAsset);
|
||||
Spine.skeletonCache[cacheKey] = skeletonData;
|
||||
return new this(skeletonData, options);
|
||||
}
|
||||
}
|
||||
|
||||
Skeleton.yDown = true;
|
||||
|
||||
export interface ISlotMesh extends DisplayObject {
|
||||
name: string;
|
||||
updateFromSpineData (
|
||||
slotTexture: SpineTexture,
|
||||
slotBlendMode: BlendMode,
|
||||
slotName: string,
|
||||
finalVertices: NumberArrayLike,
|
||||
finalVerticesLength: number,
|
||||
finalIndices: NumberArrayLike,
|
||||
finalIndicesLength: number,
|
||||
darkTint: boolean
|
||||
): void;
|
||||
}
|
||||
543
spine-ts/spine-pixi/src/SpineDebugRenderer.ts
Normal file
543
spine-ts/spine-pixi/src/SpineDebugRenderer.ts
Normal file
@ -0,0 +1,543 @@
|
||||
import { Container } from "@pixi/display";
|
||||
import { Graphics } from "@pixi/graphics";
|
||||
import { Text } from "@pixi/text";
|
||||
import type { Spine } from "./Spine";
|
||||
import type { AnimationStateListener } from "@esotericsoftware/spine-core";
|
||||
import { ClippingAttachment, MeshAttachment, PathAttachment, RegionAttachment, SkeletonBounds } from "@esotericsoftware/spine-core";
|
||||
|
||||
/**
|
||||
* Make a class that extends from this interface to create your own debug renderer.
|
||||
* @public
|
||||
*/
|
||||
export interface ISpineDebugRenderer {
|
||||
/**
|
||||
* This will be called every frame, after the spine has been updated.
|
||||
*/
|
||||
renderDebug (spine: Spine): void;
|
||||
|
||||
/**
|
||||
* This is called when the `spine.debug` object is set to null or when the spine is destroyed.
|
||||
*/
|
||||
unregisterSpine (spine: Spine): void;
|
||||
|
||||
/**
|
||||
* This is called when the `spine.debug` object is set to a new instance of a debug renderer.
|
||||
*/
|
||||
registerSpine (spine: Spine): void;
|
||||
}
|
||||
|
||||
type DebugDisplayObjects = {
|
||||
bones: Container;
|
||||
skeletonXY: Graphics;
|
||||
regionAttachmentsShape: Graphics;
|
||||
meshTrianglesLine: Graphics;
|
||||
meshHullLine: Graphics;
|
||||
clippingPolygon: Graphics;
|
||||
boundingBoxesRect: Graphics;
|
||||
boundingBoxesCircle: Graphics;
|
||||
boundingBoxesPolygon: Graphics;
|
||||
pathsCurve: Graphics;
|
||||
pathsLine: Graphics;
|
||||
parentDebugContainer: Container;
|
||||
eventText: Container;
|
||||
eventCallback: AnimationStateListener;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a debug renderer that uses PixiJS Graphics under the hood.
|
||||
* @public
|
||||
*/
|
||||
export class SpineDebugRenderer implements ISpineDebugRenderer {
|
||||
private registeredSpines: Map<Spine, DebugDisplayObjects> = new Map();
|
||||
|
||||
public drawMeshHull = true;
|
||||
public drawMeshTriangles = true;
|
||||
public drawBones = true;
|
||||
public drawPaths = true;
|
||||
public drawBoundingBoxes = true;
|
||||
public drawClipping = true;
|
||||
public drawRegionAttachments = true;
|
||||
public drawEvents = true;
|
||||
|
||||
public lineWidth = 1;
|
||||
public regionAttachmentsColor = 0x0078ff;
|
||||
public meshHullColor = 0x0078ff;
|
||||
public meshTrianglesColor = 0xffcc00;
|
||||
public clippingPolygonColor = 0xff00ff;
|
||||
public boundingBoxesRectColor = 0x00ff00;
|
||||
public boundingBoxesPolygonColor = 0x00ff00;
|
||||
public boundingBoxesCircleColor = 0x00ff00;
|
||||
public pathsCurveColor = 0xff0000;
|
||||
public pathsLineColor = 0xff00ff;
|
||||
public skeletonXYColor = 0xff0000;
|
||||
public bonesColor = 0x00eecc;
|
||||
public eventFontSize: number = 24;
|
||||
public eventFontColor: number = 0x0;
|
||||
|
||||
/**
|
||||
* The debug is attached by force to each spine object. So we need to create it inside the spine when we get the first update
|
||||
*/
|
||||
public registerSpine (spine: Spine): void {
|
||||
if (this.registeredSpines.has(spine)) {
|
||||
console.warn("SpineDebugRenderer.registerSpine() - this spine is already registered!", spine);
|
||||
return;
|
||||
}
|
||||
const debugDisplayObjects: DebugDisplayObjects = {
|
||||
parentDebugContainer: new Container(),
|
||||
bones: new Container(),
|
||||
skeletonXY: new Graphics(),
|
||||
regionAttachmentsShape: new Graphics(),
|
||||
meshTrianglesLine: new Graphics(),
|
||||
meshHullLine: new Graphics(),
|
||||
clippingPolygon: new Graphics(),
|
||||
boundingBoxesRect: new Graphics(),
|
||||
boundingBoxesCircle: new Graphics(),
|
||||
boundingBoxesPolygon: new Graphics(),
|
||||
pathsCurve: new Graphics(),
|
||||
pathsLine: new Graphics(),
|
||||
eventText: new Container(),
|
||||
eventCallback: {
|
||||
event: (_, event) => {
|
||||
if (this.drawEvents) {
|
||||
const scale = Math.abs(spine.scale.x || spine.scale.y || 1);
|
||||
const text = new Text(event.data.name, { fontSize: this.eventFontSize / scale, fill: this.eventFontColor, fontFamily: "monospace" });
|
||||
text.scale.x = Math.sign(spine.scale.x);
|
||||
text.anchor.set(0.5);
|
||||
debugDisplayObjects.eventText.addChild(text);
|
||||
setTimeout(() => {
|
||||
if (!text.destroyed) {
|
||||
text.destroy();
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.bones);
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.skeletonXY);
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.regionAttachmentsShape);
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.meshTrianglesLine);
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.meshHullLine);
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.clippingPolygon);
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.boundingBoxesRect);
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.boundingBoxesCircle);
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.boundingBoxesPolygon);
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.pathsCurve);
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.pathsLine);
|
||||
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.eventText);
|
||||
|
||||
debugDisplayObjects.parentDebugContainer.zIndex = 9999999;
|
||||
|
||||
// Disable screen reader and mouse input on debug objects.
|
||||
(debugDisplayObjects.parentDebugContainer as any).accessibleChildren = false;
|
||||
(debugDisplayObjects.parentDebugContainer as any).eventMode = "none";
|
||||
(debugDisplayObjects.parentDebugContainer as any).interactiveChildren = false;
|
||||
|
||||
spine.addChild(debugDisplayObjects.parentDebugContainer);
|
||||
|
||||
spine.state.addListener(debugDisplayObjects.eventCallback);
|
||||
|
||||
this.registeredSpines.set(spine, debugDisplayObjects);
|
||||
}
|
||||
public renderDebug (spine: Spine): void {
|
||||
if (!this.registeredSpines.has(spine)) {
|
||||
// This should never happen. Spines are registered when you assign spine.debug
|
||||
this.registerSpine(spine);
|
||||
}
|
||||
|
||||
const debugDisplayObjects = this.registeredSpines.get(spine);
|
||||
|
||||
if (!debugDisplayObjects) {
|
||||
return;
|
||||
}
|
||||
spine.addChild(debugDisplayObjects.parentDebugContainer);
|
||||
|
||||
debugDisplayObjects.skeletonXY.clear();
|
||||
debugDisplayObjects.regionAttachmentsShape.clear();
|
||||
debugDisplayObjects.meshTrianglesLine.clear();
|
||||
debugDisplayObjects.meshHullLine.clear();
|
||||
debugDisplayObjects.clippingPolygon.clear();
|
||||
debugDisplayObjects.boundingBoxesRect.clear();
|
||||
debugDisplayObjects.boundingBoxesCircle.clear();
|
||||
debugDisplayObjects.boundingBoxesPolygon.clear();
|
||||
debugDisplayObjects.pathsCurve.clear();
|
||||
debugDisplayObjects.pathsLine.clear();
|
||||
|
||||
for (let len = debugDisplayObjects.bones.children.length; len > 0; len--) {
|
||||
debugDisplayObjects.bones.children[len - 1].destroy({ children: true, texture: true, baseTexture: true });
|
||||
}
|
||||
|
||||
const scale = Math.abs(spine.scale.x || spine.scale.y || 1);
|
||||
const lineWidth = this.lineWidth / scale;
|
||||
|
||||
if (this.drawBones) {
|
||||
this.drawBonesFunc(spine, debugDisplayObjects, lineWidth, scale);
|
||||
}
|
||||
|
||||
if (this.drawPaths) {
|
||||
this.drawPathsFunc(spine, debugDisplayObjects, lineWidth);
|
||||
}
|
||||
|
||||
if (this.drawBoundingBoxes) {
|
||||
this.drawBoundingBoxesFunc(spine, debugDisplayObjects, lineWidth);
|
||||
}
|
||||
|
||||
if (this.drawClipping) {
|
||||
this.drawClippingFunc(spine, debugDisplayObjects, lineWidth);
|
||||
}
|
||||
|
||||
if (this.drawMeshHull || this.drawMeshTriangles) {
|
||||
this.drawMeshHullAndMeshTriangles(spine, debugDisplayObjects, lineWidth);
|
||||
}
|
||||
|
||||
if (this.drawRegionAttachments) {
|
||||
this.drawRegionAttachmentsFunc(spine, debugDisplayObjects, lineWidth);
|
||||
}
|
||||
|
||||
if (this.drawEvents) {
|
||||
for (const child of debugDisplayObjects.eventText.children) {
|
||||
child.alpha -= 0.05;
|
||||
child.y -= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private drawBonesFunc (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number, scale: number): void {
|
||||
const skeleton = spine.skeleton;
|
||||
const skeletonX = skeleton.x;
|
||||
const skeletonY = skeleton.y;
|
||||
const bones = skeleton.bones;
|
||||
|
||||
debugDisplayObjects.skeletonXY.lineStyle(lineWidth, this.skeletonXYColor, 1);
|
||||
|
||||
for (let i = 0, len = bones.length; i < len; i++) {
|
||||
const bone = bones[i];
|
||||
const boneLen = bone.data.length;
|
||||
const starX = skeletonX + bone.worldX;
|
||||
const starY = skeletonY + bone.worldY;
|
||||
const endX = skeletonX + boneLen * bone.a + bone.worldX;
|
||||
const endY = skeletonY + boneLen * bone.b + bone.worldY;
|
||||
|
||||
if (bone.data.name === "root" || bone.data.parent === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const w = Math.abs(starX - endX);
|
||||
const h = Math.abs(starY - endY);
|
||||
// a = w, // side length a
|
||||
const a2 = Math.pow(w, 2); // square root of side length a
|
||||
const b = h; // side length b
|
||||
const b2 = Math.pow(h, 2); // square root of side length b
|
||||
const c = Math.sqrt(a2 + b2); // side length c
|
||||
const c2 = Math.pow(c, 2); // square root of side length c
|
||||
const rad = Math.PI / 180;
|
||||
// A = Math.acos([a2 + c2 - b2] / [2 * a * c]) || 0, // Angle A
|
||||
// C = Math.acos([a2 + b2 - c2] / [2 * a * b]) || 0, // C angle
|
||||
const B = Math.acos((c2 + b2 - a2) / (2 * b * c)) || 0; // angle of corner B
|
||||
|
||||
if (c === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const gp = new Graphics();
|
||||
|
||||
debugDisplayObjects.bones.addChild(gp);
|
||||
|
||||
// draw bone
|
||||
const refRation = c / 50 / scale;
|
||||
|
||||
gp.beginFill(this.bonesColor, 1);
|
||||
gp.drawPolygon(0, 0, 0 - refRation, c - refRation * 3, 0, c - refRation, 0 + refRation, c - refRation * 3);
|
||||
gp.endFill();
|
||||
gp.x = starX;
|
||||
gp.y = starY;
|
||||
gp.pivot.y = c;
|
||||
|
||||
// Calculate bone rotation angle
|
||||
let rotation = 0;
|
||||
|
||||
if (starX < endX && starY < endY) {
|
||||
// bottom right
|
||||
rotation = -B + 180 * rad;
|
||||
} else if (starX > endX && starY < endY) {
|
||||
// bottom left
|
||||
rotation = 180 * rad + B;
|
||||
} else if (starX > endX && starY > endY) {
|
||||
// top left
|
||||
rotation = -B;
|
||||
} else if (starX < endX && starY > endY) {
|
||||
// bottom left
|
||||
rotation = B;
|
||||
} else if (starY === endY && starX < endX) {
|
||||
// To the right
|
||||
rotation = 90 * rad;
|
||||
} else if (starY === endY && starX > endX) {
|
||||
// go left
|
||||
rotation = -90 * rad;
|
||||
} else if (starX === endX && starY < endY) {
|
||||
// down
|
||||
rotation = 180 * rad;
|
||||
} else if (starX === endX && starY > endY) {
|
||||
// up
|
||||
rotation = 0;
|
||||
}
|
||||
gp.rotation = rotation;
|
||||
|
||||
// Draw the starting rotation point of the bone
|
||||
gp.lineStyle(lineWidth + refRation / 2.4, this.bonesColor, 1);
|
||||
gp.beginFill(0x000000, 0.6);
|
||||
gp.drawCircle(0, c, refRation * 1.2);
|
||||
gp.endFill();
|
||||
}
|
||||
|
||||
// Draw the skeleton starting point "X" form
|
||||
const startDotSize = lineWidth * 3;
|
||||
|
||||
debugDisplayObjects.skeletonXY.moveTo(skeletonX - startDotSize, skeletonY - startDotSize);
|
||||
debugDisplayObjects.skeletonXY.lineTo(skeletonX + startDotSize, skeletonY + startDotSize);
|
||||
debugDisplayObjects.skeletonXY.moveTo(skeletonX + startDotSize, skeletonY - startDotSize);
|
||||
debugDisplayObjects.skeletonXY.lineTo(skeletonX - startDotSize, skeletonY + startDotSize);
|
||||
}
|
||||
|
||||
private drawRegionAttachmentsFunc (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void {
|
||||
const skeleton = spine.skeleton;
|
||||
const slots = skeleton.slots;
|
||||
|
||||
debugDisplayObjects.regionAttachmentsShape.lineStyle(lineWidth, this.regionAttachmentsColor, 1);
|
||||
|
||||
for (let i = 0, len = slots.length; i < len; i++) {
|
||||
const slot = slots[i];
|
||||
const attachment = slot.getAttachment();
|
||||
|
||||
if (attachment == null || !(attachment instanceof RegionAttachment)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const regionAttachment = attachment;
|
||||
|
||||
const vertices = new Float32Array(8);
|
||||
|
||||
regionAttachment.computeWorldVertices(slot, vertices, 0, 2);
|
||||
debugDisplayObjects.regionAttachmentsShape.drawPolygon(Array.from(vertices.slice(0, 8)));
|
||||
}
|
||||
}
|
||||
|
||||
private drawMeshHullAndMeshTriangles (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void {
|
||||
const skeleton = spine.skeleton;
|
||||
const slots = skeleton.slots;
|
||||
|
||||
debugDisplayObjects.meshHullLine.lineStyle(lineWidth, this.meshHullColor, 1);
|
||||
debugDisplayObjects.meshTrianglesLine.lineStyle(lineWidth, this.meshTrianglesColor, 1);
|
||||
|
||||
for (let i = 0, len = slots.length; i < len; i++) {
|
||||
const slot = slots[i];
|
||||
|
||||
if (!slot.bone.active) {
|
||||
continue;
|
||||
}
|
||||
const attachment = slot.getAttachment();
|
||||
|
||||
if (attachment == null || !(attachment instanceof MeshAttachment)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const meshAttachment = attachment;
|
||||
|
||||
const vertices = new Float32Array(meshAttachment.worldVerticesLength);
|
||||
const triangles = meshAttachment.triangles;
|
||||
let hullLength = meshAttachment.hullLength;
|
||||
|
||||
meshAttachment.computeWorldVertices(slot, 0, meshAttachment.worldVerticesLength, vertices, 0, 2);
|
||||
// draw the skinned mesh (triangle)
|
||||
if (this.drawMeshTriangles) {
|
||||
for (let i = 0, len = triangles.length; i < len; i += 3) {
|
||||
const v1 = triangles[i] * 2;
|
||||
const v2 = triangles[i + 1] * 2;
|
||||
const v3 = triangles[i + 2] * 2;
|
||||
|
||||
debugDisplayObjects.meshTrianglesLine.moveTo(vertices[v1], vertices[v1 + 1]);
|
||||
debugDisplayObjects.meshTrianglesLine.lineTo(vertices[v2], vertices[v2 + 1]);
|
||||
debugDisplayObjects.meshTrianglesLine.lineTo(vertices[v3], vertices[v3 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// draw skin border
|
||||
if (this.drawMeshHull && hullLength > 0) {
|
||||
hullLength = (hullLength >> 1) * 2;
|
||||
let lastX = vertices[hullLength - 2];
|
||||
let lastY = vertices[hullLength - 1];
|
||||
|
||||
for (let i = 0, len = hullLength; i < len; i += 2) {
|
||||
const x = vertices[i];
|
||||
const y = vertices[i + 1];
|
||||
|
||||
debugDisplayObjects.meshHullLine.moveTo(x, y);
|
||||
debugDisplayObjects.meshHullLine.lineTo(lastX, lastY);
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private drawClippingFunc (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void {
|
||||
const skeleton = spine.skeleton;
|
||||
const slots = skeleton.slots;
|
||||
|
||||
debugDisplayObjects.clippingPolygon.lineStyle(lineWidth, this.clippingPolygonColor, 1);
|
||||
for (let i = 0, len = slots.length; i < len; i++) {
|
||||
const slot = slots[i];
|
||||
|
||||
if (!slot.bone.active) {
|
||||
continue;
|
||||
}
|
||||
const attachment = slot.getAttachment();
|
||||
|
||||
if (attachment == null || !(attachment instanceof ClippingAttachment)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const clippingAttachment = attachment;
|
||||
|
||||
const nn = clippingAttachment.worldVerticesLength;
|
||||
const world = new Float32Array(nn);
|
||||
|
||||
clippingAttachment.computeWorldVertices(slot, 0, nn, world, 0, 2);
|
||||
debugDisplayObjects.clippingPolygon.drawPolygon(Array.from(world));
|
||||
}
|
||||
}
|
||||
|
||||
private drawBoundingBoxesFunc (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void {
|
||||
// draw the total outline of the bounding box
|
||||
debugDisplayObjects.boundingBoxesRect.lineStyle(lineWidth, this.boundingBoxesRectColor, 5);
|
||||
|
||||
const bounds = new SkeletonBounds();
|
||||
|
||||
bounds.update(spine.skeleton, true);
|
||||
debugDisplayObjects.boundingBoxesRect.drawRect(bounds.minX, bounds.minY, bounds.getWidth(), bounds.getHeight());
|
||||
|
||||
const polygons = bounds.polygons;
|
||||
const drawPolygon = (polygonVertices: ArrayLike<number>, _offset: unknown, count: number): void => {
|
||||
debugDisplayObjects.boundingBoxesPolygon.lineStyle(lineWidth, this.boundingBoxesPolygonColor, 1);
|
||||
debugDisplayObjects.boundingBoxesPolygon.beginFill(this.boundingBoxesPolygonColor, 0.1);
|
||||
|
||||
if (count < 3) {
|
||||
throw new Error("Polygon must contain at least 3 vertices");
|
||||
}
|
||||
const paths = [];
|
||||
const dotSize = lineWidth * 2;
|
||||
|
||||
for (let i = 0, len = polygonVertices.length; i < len; i += 2) {
|
||||
const x1 = polygonVertices[i];
|
||||
const y1 = polygonVertices[i + 1];
|
||||
|
||||
// draw the bounding box node
|
||||
debugDisplayObjects.boundingBoxesCircle.lineStyle(0);
|
||||
debugDisplayObjects.boundingBoxesCircle.beginFill(this.boundingBoxesCircleColor);
|
||||
debugDisplayObjects.boundingBoxesCircle.drawCircle(x1, y1, dotSize);
|
||||
debugDisplayObjects.boundingBoxesCircle.endFill();
|
||||
|
||||
paths.push(x1, y1);
|
||||
}
|
||||
|
||||
// draw the bounding box area
|
||||
debugDisplayObjects.boundingBoxesPolygon.drawPolygon(paths);
|
||||
debugDisplayObjects.boundingBoxesPolygon.endFill();
|
||||
};
|
||||
|
||||
for (let i = 0, len = polygons.length; i < len; i++) {
|
||||
const polygon = polygons[i];
|
||||
|
||||
drawPolygon(polygon, 0, polygon.length);
|
||||
}
|
||||
}
|
||||
|
||||
private drawPathsFunc (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void {
|
||||
const skeleton = spine.skeleton;
|
||||
const slots = skeleton.slots;
|
||||
|
||||
debugDisplayObjects.pathsCurve.lineStyle(lineWidth, this.pathsCurveColor, 1);
|
||||
debugDisplayObjects.pathsLine.lineStyle(lineWidth, this.pathsLineColor, 1);
|
||||
|
||||
for (let i = 0, len = slots.length; i < len; i++) {
|
||||
const slot = slots[i];
|
||||
|
||||
if (!slot.bone.active) {
|
||||
continue;
|
||||
}
|
||||
const attachment = slot.getAttachment();
|
||||
|
||||
if (attachment == null || !(attachment instanceof PathAttachment)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pathAttachment = attachment;
|
||||
let nn = pathAttachment.worldVerticesLength;
|
||||
const world = new Float32Array(nn);
|
||||
|
||||
pathAttachment.computeWorldVertices(slot, 0, nn, world, 0, 2);
|
||||
let x1 = world[2];
|
||||
let y1 = world[3];
|
||||
let x2 = 0;
|
||||
let y2 = 0;
|
||||
|
||||
if (pathAttachment.closed) {
|
||||
const cx1 = world[0];
|
||||
const cy1 = world[1];
|
||||
const cx2 = world[nn - 2];
|
||||
const cy2 = world[nn - 1];
|
||||
|
||||
x2 = world[nn - 4];
|
||||
y2 = world[nn - 3];
|
||||
|
||||
// curve
|
||||
debugDisplayObjects.pathsCurve.moveTo(x1, y1);
|
||||
debugDisplayObjects.pathsCurve.bezierCurveTo(cx1, cy1, cx2, cy2, x2, y2);
|
||||
|
||||
// handle
|
||||
debugDisplayObjects.pathsLine.moveTo(x1, y1);
|
||||
debugDisplayObjects.pathsLine.lineTo(cx1, cy1);
|
||||
debugDisplayObjects.pathsLine.moveTo(x2, y2);
|
||||
debugDisplayObjects.pathsLine.lineTo(cx2, cy2);
|
||||
}
|
||||
nn -= 4;
|
||||
for (let ii = 4; ii < nn; ii += 6) {
|
||||
const cx1 = world[ii];
|
||||
const cy1 = world[ii + 1];
|
||||
const cx2 = world[ii + 2];
|
||||
const cy2 = world[ii + 3];
|
||||
|
||||
x2 = world[ii + 4];
|
||||
y2 = world[ii + 5];
|
||||
// curve
|
||||
debugDisplayObjects.pathsCurve.moveTo(x1, y1);
|
||||
debugDisplayObjects.pathsCurve.bezierCurveTo(cx1, cy1, cx2, cy2, x2, y2);
|
||||
|
||||
// handle
|
||||
debugDisplayObjects.pathsLine.moveTo(x1, y1);
|
||||
debugDisplayObjects.pathsLine.lineTo(cx1, cy1);
|
||||
debugDisplayObjects.pathsLine.moveTo(x2, y2);
|
||||
debugDisplayObjects.pathsLine.lineTo(cx2, cy2);
|
||||
x1 = x2;
|
||||
y1 = y2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unregisterSpine (spine: Spine): void {
|
||||
if (!this.registeredSpines.has(spine)) {
|
||||
console.warn("SpineDebugRenderer.unregisterSpine() - spine is not registered, can't unregister!", spine);
|
||||
}
|
||||
const debugDisplayObjects = this.registeredSpines.get(spine);
|
||||
|
||||
if (!debugDisplayObjects) {
|
||||
return;
|
||||
}
|
||||
|
||||
spine.state.removeListener(debugDisplayObjects.eventCallback);
|
||||
|
||||
debugDisplayObjects.parentDebugContainer.destroy({ baseTexture: true, children: true, texture: true });
|
||||
this.registeredSpines.delete(spine);
|
||||
}
|
||||
}
|
||||
109
spine-ts/spine-pixi/src/SpineTexture.ts
Normal file
109
spine-ts/spine-pixi/src/SpineTexture.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { BlendMode, Texture, TextureFilter, TextureWrap } from "@esotericsoftware/spine-core";
|
||||
import type { BaseTexture as PixiBaseTexture, BaseImageResource } from "@pixi/core";
|
||||
import { Texture as PixiTexture, SCALE_MODES, MIPMAP_MODES, WRAP_MODES, BLEND_MODES } from "@pixi/core";
|
||||
|
||||
export class SpineTexture extends Texture {
|
||||
private static textureMap: Map<PixiBaseTexture, SpineTexture> = new Map<PixiBaseTexture, SpineTexture>();
|
||||
|
||||
public static from (texture: PixiBaseTexture): SpineTexture {
|
||||
if (SpineTexture.textureMap.has(texture)) {
|
||||
return SpineTexture.textureMap.get(texture)!;
|
||||
}
|
||||
return new SpineTexture(texture);
|
||||
}
|
||||
|
||||
public readonly texture: PixiTexture;
|
||||
|
||||
private constructor (image: PixiBaseTexture) {
|
||||
// Todo: maybe add error handling if you feed a video texture to spine?
|
||||
super((image.resource as BaseImageResource).source as any);
|
||||
this.texture = PixiTexture.from(image);
|
||||
}
|
||||
|
||||
public setFilters (minFilter: TextureFilter, _magFilter: TextureFilter): void {
|
||||
this.texture.baseTexture.scaleMode = SpineTexture.toPixiTextureFilter(minFilter);
|
||||
this.texture.baseTexture.mipmap = SpineTexture.toPixiMipMap(minFilter);
|
||||
|
||||
// pixi only has one filter for both min and mag, too bad
|
||||
}
|
||||
|
||||
public setWraps (uWrap: TextureWrap, _vWrap: TextureWrap): void {
|
||||
this.texture.baseTexture.wrapMode = SpineTexture.toPixiTextureWrap(uWrap);
|
||||
|
||||
// Pixi only has one setting
|
||||
}
|
||||
|
||||
public dispose (): void {
|
||||
// I am not entirely sure about this...
|
||||
this.texture.destroy();
|
||||
}
|
||||
|
||||
private static toPixiTextureFilter (filter: TextureFilter): SCALE_MODES {
|
||||
switch (filter) {
|
||||
case TextureFilter.Nearest:
|
||||
case TextureFilter.MipMapNearestLinear:
|
||||
case TextureFilter.MipMapNearestNearest:
|
||||
return SCALE_MODES.NEAREST;
|
||||
|
||||
case TextureFilter.Linear:
|
||||
case TextureFilter.MipMapLinearLinear: // TextureFilter.MipMapLinearLinear == TextureFilter.MipMap
|
||||
case TextureFilter.MipMapLinearNearest:
|
||||
return SCALE_MODES.LINEAR;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown texture filter: ${String(filter)}`);
|
||||
}
|
||||
}
|
||||
|
||||
private static toPixiMipMap (filter: TextureFilter): MIPMAP_MODES {
|
||||
switch (filter) {
|
||||
case TextureFilter.Nearest:
|
||||
case TextureFilter.Linear:
|
||||
return MIPMAP_MODES.OFF;
|
||||
|
||||
case TextureFilter.MipMapNearestLinear:
|
||||
case TextureFilter.MipMapNearestNearest:
|
||||
case TextureFilter.MipMapLinearLinear: // TextureFilter.MipMapLinearLinear == TextureFilter.MipMap
|
||||
case TextureFilter.MipMapLinearNearest:
|
||||
return MIPMAP_MODES.ON;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown texture filter: ${String(filter)}`);
|
||||
}
|
||||
}
|
||||
|
||||
private static toPixiTextureWrap (wrap: TextureWrap): WRAP_MODES {
|
||||
switch (wrap) {
|
||||
case TextureWrap.ClampToEdge:
|
||||
return WRAP_MODES.CLAMP;
|
||||
|
||||
case TextureWrap.MirroredRepeat:
|
||||
return WRAP_MODES.MIRRORED_REPEAT;
|
||||
|
||||
case TextureWrap.Repeat:
|
||||
return WRAP_MODES.REPEAT;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown texture wrap: ${String(wrap)}`);
|
||||
}
|
||||
}
|
||||
|
||||
public static toPixiBlending (blend: BlendMode): BLEND_MODES {
|
||||
switch (blend) {
|
||||
case BlendMode.Normal:
|
||||
return BLEND_MODES.NORMAL;
|
||||
|
||||
case BlendMode.Additive:
|
||||
return BLEND_MODES.ADD;
|
||||
|
||||
case BlendMode.Multiply:
|
||||
return BLEND_MODES.MULTIPLY;
|
||||
|
||||
case BlendMode.Screen:
|
||||
return BLEND_MODES.SCREEN;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown blendMode: ${String(blend)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
93
spine-ts/spine-pixi/src/assets/atlasLoader.ts
Normal file
93
spine-ts/spine-pixi/src/assets/atlasLoader.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { TextureAtlas } from "@esotericsoftware/spine-core";
|
||||
import { SpineTexture } from "../SpineTexture";
|
||||
import type { AssetExtension, LoadAsset, Loader } from "@pixi/assets";
|
||||
import { LoaderParserPriority, checkExtension } from "@pixi/assets";
|
||||
import type { Texture } from "@pixi/core";
|
||||
import { ExtensionType, settings, utils, BaseTexture, extensions } from "@pixi/core";
|
||||
|
||||
type RawAtlas = string;
|
||||
|
||||
const spineTextureAtlasLoader: AssetExtension<RawAtlas | TextureAtlas, ISpineAtlasMetadata> = {
|
||||
extension: ExtensionType.Asset,
|
||||
|
||||
loader: {
|
||||
extension: {
|
||||
type: ExtensionType.LoadParser,
|
||||
priority: LoaderParserPriority.Normal,
|
||||
name: "spineTextureAtlasLoader",
|
||||
},
|
||||
|
||||
test(url: string): boolean {
|
||||
return checkExtension(url, ".atlas");
|
||||
},
|
||||
|
||||
async load(url: string): Promise<RawAtlas> {
|
||||
const response = await settings.ADAPTER.fetch(url);
|
||||
|
||||
const txt = await response.text();
|
||||
|
||||
return txt;
|
||||
},
|
||||
|
||||
testParse(asset: unknown, options: LoadAsset): Promise<boolean> {
|
||||
const isExtensionRight = checkExtension(options.src, ".atlas");
|
||||
const isString = typeof asset === "string";
|
||||
|
||||
return Promise.resolve(isExtensionRight && isString);
|
||||
},
|
||||
|
||||
unload(atlas: TextureAtlas) {
|
||||
atlas.dispose();
|
||||
},
|
||||
|
||||
async parse(asset: RawAtlas, options: LoadAsset, loader: Loader): Promise<TextureAtlas> {
|
||||
const metadata: ISpineAtlasMetadata = options.data || {};
|
||||
let basePath = utils.path.dirname(options.src);
|
||||
|
||||
if (basePath && basePath.lastIndexOf("/") !== basePath.length - 1) {
|
||||
basePath += "/";
|
||||
}
|
||||
|
||||
// Retval is going to be a texture atlas. However we need to wait for it's callback to resolve this promise.
|
||||
const retval = new TextureAtlas(asset);
|
||||
|
||||
// If the user gave me only one texture, that one is assumed to be the "first" texture in the atlas
|
||||
if (metadata.images instanceof BaseTexture || typeof metadata.images === "string") {
|
||||
const pixiTexture = metadata.images;
|
||||
metadata.images = {} as Record<string, BaseTexture | string>;
|
||||
metadata.images[retval.pages[0].name] = pixiTexture;
|
||||
}
|
||||
|
||||
// we will wait for all promises for the textures at the same time at the end.
|
||||
const textureLoadingPromises = [];
|
||||
|
||||
// fill the pages
|
||||
for (const page of retval.pages) {
|
||||
const pageName = page.name;
|
||||
const providedPage = metadata?.images ? metadata.images[pageName] : undefined;
|
||||
if (providedPage instanceof BaseTexture) {
|
||||
page.setTexture(SpineTexture.from(providedPage));
|
||||
} else {
|
||||
const url: string = providedPage ?? utils.path.normalize([...basePath.split(utils.path.sep), pageName].join(utils.path.sep));
|
||||
const pixiPromise = loader.load<Texture>({ src: url, data: metadata.imageMetadata }).then((texture) => {
|
||||
page.setTexture(SpineTexture.from(texture.baseTexture));
|
||||
});
|
||||
textureLoadingPromises.push(pixiPromise);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(textureLoadingPromises);
|
||||
|
||||
return retval;
|
||||
},
|
||||
},
|
||||
} as AssetExtension<RawAtlas | TextureAtlas, ISpineAtlasMetadata>;
|
||||
|
||||
extensions.add(spineTextureAtlasLoader);
|
||||
|
||||
export interface ISpineAtlasMetadata {
|
||||
// If you are downloading an .atlas file, this metadata will go to the Texture loader
|
||||
imageMetadata?: any;
|
||||
// If you already have atlas pages loaded as pixi textures and want to use that to create the atlas, you can pass them here
|
||||
images?: BaseTexture | string | Record<string, BaseTexture | string>;
|
||||
}
|
||||
45
spine-ts/spine-pixi/src/assets/skeletonLoader.ts
Normal file
45
spine-ts/spine-pixi/src/assets/skeletonLoader.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import type { AssetExtension, LoadAsset } from "@pixi/assets";
|
||||
import { LoaderParserPriority, checkExtension } from "@pixi/assets";
|
||||
import { ExtensionType, settings, extensions } from "@pixi/core";
|
||||
|
||||
type SkeletonJsonAsset = any;
|
||||
type SkeletonBinaryAsset = Uint8Array;
|
||||
|
||||
function isJson(resource: any): resource is SkeletonJsonAsset {
|
||||
return resource.hasOwnProperty("bones");
|
||||
}
|
||||
|
||||
function isBuffer(resource: any): resource is SkeletonBinaryAsset {
|
||||
return resource instanceof Uint8Array;
|
||||
}
|
||||
|
||||
const spineLoaderExtension: AssetExtension<SkeletonJsonAsset | SkeletonBinaryAsset> = {
|
||||
extension: ExtensionType.Asset,
|
||||
|
||||
loader: {
|
||||
extension: {
|
||||
type: ExtensionType.LoadParser,
|
||||
priority: LoaderParserPriority.Normal,
|
||||
},
|
||||
|
||||
test(url) {
|
||||
return checkExtension(url, ".skel");
|
||||
},
|
||||
|
||||
async load(url: string): Promise<SkeletonBinaryAsset> {
|
||||
const response = await settings.ADAPTER.fetch(url);
|
||||
|
||||
const buffer = new Uint8Array(await response.arrayBuffer());
|
||||
|
||||
return buffer;
|
||||
},
|
||||
testParse(asset: unknown, options: LoadAsset): Promise<boolean> {
|
||||
const isJsonSpineModel = checkExtension(options.src, ".json") && isJson(asset);
|
||||
const isBinarySpineModel = checkExtension(options.src, ".skel") && isBuffer(asset);
|
||||
|
||||
return Promise.resolve(isJsonSpineModel || isBinarySpineModel);
|
||||
},
|
||||
},
|
||||
} as AssetExtension<SkeletonJsonAsset | SkeletonBinaryAsset>;
|
||||
|
||||
extensions.add(spineLoaderExtension);
|
||||
32
spine-ts/spine-pixi/src/darkTintMesh/DarkTintBatchGeom.ts
Normal file
32
spine-ts/spine-pixi/src/darkTintMesh/DarkTintBatchGeom.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Geometry, Buffer, TYPES } from "@pixi/core";
|
||||
|
||||
/**
|
||||
* Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, Graphics objects).
|
||||
* @memberof PIXI
|
||||
*/
|
||||
export class DarkTintBatchGeometry extends Geometry {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _buffer: Buffer;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _indexBuffer: Buffer;
|
||||
|
||||
/**
|
||||
* @param {boolean} [_static=false] - Optimization flag, where `false`
|
||||
* is updated every frame, `true` doesn't change frame-to-frame.
|
||||
*/
|
||||
constructor(_static = false) {
|
||||
super();
|
||||
|
||||
this._buffer = new Buffer(undefined, _static, false);
|
||||
|
||||
this._indexBuffer = new Buffer(undefined, _static, true);
|
||||
|
||||
this.addAttribute("aVertexPosition", this._buffer, 2, false, TYPES.FLOAT)
|
||||
.addAttribute("aTextureCoord", this._buffer, 2, false, TYPES.FLOAT)
|
||||
.addAttribute("aColor", this._buffer, 4, true, TYPES.UNSIGNED_BYTE)
|
||||
.addAttribute("aDarkColor", this._buffer, 4, true, TYPES.UNSIGNED_BYTE)
|
||||
.addAttribute("aTextureId", this._buffer, 1, true, TYPES.FLOAT)
|
||||
.addIndex(this._indexBuffer);
|
||||
}
|
||||
}
|
||||
23
spine-ts/spine-pixi/src/darkTintMesh/DarkTintGeom.ts
Normal file
23
spine-ts/spine-pixi/src/darkTintMesh/DarkTintGeom.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Geometry, Buffer, TYPES } from "@pixi/core";
|
||||
|
||||
/**
|
||||
* Geometry used to batch standard PIXI content (e.g. Mesh, Sprite, Graphics objects).
|
||||
* @memberof PIXI
|
||||
*/
|
||||
export class DarkTintGeometry extends Geometry {
|
||||
/**
|
||||
* @param {boolean} [_static=false] - Optimization flag, where `false`
|
||||
* is updated every frame, `true` doesn't change frame-to-frame.
|
||||
*/
|
||||
constructor(_static = false) {
|
||||
super();
|
||||
|
||||
const verticesBuffer = new Buffer(undefined);
|
||||
const uvsBuffer = new Buffer(undefined, true);
|
||||
const indexBuffer = new Buffer(undefined, true, true);
|
||||
|
||||
this.addAttribute("aVertexPosition", verticesBuffer, 2, false, TYPES.FLOAT);
|
||||
this.addAttribute("aTextureCoord", uvsBuffer, 2, false, TYPES.FLOAT);
|
||||
this.addIndex(indexBuffer);
|
||||
}
|
||||
}
|
||||
175
spine-ts/spine-pixi/src/darkTintMesh/DarkTintMaterial.ts
Normal file
175
spine-ts/spine-pixi/src/darkTintMesh/DarkTintMaterial.ts
Normal file
@ -0,0 +1,175 @@
|
||||
import type { ColorSource } from "@pixi/core";
|
||||
import { Shader, TextureMatrix, Color, Texture, Matrix, Program } from "@pixi/core";
|
||||
|
||||
const vertex = `
|
||||
attribute vec2 aVertexPosition;
|
||||
attribute vec2 aTextureCoord;
|
||||
|
||||
uniform mat3 projectionMatrix;
|
||||
uniform mat3 translationMatrix;
|
||||
uniform mat3 uTextureMatrix;
|
||||
|
||||
varying vec2 vTextureCoord;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
|
||||
|
||||
vTextureCoord = (uTextureMatrix * vec3(aTextureCoord, 1.0)).xy;
|
||||
}
|
||||
`;
|
||||
|
||||
const fragment = `
|
||||
varying vec2 vTextureCoord;
|
||||
uniform vec4 uColor;
|
||||
uniform vec4 uDarkColor;
|
||||
|
||||
uniform sampler2D uSampler;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec4 texColor = texture2D(uSampler, vTextureCoord);
|
||||
gl_FragColor.a = texColor.a * uColor.a;
|
||||
gl_FragColor.rgb = ((texColor.a - 1.0) * uDarkColor.a + 1.0 - texColor.rgb) * uDarkColor.rgb + texColor.rgb * uColor.rgb;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface IDarkTintMaterialOptions {
|
||||
alpha?: number;
|
||||
tint?: ColorSource;
|
||||
darkTint?: ColorSource;
|
||||
pluginName?: string;
|
||||
uniforms?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export class DarkTintMaterial extends Shader {
|
||||
public readonly uvMatrix: TextureMatrix;
|
||||
|
||||
public batchable: boolean;
|
||||
|
||||
public pluginName: string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _tintRGB: number;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _darkTintRGB: number;
|
||||
|
||||
/**
|
||||
* Only do update if tint or alpha changes.
|
||||
* @private
|
||||
* @default false
|
||||
*/
|
||||
private _colorDirty: boolean;
|
||||
private _alpha: number;
|
||||
|
||||
private _tintColor: Color;
|
||||
private _darkTintColor: Color;
|
||||
|
||||
constructor(texture?: Texture) {
|
||||
const uniforms = {
|
||||
uSampler: texture ?? Texture.EMPTY,
|
||||
alpha: 1,
|
||||
uTextureMatrix: Matrix.IDENTITY,
|
||||
uColor: new Float32Array([1, 1, 1, 1]),
|
||||
uDarkColor: new Float32Array([0, 0, 0, 0]),
|
||||
};
|
||||
|
||||
// Set defaults
|
||||
const options = {
|
||||
tint: 0xffffff,
|
||||
darkTint: 0x0,
|
||||
alpha: 1,
|
||||
pluginName: "darkTintBatch",
|
||||
};
|
||||
|
||||
super(Program.from(vertex, fragment), uniforms);
|
||||
|
||||
this._colorDirty = false;
|
||||
|
||||
this.uvMatrix = new TextureMatrix(uniforms.uSampler);
|
||||
this.batchable = true;
|
||||
this.pluginName = options.pluginName;
|
||||
|
||||
this._tintColor = new Color(options.tint);
|
||||
this._darkTintColor = new Color(options.darkTint);
|
||||
this._tintRGB = this._tintColor.toLittleEndianNumber();
|
||||
this._darkTintRGB = this._darkTintColor.toLittleEndianNumber();
|
||||
this._alpha = options.alpha;
|
||||
this._colorDirty = true;
|
||||
}
|
||||
|
||||
public get texture(): Texture {
|
||||
return this.uniforms.uSampler;
|
||||
}
|
||||
public set texture(value: Texture) {
|
||||
if (this.uniforms.uSampler !== value) {
|
||||
if (!this.uniforms.uSampler.baseTexture.alphaMode !== !value.baseTexture.alphaMode) {
|
||||
this._colorDirty = true;
|
||||
}
|
||||
|
||||
this.uniforms.uSampler = value;
|
||||
this.uvMatrix.texture = value;
|
||||
}
|
||||
}
|
||||
|
||||
public set alpha(value: number) {
|
||||
if (value === this._alpha) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._alpha = value;
|
||||
this._colorDirty = true;
|
||||
}
|
||||
public get alpha(): number {
|
||||
return this._alpha;
|
||||
}
|
||||
|
||||
public set tint(value: ColorSource) {
|
||||
if (value === this.tint) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._tintColor.setValue(value);
|
||||
this._tintRGB = this._tintColor.toLittleEndianNumber();
|
||||
this._colorDirty = true;
|
||||
}
|
||||
public get tint(): ColorSource {
|
||||
return this._tintColor.value!;
|
||||
}
|
||||
|
||||
public set darkTint(value: ColorSource) {
|
||||
if (value === this.darkTint) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._darkTintColor.setValue(value);
|
||||
this._darkTintRGB = this._darkTintColor.toLittleEndianNumber();
|
||||
this._colorDirty = true;
|
||||
}
|
||||
public get darkTint(): ColorSource {
|
||||
return this._darkTintColor.value!;
|
||||
}
|
||||
|
||||
public get tintValue(): number {
|
||||
return this._tintColor.toNumber();
|
||||
}
|
||||
|
||||
public get darkTintValue(): number {
|
||||
return this._darkTintColor.toNumber();
|
||||
}
|
||||
|
||||
/** Gets called automatically by the Mesh. Intended to be overridden for custom {@link PIXI.MeshMaterial} objects. */
|
||||
public update(): void {
|
||||
if (this._colorDirty) {
|
||||
this._colorDirty = false;
|
||||
const baseTexture = this.texture.baseTexture;
|
||||
const applyToChannels = baseTexture.alphaMode as unknown as boolean;
|
||||
|
||||
Color.shared.setValue(this._tintColor).premultiply(this._alpha, applyToChannels).toArray(this.uniforms.uColor);
|
||||
Color.shared.setValue(this._darkTintColor).premultiply(this._alpha, applyToChannels).toArray(this.uniforms.uDarkColor);
|
||||
}
|
||||
if (this.uvMatrix.update()) {
|
||||
this.uniforms.uTextureMatrix = this.uvMatrix.mapCoord;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
spine-ts/spine-pixi/src/darkTintMesh/DarkTintMesh.ts
Normal file
62
spine-ts/spine-pixi/src/darkTintMesh/DarkTintMesh.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import type { Texture, ColorSource, Renderer, BLEND_MODES } from "@pixi/core";
|
||||
import { Mesh } from "@pixi/mesh";
|
||||
import { DarkTintGeometry } from "./DarkTintGeom";
|
||||
import { DarkTintMaterial } from "./DarkTintMaterial";
|
||||
|
||||
export interface IDarkTintElement {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
_texture: Texture;
|
||||
vertexData: Float32Array;
|
||||
indices: Uint16Array | Uint32Array | Array<number>;
|
||||
uvs: Float32Array;
|
||||
worldAlpha: number;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
_tintRGB: number;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
_darkTintRGB: number;
|
||||
blendMode: BLEND_MODES;
|
||||
}
|
||||
|
||||
export class DarkTintMesh extends Mesh<DarkTintMaterial> {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _darkTintRGB: number = 0;
|
||||
|
||||
constructor(texture?: Texture) {
|
||||
super(new DarkTintGeometry(), new DarkTintMaterial(texture), undefined, undefined);
|
||||
}
|
||||
|
||||
public get darkTint(): ColorSource | null {
|
||||
return "darkTint" in this.shader ? (this.shader as unknown as DarkTintMaterial).darkTint : null;
|
||||
}
|
||||
|
||||
public set darkTint(value: ColorSource | null) {
|
||||
(this.shader as unknown as DarkTintMaterial).darkTint = value!;
|
||||
}
|
||||
|
||||
public get darkTintValue(): number {
|
||||
return (this.shader as unknown as DarkTintMaterial).darkTintValue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
protected override _renderToBatch(renderer: Renderer): void {
|
||||
const geometry = this.geometry;
|
||||
const shader = this.shader;
|
||||
|
||||
if (shader.uvMatrix) {
|
||||
shader.uvMatrix.update();
|
||||
this.calculateUvs();
|
||||
}
|
||||
|
||||
// set properties for batching..
|
||||
this.calculateVertices();
|
||||
this.indices = geometry.indexBuffer.data as Uint16Array;
|
||||
this._tintRGB = shader._tintRGB;
|
||||
this._darkTintRGB = shader._darkTintRGB;
|
||||
this._texture = shader.texture;
|
||||
|
||||
const pluginName = this.material.pluginName;
|
||||
|
||||
renderer.batch.setObjectRenderer(renderer.plugins[pluginName]);
|
||||
renderer.plugins[pluginName].render(this);
|
||||
}
|
||||
}
|
||||
90
spine-ts/spine-pixi/src/darkTintMesh/DarkTintRenderer.ts
Normal file
90
spine-ts/spine-pixi/src/darkTintMesh/DarkTintRenderer.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import type { IDarkTintElement } from "./DarkTintMesh";
|
||||
import { DarkTintBatchGeometry } from "./DarkTintBatchGeom";
|
||||
import type { ExtensionMetadata, Renderer, ViewableBuffer } from "@pixi/core";
|
||||
import { BatchRenderer, ExtensionType, BatchShaderGenerator, Color } from "@pixi/core";
|
||||
|
||||
const vertex = `
|
||||
precision highp float;
|
||||
attribute vec2 aVertexPosition;
|
||||
attribute vec2 aTextureCoord;
|
||||
attribute vec4 aColor;
|
||||
attribute vec4 aDarkColor;
|
||||
attribute float aTextureId;
|
||||
|
||||
uniform mat3 projectionMatrix;
|
||||
uniform mat3 translationMatrix;
|
||||
uniform vec4 tint;
|
||||
|
||||
varying vec2 vTextureCoord;
|
||||
varying vec4 vColor;
|
||||
varying vec4 vDarkColor;
|
||||
varying float vTextureId;
|
||||
|
||||
void main(void){
|
||||
gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
|
||||
|
||||
vTextureCoord = aTextureCoord;
|
||||
vTextureId = aTextureId;
|
||||
vColor = aColor * tint;
|
||||
vDarkColor = aDarkColor * tint;
|
||||
|
||||
}
|
||||
`;
|
||||
|
||||
const fragment = `
|
||||
varying vec2 vTextureCoord;
|
||||
varying vec4 vColor;
|
||||
varying vec4 vDarkColor;
|
||||
varying float vTextureId;
|
||||
uniform sampler2D uSamplers[%count%];
|
||||
|
||||
void main(void){
|
||||
vec4 color;
|
||||
%forloop%
|
||||
|
||||
|
||||
gl_FragColor.a = color.a * vColor.a;
|
||||
gl_FragColor.rgb = ((color.a - 1.0) * vDarkColor.a + 1.0 - color.rgb) * vDarkColor.rgb + color.rgb * vColor.rgb;
|
||||
}
|
||||
`;
|
||||
|
||||
export class DarkTintRenderer extends BatchRenderer {
|
||||
public static override extension: ExtensionMetadata = {
|
||||
name: "darkTintBatch",
|
||||
type: ExtensionType.RendererPlugin,
|
||||
};
|
||||
|
||||
constructor(renderer: Renderer) {
|
||||
super(renderer);
|
||||
this.shaderGenerator = new BatchShaderGenerator(vertex, fragment);
|
||||
this.geometryClass = DarkTintBatchGeometry;
|
||||
// Pixi's default 6 + 1 for uDarkTint. (this is size in _floats_. color is 4 bytes which roughly equals one float :P )
|
||||
this.vertexSize = 7;
|
||||
}
|
||||
|
||||
public override packInterleavedGeometry(element: IDarkTintElement, attributeBuffer: ViewableBuffer, indexBuffer: Uint16Array, aIndex: number, iIndex: number): void {
|
||||
const { uint32View, float32View } = attributeBuffer;
|
||||
const packedVertices = aIndex / this.vertexSize;
|
||||
const uvs = element.uvs;
|
||||
const indicies = element.indices;
|
||||
const vertexData = element.vertexData;
|
||||
const textureId = element._texture.baseTexture._batchLocation;
|
||||
const alpha = Math.min(element.worldAlpha, 1.0);
|
||||
const argb = Color.shared.setValue(element._tintRGB).toPremultiplied(alpha, (element._texture.baseTexture.alphaMode ?? 0) > 0);
|
||||
const darkargb = Color.shared.setValue(element._darkTintRGB).toPremultiplied(alpha, (element._texture.baseTexture.alphaMode ?? 0) > 0);
|
||||
|
||||
// lets not worry about tint! for now..
|
||||
for (let i = 0; i < vertexData.length; i += 2) {
|
||||
float32View[aIndex++] = vertexData[i];
|
||||
float32View[aIndex++] = vertexData[i + 1];
|
||||
float32View[aIndex++] = uvs[i];
|
||||
float32View[aIndex++] = uvs[i + 1];
|
||||
uint32View[aIndex++] = argb;
|
||||
uint32View[aIndex++] = darkargb;
|
||||
float32View[aIndex++] = textureId;
|
||||
}
|
||||
for (let i = 0; i < indicies.length; i++) {
|
||||
indexBuffer[iIndex++] = packedVertices + indicies[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
18
spine-ts/spine-pixi/src/index.ts
Normal file
18
spine-ts/spine-pixi/src/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export * from './require-shim';
|
||||
export * from './Spine';
|
||||
export * from './SpineDebugRenderer';
|
||||
export * from './SpineTexture';
|
||||
export * from './SlotMesh';
|
||||
export * from './DarkSlotMesh';
|
||||
export * from './assets/atlasLoader';
|
||||
export * from './assets/skeletonLoader';
|
||||
export * from './darkTintMesh/DarkTintBatchGeom';
|
||||
export * from './darkTintMesh/DarkTintGeom';
|
||||
export * from './darkTintMesh/DarkTintMaterial';
|
||||
export * from './darkTintMesh/DarkTintMesh';
|
||||
export * from './darkTintMesh/DarkTintRenderer';
|
||||
export * from "@esotericsoftware/spine-core";
|
||||
|
||||
|
||||
import './assets/atlasLoader'; // Side effects install the loaders into pixi
|
||||
import './assets/skeletonLoader'; // Side effects install the loaders into pixi
|
||||
43
spine-ts/spine-pixi/src/require-shim.ts
Normal file
43
spine-ts/spine-pixi/src/require-shim.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated September 24, 2021. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
declare global {
|
||||
var require: any;
|
||||
var PIXI: any;
|
||||
}
|
||||
|
||||
if (window.PIXI) {
|
||||
let prevRequire = window.require;
|
||||
window.require = (x: string) => {
|
||||
if (prevRequire) return prevRequire(x);
|
||||
else if (x.startsWith("@pixi/")) return window.PIXI;
|
||||
}
|
||||
}
|
||||
|
||||
export { }
|
||||
24
spine-ts/spine-pixi/tsconfig.json
Normal file
24
spine-ts/spine-pixi/tsconfig.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@esotericsoftware/spine-core": [
|
||||
"../spine-core/src"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"dist/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../spine-core"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -18,6 +18,9 @@
|
||||
},
|
||||
{
|
||||
"path": "./spine-threejs"
|
||||
},
|
||||
{
|
||||
"path": "./spine-pixi"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -664,7 +664,7 @@ namespace Spine.Unity.Editor {
|
||||
Material material = (Material)AssetDatabase.LoadAssetAtPath(materialPath, typeof(Material));
|
||||
|
||||
if (material == null) {
|
||||
Shader defaultShader = Shader.Find(SpineEditorUtilities.Preferences.DefaultShader);
|
||||
Shader defaultShader = GetDefaultShader();
|
||||
material = defaultShader != null ? new Material(defaultShader) : null;
|
||||
if (material) {
|
||||
ApplyPMAOrStraightAlphaSettings(material, SpineEditorUtilities.Preferences.textureSettingsReference);
|
||||
@ -737,6 +737,13 @@ namespace Spine.Unity.Editor {
|
||||
return loadedAtlas != null ? loadedAtlas : atlasAsset;
|
||||
}
|
||||
|
||||
public static Shader GetDefaultShader () {
|
||||
Shader shader = Shader.Find(SpineEditorUtilities.Preferences.DefaultShader);
|
||||
if (shader == null) shader = Shader.Find("Spine/Skeleton");
|
||||
if (shader == null) shader = Shader.Find("Standard");
|
||||
return shader;
|
||||
}
|
||||
|
||||
public static bool SpriteAtlasSettingsNeedAdjustment (UnityEngine.U2D.SpriteAtlas spriteAtlas) {
|
||||
#if EXPOSES_SPRITE_ATLAS_UTILITIES
|
||||
UnityEditor.U2D.SpriteAtlasPackingSettings packingSettings = UnityEditor.U2D.SpriteAtlasExtensions.GetPackingSettings(spriteAtlas);
|
||||
@ -851,24 +858,24 @@ namespace Spine.Unity.Editor {
|
||||
|
||||
{
|
||||
string pageName = "SpriteAtlas";
|
||||
|
||||
string materialPath = assetPath + "/" + primaryName + "_" + pageName + ".mat";
|
||||
Material mat = AssetDatabase.LoadAssetAtPath<Material>(materialPath);
|
||||
Material material = AssetDatabase.LoadAssetAtPath<Material>(materialPath);
|
||||
|
||||
if (mat == null) {
|
||||
mat = new Material(Shader.Find(SpineEditorUtilities.Preferences.defaultShader));
|
||||
ApplyPMAOrStraightAlphaSettings(mat, SpineEditorUtilities.Preferences.textureSettingsReference);
|
||||
AssetDatabase.CreateAsset(mat, materialPath);
|
||||
if (material == null) {
|
||||
Shader defaultShader = GetDefaultShader();
|
||||
material = defaultShader != null ? new Material(defaultShader) : null;
|
||||
ApplyPMAOrStraightAlphaSettings(material, SpineEditorUtilities.Preferences.textureSettingsReference);
|
||||
AssetDatabase.CreateAsset(material, materialPath);
|
||||
} else {
|
||||
vestigialMaterials.Remove(mat);
|
||||
vestigialMaterials.Remove(material);
|
||||
}
|
||||
|
||||
if (texture != null)
|
||||
mat.mainTexture = texture;
|
||||
material.mainTexture = texture;
|
||||
|
||||
EditorUtility.SetDirty(mat);
|
||||
EditorUtility.SetDirty(material);
|
||||
// note: don't call AssetDatabase.SaveAssets() since this would trigger OnPostprocessAllAssets() every time unnecessarily.
|
||||
populatingMaterials.Add(mat); //atlasAsset.materials[i] = mat;
|
||||
populatingMaterials.Add(material);
|
||||
}
|
||||
|
||||
atlasAsset.materials = populatingMaterials.ToArray();
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"name": "com.esotericsoftware.spine.spine-unity",
|
||||
"displayName": "spine-unity Runtime",
|
||||
"description": "This plugin provides the spine-unity runtime core.",
|
||||
"version": "4.2.14",
|
||||
"version": "4.2.15",
|
||||
"unity": "2018.3",
|
||||
"author": {
|
||||
"name": "Esoteric Software",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user