diff --git a/spine-ts/spine-threejs/example/shadow.html b/spine-ts/spine-threejs/example/shadow.html
new file mode 100644
index 0000000000..d48ae92570
--- /dev/null
+++ b/spine-ts/spine-threejs/example/shadow.html
@@ -0,0 +1,244 @@
+
+
+
+ spine-threejs
+
+
+
+
+
+
+
+
+
+
diff --git a/spine-ts/spine-threejs/src/MeshBatcher.ts b/spine-ts/spine-threejs/src/MeshBatcher.ts
index ac6fc1114b..4f5604025c 100644
--- a/spine-ts/spine-threejs/src/MeshBatcher.ts
+++ b/spine-ts/spine-threejs/src/MeshBatcher.ts
@@ -27,12 +27,16 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
-import { SkeletonMeshMaterial, SkeletonMeshMaterialParametersCustomizer } from "./SkeletonMesh.js";
import * as THREE from "three"
+
import { ThreeJsTexture, ThreeBlendOptions } from "./ThreeJsTexture.js";
import { BlendMode } from "@esotericsoftware/spine-core";
+import { SkeletonMesh } from "./SkeletonMesh.js";
+type MaterialWithMap = THREE.Material & { map: THREE.Texture | null };
export class MeshBatcher extends THREE.Mesh {
+ public static MAX_VERTICES = 10920;
+
private static VERTEX_SIZE = 9;
private vertexBuffer: THREE.InterleavedBuffer;
private vertices: Float32Array;
@@ -41,9 +45,9 @@ export class MeshBatcher extends THREE.Mesh {
private indicesLength = 0;
private materialGroups: [number, number, number][] = [];
- constructor (maxVertices: number = 10920, private materialCustomizer: SkeletonMeshMaterialParametersCustomizer = (parameters) => { }) {
+ constructor (maxVertices: number = MeshBatcher.MAX_VERTICES, private materialFactory: (parameters: THREE.MaterialParameters) => THREE.Material) {
super();
- if (maxVertices > 10920) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
+ if (maxVertices > MeshBatcher.MAX_VERTICES) throw new Error("Can't have more than 10920 triangles per batch: " + maxVertices);
let vertices = this.vertices = new Float32Array(maxVertices * MeshBatcher.VERTEX_SIZE);
let indices = this.indices = new Uint16Array(maxVertices * 3);
let geo = new THREE.BufferGeometry();
@@ -57,7 +61,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 = [];
}
dispose () {
@@ -80,13 +84,13 @@ export class MeshBatcher extends THREE.Mesh {
geo.clearGroups();
this.materialGroups = [];
if (this.material instanceof THREE.Material) {
- const meshMaterial = this.material as SkeletonMeshMaterial;
- meshMaterial.uniforms.map.value = null;
+ const meshMaterial = this.material as MaterialWithMap;
+ meshMaterial.map = 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;
+ const meshMaterial = this.material[i] as MaterialWithMap;
+ meshMaterial.map = null;
meshMaterial.blending = THREE.NormalBlending;
}
}
@@ -167,14 +171,14 @@ export class MeshBatcher extends THREE.Mesh {
if (Array.isArray(this.material)) {
for (let i = 0; i < this.material.length; i++) {
- const meshMaterial = this.material[i] as SkeletonMeshMaterial;
+ const meshMaterial = this.material[i] as MaterialWithMap;
- if (!meshMaterial.uniforms.map.value) {
+ if (!meshMaterial.map) {
updateMeshMaterial(meshMaterial, slotTexture, blendingObject);
return i;
}
- if (meshMaterial.uniforms.map.value === slotTexture
+ if (meshMaterial.map === slotTexture
&& blendingObject.blending === meshMaterial.blending
&& (blendingObject.blendSrc === undefined || blendingObject.blendSrc === meshMaterial.blendSrc)
&& (blendingObject.blendDst === undefined || blendingObject.blendDst === meshMaterial.blendDst)
@@ -185,8 +189,13 @@ export class MeshBatcher extends THREE.Mesh {
}
}
- const meshMaterial = new SkeletonMeshMaterial(this.materialCustomizer);
- updateMeshMaterial(meshMaterial, slotTexture, blendingObject);
+ const meshMaterial = this.materialFactory(SkeletonMesh.DEFAULT_MATERIAL_PARAMETERS);
+
+ if (!('map' in meshMaterial)) {
+ throw new Error("The material factory must return a material having the map property for the texture.");
+ }
+
+ updateMeshMaterial(meshMaterial as MaterialWithMap, slotTexture, blendingObject);
this.material.push(meshMaterial);
group = this.material.length - 1;
} else {
@@ -197,8 +206,8 @@ export class MeshBatcher extends THREE.Mesh {
}
}
-function updateMeshMaterial (meshMaterial: SkeletonMeshMaterial, slotTexture: THREE.Texture, blending: ThreeBlendOptions) {
- meshMaterial.uniforms.map.value = slotTexture;
+function updateMeshMaterial (meshMaterial: MaterialWithMap, slotTexture: THREE.Texture, blending: ThreeBlendOptions) {
+ meshMaterial.map = slotTexture;
Object.assign(meshMaterial, blending);
meshMaterial.needsUpdate = true;
}
diff --git a/spine-ts/spine-threejs/src/SkeletonMesh.ts b/spine-ts/spine-threejs/src/SkeletonMesh.ts
index 71b99d3052..d48efb4039 100644
--- a/spine-ts/spine-threejs/src/SkeletonMesh.ts
+++ b/spine-ts/spine-threejs/src/SkeletonMesh.ts
@@ -27,10 +27,10 @@
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
+import * as THREE from "three";
import {
AnimationState,
AnimationStateData,
- BlendMode,
ClippingAttachment,
Color,
MeshAttachment,
@@ -40,69 +40,26 @@ import {
Skeleton,
SkeletonClipping,
SkeletonData,
- TextureAtlasRegion,
Utils,
Vector2,
} from "@esotericsoftware/spine-core";
+
import { MeshBatcher } from "./MeshBatcher.js";
-import * as THREE from "three";
import { ThreeJsTexture } from "./ThreeJsTexture.js";
+import { Material } from "three";
-export type SkeletonMeshMaterialParametersCustomizer = (
- materialParameters: THREE.ShaderMaterialParameters
-) => void;
-
-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);
- }
- `;
- let fragmentShader = `
- uniform sampler2D map;
- #ifdef USE_SPINE_ALPHATEST
- uniform float alphaTest;
- #endif
- varying vec2 vUv;
- varying vec4 vColor;
- void main(void) {
- gl_FragColor = texture2D(map, vUv)*vColor;
- #ifdef USE_SPINE_ALPHATEST
- if (gl_FragColor.a < alphaTest) discard;
- #endif
- }
- `;
+type SkeletonMeshMaterialParametersCustomizer = (materialParameters: THREE.MaterialParameters) => void;
- let parameters: THREE.ShaderMaterialParameters = {
- uniforms: {
- map: { value: null },
- },
- vertexShader: vertexShader,
- fragmentShader: fragmentShader,
- side: THREE.DoubleSide,
- transparent: true,
- depthWrite: true,
- alphaTest: 0.0,
- };
- customizer(parameters);
- if (parameters.alphaTest && parameters.alphaTest > 0) {
- parameters.defines = { USE_SPINE_ALPHATEST: 1 };
- if (!parameters.uniforms) parameters.uniforms = {};
- parameters.uniforms["alphaTest"] = { value: parameters.alphaTest };
- }
- super(parameters);
- // non-pma textures are premultiply on upload, so we set premultipliedAlpha to true
- this.premultipliedAlpha = true;
+export class SkeletonMesh extends THREE.Object3D {
+ public static readonly DEFAULT_MATERIAL_PARAMETERS: THREE.MaterialParameters = {
+ side: THREE.DoubleSide,
+ transparent: true,
+ depthWrite: true,
+ alphaTest: 0.0,
+ premultipliedAlpha: true,
+ vertexColors: true,
}
-}
-export class SkeletonMesh extends THREE.Object3D {
tempPos: Vector2 = new Vector2();
tempUv: Vector2 = new Vector2();
tempLight = new Color();
@@ -112,6 +69,7 @@ export class SkeletonMesh extends THREE.Object3D {
zOffset: number = 0.1;
private batches = new Array();
+ private materialFactory: (parameters: THREE.MaterialParameters) => Material;
private nextBatchIndex = 0;
private clipper: SkeletonClipping = new SkeletonClipping();
@@ -121,17 +79,67 @@ export class SkeletonMesh extends THREE.Object3D {
private vertices = Utils.newFloatArray(1024);
private tempColor = new Color();
+ private _castShadow = false;
+ private _receiveShadow = false;
+
+ constructor (configuration: { skeletonData: SkeletonData, materialFactory?: (parameters: THREE.MaterialParameters) => Material })
+ /**
+ * @deprecated TODO
+ *
+ * @param skeletonData
+ * @param materialCustomizer
+ */
constructor (
skeletonData: SkeletonData,
- private materialCustomerizer: SkeletonMeshMaterialParametersCustomizer = (
- material
- ) => { }
+ materialCustomizer: SkeletonMeshMaterialParametersCustomizer,
+ )
+ constructor (
+ skeletonDataOrConfiguration: SkeletonData | { skeletonData: SkeletonData, materialFactory?: (parameters: THREE.MaterialParameters) => Material },
+ materialCustomizer: SkeletonMeshMaterialParametersCustomizer = () => { }
) {
super();
- this.skeleton = new Skeleton(skeletonData);
- let animData = new AnimationStateData(skeletonData);
+ if (!('skeletonData' in skeletonDataOrConfiguration)) {
+ const materialFactory = () => {
+ const parameters: THREE.MaterialParameters = { ...SkeletonMesh.DEFAULT_MATERIAL_PARAMETERS };
+ materialCustomizer(parameters);
+ return new THREE.MeshBasicMaterial(parameters);
+ };
+ skeletonDataOrConfiguration = {
+ skeletonData: skeletonDataOrConfiguration,
+ materialFactory,
+ }
+ }
+
+ this.materialFactory = skeletonDataOrConfiguration.materialFactory ?? (() => new THREE.MeshStandardMaterial(SkeletonMesh.DEFAULT_MATERIAL_PARAMETERS));
+ this.skeleton = new Skeleton(skeletonDataOrConfiguration.skeletonData);
+ let animData = new AnimationStateData(skeletonDataOrConfiguration.skeletonData);
this.state = new AnimationState(animData);
+
+ Object.defineProperty(this, 'castShadow', {
+ get: () => this._castShadow,
+ set: (value: boolean) => {
+ this._castShadow = value;
+ this.traverse((child) => {
+ if (child instanceof MeshBatcher) {
+ child.castShadow = value;
+ }
+ });
+ },
+ });
+
+ Object.defineProperty(this, 'receiveShadow', {
+ get: () => this._receiveShadow,
+ set: (value: boolean) => {
+ this._receiveShadow = value;
+ // Propagate to children
+ this.traverse((child) => {
+ if (child instanceof MeshBatcher) {
+ child.receiveShadow = value;
+ }
+ });
+ },
+ });
}
update (deltaTime: number) {
@@ -162,7 +170,7 @@ export class SkeletonMesh extends THREE.Object3D {
private nextBatch () {
if (this.batches.length == this.nextBatchIndex) {
- let batch = new MeshBatcher(10920, this.materialCustomerizer);
+ let batch = new MeshBatcher(MeshBatcher.MAX_VERTICES, this.materialFactory);
this.add(batch);
this.batches.push(batch);
}
@@ -174,8 +182,6 @@ export class SkeletonMesh extends THREE.Object3D {
private updateGeometry () {
this.clearBatches();
- let tempPos = this.tempPos;
- let tempUv = this.tempUv;
let tempLight = this.tempLight;
let tempDark = this.tempDark;
let clipper = this.clipper;
diff --git a/spine-ts/spine-threejs/src/ThreeJsTexture.ts b/spine-ts/spine-threejs/src/ThreeJsTexture.ts
index 16961006fe..435e56385d 100644
--- a/spine-ts/spine-threejs/src/ThreeJsTexture.ts
+++ b/spine-ts/spine-threejs/src/ThreeJsTexture.ts
@@ -42,6 +42,10 @@ export class ThreeJsTexture extends Texture {
// if the texture is not pma, we ask to threejs to premultiply on upload
this.texture.premultiplyAlpha = !pma;
this.texture.flipY = false;
+
+ // this is necessary in future versions of ThreeJS
+ this.texture.colorSpace = THREE.SRGBColorSpace;
+
this.texture.needsUpdate = true;
}