mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-22 02:06:03 +08:00
[ts][threejs] Added multi-page atlas and blend modes support
Also updated to latest ThreeJS release and some clean-up.
This commit is contained in:
parent
caed7614c8
commit
38cc19f16d
32
spine-ts/package-lock.json
generated
32
spine-ts/package-lock.json
generated
@ -156,9 +156,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/three": {
|
||||
"version": "0.131.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.131.1.tgz",
|
||||
"integrity": "sha512-unnjsolcm7R90e4XK9qMq4JYEzly0XQNa0pG8RAOMZeVzj3FLIFPymAYUx4Osz0gY9jFZz8omIQplqiieEE7gw=="
|
||||
"version": "0.133.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.133.1.tgz",
|
||||
"integrity": "sha512-XqBrP/+kbs+o0CYRhCVVE95v7FaL2bO5Z7+3VQJE0nEyjo+9LoLfeNgZITOnndKHxM+7ltEciAIR7uE0SZlsOg=="
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.7",
|
||||
@ -7838,9 +7838,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/three": {
|
||||
"version": "0.132.2",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.132.2.tgz",
|
||||
"integrity": "sha512-0wcR7LxxkXMn6Gi58gEs3QvY8WpTVXA31L2VOvpjm4ZPYFRHCZC13UqynheFoS5OXDYgtBneN0dhbaNBE8iLhQ=="
|
||||
"version": "0.133.1",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.133.1.tgz",
|
||||
"integrity": "sha512-WydohO8ll949B0FTD6MGz59Yv2Lwj8hvObg/0Heh2r42S6+tQC1WByfCNRdmG4D7+odfGod+n8JPV1I2xrboWw=="
|
||||
},
|
||||
"node_modules/through": {
|
||||
"version": "2.3.8",
|
||||
@ -8280,8 +8280,8 @@
|
||||
"license": "LicenseRef-LICENSE",
|
||||
"dependencies": {
|
||||
"@esotericsoftware/spine-core": "^4.0.13",
|
||||
"@types/three": "^0.131.0",
|
||||
"three": "^0.132.0"
|
||||
"@types/three": "^0.133.1",
|
||||
"three": "^0.133.1"
|
||||
}
|
||||
},
|
||||
"spine-webgl": {
|
||||
@ -8391,8 +8391,8 @@
|
||||
"version": "file:spine-threejs",
|
||||
"requires": {
|
||||
"@esotericsoftware/spine-core": "^4.0.13",
|
||||
"@types/three": "^0.131.0",
|
||||
"three": "^0.132.0"
|
||||
"@types/three": "^0.133.1",
|
||||
"three": "^0.133.1"
|
||||
}
|
||||
},
|
||||
"@esotericsoftware/spine-webgl": {
|
||||
@ -8414,9 +8414,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/three": {
|
||||
"version": "0.131.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.131.1.tgz",
|
||||
"integrity": "sha512-unnjsolcm7R90e4XK9qMq4JYEzly0XQNa0pG8RAOMZeVzj3FLIFPymAYUx4Osz0gY9jFZz8omIQplqiieEE7gw=="
|
||||
"version": "0.133.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.133.1.tgz",
|
||||
"integrity": "sha512-XqBrP/+kbs+o0CYRhCVVE95v7FaL2bO5Z7+3VQJE0nEyjo+9LoLfeNgZITOnndKHxM+7ltEciAIR7uE0SZlsOg=="
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
@ -14486,9 +14486,9 @@
|
||||
}
|
||||
},
|
||||
"three": {
|
||||
"version": "0.132.2",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.132.2.tgz",
|
||||
"integrity": "sha512-0wcR7LxxkXMn6Gi58gEs3QvY8WpTVXA31L2VOvpjm4ZPYFRHCZC13UqynheFoS5OXDYgtBneN0dhbaNBE8iLhQ=="
|
||||
"version": "0.133.1",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.133.1.tgz",
|
||||
"integrity": "sha512-WydohO8ll949B0FTD6MGz59Yv2Lwj8hvObg/0Heh2r42S6+tQC1WByfCNRdmG4D7+odfGod+n8JPV1I2xrboWw=="
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
<title>spine-threejs</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.js"></script>
|
||||
<script src="../dist/iife/spine-threejs.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</head>
|
||||
<style>
|
||||
* {
|
||||
|
||||
@ -30,8 +30,8 @@
|
||||
},
|
||||
"homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
|
||||
"dependencies": {
|
||||
"@types/three": "^0.131.0",
|
||||
"three": "^0.132.0",
|
||||
"@types/three": "^0.133.1",
|
||||
"three": "^0.133.1",
|
||||
"@esotericsoftware/spine-core": "^4.0.13"
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,8 @@
|
||||
|
||||
import { SkeletonMeshMaterial, SkeletonMeshMaterialParametersCustomizer } from "./SkeletonMesh";
|
||||
import * as THREE from "three"
|
||||
import { ThreeJsTexture } from "./ThreeJsTexture";
|
||||
import { BlendMode } from "@esotericsoftware/spine-core";
|
||||
|
||||
export class MeshBatcher extends THREE.Mesh {
|
||||
private static VERTEX_SIZE = 9;
|
||||
@ -37,8 +39,9 @@ export class MeshBatcher extends THREE.Mesh {
|
||||
private verticesLength = 0;
|
||||
private indices: Uint16Array;
|
||||
private indicesLength = 0;
|
||||
private materialGroups: [number, number, number][] = [];
|
||||
|
||||
constructor (maxVertices: number = 10920, materialCustomizer: SkeletonMeshMaterialParametersCustomizer = (parameters) => { }) {
|
||||
constructor (maxVertices: number = 10920, private materialCustomizer: SkeletonMeshMaterialParametersCustomizer = (parameters) => { }) {
|
||||
super();
|
||||
if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
|
||||
let vertices = this.vertices = new Float32Array(maxVertices * MeshBatcher.VERTEX_SIZE);
|
||||
@ -54,7 +57,7 @@ export class MeshBatcher extends THREE.Mesh {
|
||||
geo.drawRange.start = 0;
|
||||
geo.drawRange.count = 0;
|
||||
this.geometry = geo;
|
||||
this.material = new SkeletonMeshMaterial(materialCustomizer);
|
||||
this.material = [new SkeletonMeshMaterial(materialCustomizer)];
|
||||
}
|
||||
|
||||
dispose () {
|
||||
@ -74,7 +77,19 @@ export class MeshBatcher extends THREE.Mesh {
|
||||
let geo = (<THREE.BufferGeometry>this.geometry);
|
||||
geo.drawRange.start = 0;
|
||||
geo.drawRange.count = 0;
|
||||
(<SkeletonMeshMaterial>this.material).uniforms.map.value = null;
|
||||
geo.clearGroups();
|
||||
this.materialGroups = [];
|
||||
if (this.material instanceof THREE.Material) {
|
||||
const meshMaterial = this.material as SkeletonMeshMaterial;
|
||||
meshMaterial.uniforms.map.value = null;
|
||||
meshMaterial.blending = THREE.NormalBlending;
|
||||
} else if (Array.isArray(this.material)) {
|
||||
for (let i = 0; i < this.material.length; i++) {
|
||||
const meshMaterial = this.material[i] as SkeletonMeshMaterial;
|
||||
meshMaterial.uniforms.map.value = null;
|
||||
meshMaterial.blending = THREE.NormalBlending;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -118,10 +133,67 @@ export class MeshBatcher extends THREE.Mesh {
|
||||
this.vertexBuffer.updateRange.offset = 0;
|
||||
this.vertexBuffer.updateRange.count = this.verticesLength;
|
||||
let geo = (<THREE.BufferGeometry>this.geometry);
|
||||
this.closeMaterialGroups();
|
||||
geo.getIndex().needsUpdate = this.indicesLength > 0;
|
||||
geo.getIndex().updateRange.offset = 0;
|
||||
geo.getIndex().updateRange.count = this.indicesLength;
|
||||
geo.drawRange.start = 0;
|
||||
geo.drawRange.count = this.indicesLength;
|
||||
}
|
||||
|
||||
addMaterialGroup (indicesLength: number, materialGroup: number) {
|
||||
const currentGroup = this.materialGroups[this.materialGroups.length - 1];
|
||||
|
||||
if (currentGroup === undefined || currentGroup[2] !== materialGroup) {
|
||||
this.materialGroups.push([this.indicesLength, indicesLength, materialGroup]);
|
||||
} else {
|
||||
currentGroup[1] += indicesLength;
|
||||
}
|
||||
}
|
||||
|
||||
private closeMaterialGroups () {
|
||||
const geometry = this.geometry as THREE.BufferGeometry;
|
||||
for (let i = 0; i < this.materialGroups.length; i++) {
|
||||
const [startIndex, count, materialGroup] = this.materialGroups[i];
|
||||
|
||||
geometry.addGroup(startIndex, count, materialGroup);
|
||||
}
|
||||
}
|
||||
|
||||
findMaterialGroup (slotTexture: THREE.Texture, slotBlendMode: BlendMode) {
|
||||
const blending = ThreeJsTexture.toThreeJsBlending(slotBlendMode);
|
||||
let group = -1;
|
||||
|
||||
if (Array.isArray(this.material)) {
|
||||
for (let i = 0; i < this.material.length; i++) {
|
||||
const meshMaterial = this.material[i] as SkeletonMeshMaterial;
|
||||
|
||||
if (meshMaterial.uniforms.map.value === null) {
|
||||
updateMeshMaterial(meshMaterial, slotTexture, blending);
|
||||
return i;
|
||||
}
|
||||
|
||||
if (meshMaterial.uniforms.map.value === slotTexture && meshMaterial.blending === blending) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
const meshMaterial = new SkeletonMeshMaterial(this.materialCustomizer);
|
||||
updateMeshMaterial(meshMaterial, slotTexture, blending);
|
||||
this.material.push(meshMaterial);
|
||||
group = this.material.length - 1;
|
||||
} else {
|
||||
throw new Error("MeshBatcher.material needs to be an array for geometry groups to work");
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
function updateMeshMaterial (meshMaterial: SkeletonMeshMaterial, slotTexture: THREE.Texture, blending: THREE.Blending) {
|
||||
meshMaterial.uniforms.map.value = slotTexture;
|
||||
meshMaterial.blending = blending;
|
||||
meshMaterial.blendDst = blending === THREE.CustomBlending ? THREE.OneMinusSrcColorFactor : THREE.OneMinusSrcAlphaFactor;
|
||||
meshMaterial.blendSrc = blending === THREE.CustomBlending ? THREE.OneFactor : THREE.SrcAlphaFactor;
|
||||
meshMaterial.needsUpdate = true;
|
||||
}
|
||||
|
||||
@ -39,23 +39,23 @@ export interface SkeletonMeshMaterialParametersCustomizer {
|
||||
export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
|
||||
constructor (customizer: SkeletonMeshMaterialParametersCustomizer) {
|
||||
let vertexShader = `
|
||||
attribute vec4 color;
|
||||
varying vec2 vUv;
|
||||
varying vec4 vColor;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
vColor = color;
|
||||
gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
|
||||
}
|
||||
`;
|
||||
attribute vec4 color;
|
||||
varying vec2 vUv;
|
||||
varying vec4 vColor;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
vColor = color;
|
||||
gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
|
||||
}
|
||||
`;
|
||||
let fragmentShader = `
|
||||
uniform sampler2D map;
|
||||
varying vec2 vUv;
|
||||
varying vec4 vColor;
|
||||
void main(void) {
|
||||
gl_FragColor = texture2D(map, vUv)*vColor;
|
||||
}
|
||||
`;
|
||||
uniform sampler2D map;
|
||||
varying vec2 vUv;
|
||||
varying vec4 vColor;
|
||||
void main(void) {
|
||||
gl_FragColor = texture2D(map, vUv)*vColor;
|
||||
}
|
||||
`;
|
||||
|
||||
let parameters: THREE.ShaderMaterialParameters = {
|
||||
uniforms: {
|
||||
@ -65,6 +65,7 @@ export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
|
||||
fragmentShader: fragmentShader,
|
||||
side: THREE.DoubleSide,
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
alphaTest: 0.5
|
||||
};
|
||||
customizer(parameters);
|
||||
@ -91,11 +92,10 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
|
||||
private vertices = Utils.newFloatArray(1024);
|
||||
private tempColor = new Color();
|
||||
private materialCustomizer: SkeletonMeshMaterialParametersCustomizer;
|
||||
|
||||
constructor (skeletonData: SkeletonData, materialCustomizer: SkeletonMeshMaterialParametersCustomizer = (parameters) => { }) {
|
||||
constructor (skeletonData: SkeletonData) {
|
||||
super();
|
||||
this.materialCustomizer = materialCustomizer;
|
||||
|
||||
this.skeleton = new Skeleton(skeletonData);
|
||||
let animData = new AnimationStateData(skeletonData);
|
||||
this.state = new AnimationState(animData);
|
||||
@ -128,7 +128,7 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
|
||||
private nextBatch () {
|
||||
if (this.batches.length == this.nextBatchIndex) {
|
||||
let batch = new MeshBatcher(10920, this.materialCustomizer);
|
||||
let batch = new MeshBatcher();
|
||||
this.add(batch);
|
||||
this.batches.push(batch);
|
||||
}
|
||||
@ -163,10 +163,7 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
for (let i = 0, n = drawOrder.length; i < n; i++) {
|
||||
let vertexSize = clipper.isClipping() ? 2 : SkeletonMesh.VERTEX_SIZE;
|
||||
let slot = drawOrder[i];
|
||||
if (!slot.bone.active) {
|
||||
clipper.clipEndWithSlot(slot);
|
||||
continue;
|
||||
}
|
||||
if (!slot.bone.active) continue;
|
||||
let attachment = slot.getAttachment();
|
||||
let attachmentColor: Color = null;
|
||||
let texture: ThreeJsTexture = null;
|
||||
@ -196,12 +193,9 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
let clip = <ClippingAttachment>(attachment);
|
||||
clipper.clipStart(slot, clip);
|
||||
continue;
|
||||
} else {
|
||||
clipper.clipEndWithSlot(slot);
|
||||
continue;
|
||||
}
|
||||
} else continue;
|
||||
|
||||
if (texture) {
|
||||
if (texture != null) {
|
||||
let skeleton = slot.bone.skeleton;
|
||||
let skeletonColor = skeleton.color;
|
||||
let slotColor = slot.color;
|
||||
@ -221,7 +215,7 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
clipper.clipTriangles(vertices, numFloats, triangles, triangles.length, uvs, color, null, false);
|
||||
let clippedVertices = clipper.clippedVertices;
|
||||
let clippedTriangles = clipper.clippedTriangles;
|
||||
if (this.vertexEffect) {
|
||||
if (this.vertexEffect != null) {
|
||||
let vertexEffect = this.vertexEffect;
|
||||
let verts = clippedVertices;
|
||||
for (let v = 0, n = clippedVertices.length; v < n; v += vertexSize) {
|
||||
@ -248,7 +242,7 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
finalIndicesLength = clippedTriangles.length;
|
||||
} else {
|
||||
let verts = vertices;
|
||||
if (this.vertexEffect) {
|
||||
if (this.vertexEffect != null) {
|
||||
let vertexEffect = this.vertexEffect;
|
||||
for (let v = 0, u = 0, n = numFloats; v < n; v += vertexSize, u += 2) {
|
||||
tempPos.x = verts[v];
|
||||
@ -283,10 +277,8 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
finalIndicesLength = triangles.length;
|
||||
}
|
||||
|
||||
if (finalVerticesLength == 0 || finalIndicesLength == 0) {
|
||||
clipper.clipEndWithSlot(slot);
|
||||
if (finalVerticesLength == 0 || finalIndicesLength == 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start new batch if this one can't hold vertices/indices
|
||||
if (!batch.canBatch(finalVerticesLength, finalIndicesLength)) {
|
||||
@ -295,24 +287,11 @@ export class SkeletonMesh extends THREE.Object3D {
|
||||
batch.begin();
|
||||
}
|
||||
|
||||
// FIXME per slot blending would require multiple material support
|
||||
//let slotBlendMode = slot.data.blendMode;
|
||||
//if (slotBlendMode != blendMode) {
|
||||
// blendMode = slotBlendMode;
|
||||
// batcher.setBlendMode(getSourceGLBlendMode(this._gl, blendMode, premultipliedAlpha), getDestGLBlendMode(this._gl, blendMode));
|
||||
//}
|
||||
|
||||
let batchMaterial = <SkeletonMeshMaterial>batch.material;
|
||||
if (!batchMaterial.uniforms.map.value) batchMaterial.uniforms.map.value = texture.texture;
|
||||
if (batchMaterial.uniforms.map.value != texture.texture) {
|
||||
batch.end();
|
||||
batch = this.nextBatch();
|
||||
batch.begin();
|
||||
batchMaterial = <SkeletonMeshMaterial>batch.material;
|
||||
batchMaterial.uniforms.map.value = texture.texture;
|
||||
}
|
||||
batchMaterial.needsUpdate = true;
|
||||
const slotBlendMode = slot.data.blendMode;
|
||||
const slotTexture = texture.texture;
|
||||
const materialGroup = batch.findMaterialGroup(slotTexture, slotBlendMode);
|
||||
|
||||
batch.addMaterialGroup(finalIndicesLength, materialGroup);
|
||||
batch.batch(finalVertices, finalVerticesLength, finalIndices, finalIndicesLength, z);
|
||||
z += zOffset;
|
||||
}
|
||||
|
||||
@ -27,34 +27,34 @@
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
import { Texture, TextureFilter, TextureWrap } from "@esotericsoftware/spine-core";
|
||||
import { BlendMode, Texture, TextureFilter, TextureWrap } from "@esotericsoftware/spine-core";
|
||||
import * as THREE from "three";
|
||||
|
||||
export class ThreeJsTexture extends Texture {
|
||||
texture: THREE.Texture;
|
||||
|
||||
constructor (image: HTMLImageElement) {
|
||||
constructor(image: HTMLImageElement) {
|
||||
super(image);
|
||||
this.texture = new THREE.Texture(image);
|
||||
this.texture.flipY = false;
|
||||
this.texture.needsUpdate = true;
|
||||
}
|
||||
|
||||
setFilters (minFilter: TextureFilter, magFilter: TextureFilter) {
|
||||
setFilters(minFilter: TextureFilter, magFilter: TextureFilter) {
|
||||
this.texture.minFilter = ThreeJsTexture.toThreeJsTextureFilter(minFilter);
|
||||
this.texture.magFilter = ThreeJsTexture.toThreeJsTextureFilter(magFilter);
|
||||
}
|
||||
|
||||
setWraps (uWrap: TextureWrap, vWrap: TextureWrap) {
|
||||
setWraps(uWrap: TextureWrap, vWrap: TextureWrap) {
|
||||
this.texture.wrapS = ThreeJsTexture.toThreeJsTextureWrap(uWrap);
|
||||
this.texture.wrapT = ThreeJsTexture.toThreeJsTextureWrap(vWrap);
|
||||
}
|
||||
|
||||
dispose () {
|
||||
dispose() {
|
||||
this.texture.dispose();
|
||||
}
|
||||
|
||||
static toThreeJsTextureFilter (filter: TextureFilter) {
|
||||
static toThreeJsTextureFilter(filter: TextureFilter) {
|
||||
if (filter === TextureFilter.Linear) return THREE.LinearFilter;
|
||||
else if (filter === TextureFilter.MipMap) return THREE.LinearMipMapLinearFilter; // also includes TextureFilter.MipMapLinearLinear
|
||||
else if (filter === TextureFilter.MipMapLinearNearest) return THREE.LinearMipMapNearestFilter;
|
||||
@ -64,10 +64,18 @@ export class ThreeJsTexture extends Texture {
|
||||
else throw new Error("Unknown texture filter: " + filter);
|
||||
}
|
||||
|
||||
static toThreeJsTextureWrap (wrap: TextureWrap) {
|
||||
static toThreeJsTextureWrap(wrap: TextureWrap) {
|
||||
if (wrap === TextureWrap.ClampToEdge) return THREE.ClampToEdgeWrapping;
|
||||
else if (wrap === TextureWrap.MirroredRepeat) return THREE.MirroredRepeatWrapping;
|
||||
else if (wrap === TextureWrap.Repeat) return THREE.RepeatWrapping;
|
||||
else throw new Error("Unknown texture wrap: " + wrap);
|
||||
}
|
||||
|
||||
static toThreeJsBlending(blend: BlendMode) {
|
||||
if (blend === BlendMode.Normal) return THREE.NormalBlending;
|
||||
else if (blend === BlendMode.Additive) return THREE.AdditiveBlending;
|
||||
else if (blend === BlendMode.Multiply) return THREE.MultiplyBlending;
|
||||
else if (blend === BlendMode.Screen) return THREE.CustomBlending;
|
||||
else throw new Error("Unknown blendMode: " + blend);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user