Skip to content

Commit

Permalink
[ts][threejs] Added multi-page atlas and blend modes support
Browse files Browse the repository at this point in the history
Also updated to latest ThreeJS release and some clean-up.
  • Loading branch information
badlogic committed Oct 19, 2021
1 parent caed761 commit 38cc19f
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 82 deletions.
32 changes: 16 additions & 16 deletions spine-ts/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion spine-ts/spine-threejs/example/index.html
Expand Up @@ -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>
* {
Expand Down
6 changes: 3 additions & 3 deletions spine-ts/spine-threejs/package.json
Expand Up @@ -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"
}
}
}
78 changes: 75 additions & 3 deletions spine-ts/spine-threejs/src/MeshBatcher.ts
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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 () {
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
81 changes: 30 additions & 51 deletions spine-ts/spine-threejs/src/SkeletonMesh.ts
Expand Up @@ -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: {
Expand All @@ -65,6 +65,7 @@ export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
fragmentShader: fragmentShader,
side: THREE.DoubleSide,
transparent: true,
depthWrite: false,
alphaTest: 0.5
};
customizer(parameters);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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];
Expand Down Expand Up @@ -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)) {
Expand All @@ -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;
}
Expand Down

0 comments on commit 38cc19f

Please sign in to comment.