Prechádzať zdrojové kódy

[ts][threejs] Allow to load pma textures. Non-pma textures are pma on upload. Fixed blending modes. Add physics example.
Close #2503.

Davide Tantillo 1 rok pred
rodič
commit
f3b3cb429a

+ 1 - 0
examples/export/runtimes.sh

@@ -456,6 +456,7 @@ rm "$ROOT/spine-ts/spine-threejs/example/assets/"*
 cp -f ../raptor/export/raptor-pro.json "$ROOT/spine-ts/spine-threejs/example/assets/"
 cp -f ../raptor/export/raptor.atlas "$ROOT/spine-ts/spine-threejs/example/assets/"
 cp -f ../raptor/export/raptor.png "$ROOT/spine-ts/spine-threejs/example/assets/"
+cp -f ../celestial-circus/export/* "$ROOT/spine-ts/spine-threejs/example/assets/"
 
 rm "$ROOT/spine-ts/spine-player/example/assets/"*
 cp -f ../raptor/export/raptor-pro.json "$ROOT/spine-ts/spine-player/example/assets/"

+ 3 - 0
spine-ts/index.html

@@ -178,6 +178,9 @@
         <li>
           <a href="/spine-threejs/example/logarithmic-depth-buffer.html">Logarithmic depth buffer</a>
         </li>
+        <li>
+          <a href="/spine-threejs/example/physics.html">Physics</a>
+        </li>
       </ul>
     </ul>
   </div>

+ 174 - 0
spine-ts/spine-threejs/example/assets/celestial-circus-pma.atlas

@@ -0,0 +1,174 @@
+celestial-circus-pma.png
+	size: 1024, 1024
+	filter: Linear, Linear
+	pma: true
+	scale: 0.4
+arm-back-down
+	bounds: 324, 401, 38, 82
+	rotate: 90
+arm-back-up
+	bounds: 290, 44, 83, 116
+	rotate: 90
+arm-front-down
+	bounds: 706, 2, 36, 78
+	rotate: 90
+arm-front-up
+	bounds: 860, 138, 77, 116
+bench
+	bounds: 725, 256, 189, 48
+body-bottom
+	bounds: 879, 868, 154, 124
+	rotate: 90
+body-top
+	bounds: 725, 128, 126, 133
+	rotate: 90
+chest
+	bounds: 408, 26, 104, 93
+cloud-back
+	bounds: 752, 378, 202, 165
+cloud-front
+	bounds: 2, 2, 325, 196
+	rotate: 90
+collar
+	bounds: 786, 13, 47, 26
+ear
+	bounds: 1002, 643, 20, 28
+eye-back-shadow
+	bounds: 428, 395, 14, 10
+eye-front-shadow
+	bounds: 704, 529, 24, 14
+eye-reflex-back
+	bounds: 860, 128, 8, 7
+	rotate: 90
+eye-reflex-front
+	bounds: 726, 386, 10, 7
+eye-white-back
+	bounds: 835, 23, 13, 16
+eye-white-front
+	bounds: 1005, 1000, 22, 17
+	rotate: 90
+eyelashes-down-back
+	bounds: 232, 329, 11, 6
+	rotate: 90
+eyelashes-down-front
+	bounds: 913, 851, 15, 6
+	rotate: 90
+eyelashes-top-back
+	bounds: 408, 395, 18, 10
+eyelashes-top-front
+	bounds: 702, 179, 30, 16
+	rotate: 90
+face
+	bounds: 514, 26, 93, 102
+	rotate: 90
+feathers-back
+	bounds: 954, 625, 46, 46
+feathers-front
+	bounds: 706, 40, 72, 86
+fringe-middle-back
+	bounds: 200, 6, 33, 52
+	rotate: 90
+fringe-middle-front
+	bounds: 878, 76, 60, 50
+	rotate: 90
+fringe-side-back
+	bounds: 780, 41, 27, 94
+	rotate: 90
+fringe-side-front
+	bounds: 939, 161, 26, 93
+glove-bottom-back
+	bounds: 954, 572, 51, 41
+	rotate: 90
+glove-bottom-front
+	bounds: 916, 256, 47, 48
+hair-back-1
+	bounds: 444, 395, 132, 306
+	rotate: 90
+hair-back-2
+	bounds: 438, 211, 80, 285
+	rotate: 90
+hair-back-3
+	bounds: 719, 306, 70, 268
+	rotate: 90
+hair-back-4
+	bounds: 438, 121, 88, 262
+	rotate: 90
+hair-back-5
+	bounds: 438, 293, 88, 279
+	rotate: 90
+hair-back-6
+	bounds: 200, 41, 88, 286
+hair-hat-shadow
+	bounds: 232, 398, 90, 41
+hand-back
+	bounds: 954, 673, 60, 47
+	rotate: 90
+hand-front
+	bounds: 967, 172, 53, 60
+hat-back
+	bounds: 954, 802, 64, 45
+	rotate: 90
+hat-front
+	bounds: 780, 70, 96, 56
+head-back
+	bounds: 618, 17, 102, 86
+	rotate: 90
+jabot
+	bounds: 967, 234, 70, 55
+	rotate: 90
+leg-back
+	bounds: 232, 441, 210, 333
+leg-front
+	bounds: 444, 529, 258, 320
+logo-brooch
+	bounds: 954, 545, 16, 25
+mouth
+	bounds: 408, 121, 22, 6
+neck
+	bounds: 232, 342, 39, 56
+	rotate: 90
+nose
+	bounds: 742, 529, 6, 7
+	rotate: 90
+nose-highlight
+	bounds: 719, 300, 4, 4
+nose-shadow
+	bounds: 869, 128, 7, 8
+pupil-back
+	bounds: 730, 529, 10, 14
+pupil-front
+	bounds: 254, 21, 12, 18
+rope-back
+	bounds: 232, 383, 10, 492
+	rotate: 90
+rope-front
+	bounds: 232, 383, 10, 492
+	rotate: 90
+rope-front-bottom
+	bounds: 954, 735, 42, 65
+skirt
+	bounds: 2, 776, 440, 246
+sock-bow
+	bounds: 408, 407, 33, 32
+spine-logo-body
+	bounds: 879, 853, 13, 32
+	rotate: 90
+star-big
+	bounds: 939, 141, 18, 24
+	rotate: 90
+star-medium
+	bounds: 742, 537, 6, 8
+	rotate: 90
+star-small
+	bounds: 719, 378, 3, 4
+	rotate: 90
+underskirt
+	bounds: 2, 329, 445, 228
+	rotate: 90
+underskirt-back
+	bounds: 444, 851, 433, 171
+wing-back
+	bounds: 290, 129, 146, 252
+wing-front
+	bounds: 704, 545, 304, 248
+	rotate: 90

BIN
spine-ts/spine-threejs/example/assets/celestial-circus-pma.png


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 817 - 0
spine-ts/spine-threejs/example/assets/celestial-circus-pro.json


BIN
spine-ts/spine-threejs/example/assets/celestial-circus-pro.skel


+ 173 - 0
spine-ts/spine-threejs/example/assets/celestial-circus.atlas

@@ -0,0 +1,173 @@
+celestial-circus.png
+	size: 1024, 1024
+	filter: Linear, Linear
+	scale: 0.4
+arm-back-down
+	bounds: 324, 401, 38, 82
+	rotate: 90
+arm-back-up
+	bounds: 290, 44, 83, 116
+	rotate: 90
+arm-front-down
+	bounds: 706, 2, 36, 78
+	rotate: 90
+arm-front-up
+	bounds: 860, 138, 77, 116
+bench
+	bounds: 725, 256, 189, 48
+body-bottom
+	bounds: 879, 868, 154, 124
+	rotate: 90
+body-top
+	bounds: 725, 128, 126, 133
+	rotate: 90
+chest
+	bounds: 408, 26, 104, 93
+cloud-back
+	bounds: 752, 378, 202, 165
+cloud-front
+	bounds: 2, 2, 325, 196
+	rotate: 90
+collar
+	bounds: 786, 13, 47, 26
+ear
+	bounds: 1002, 643, 20, 28
+eye-back-shadow
+	bounds: 428, 395, 14, 10
+eye-front-shadow
+	bounds: 704, 529, 24, 14
+eye-reflex-back
+	bounds: 860, 128, 8, 7
+	rotate: 90
+eye-reflex-front
+	bounds: 726, 386, 10, 7
+eye-white-back
+	bounds: 835, 23, 13, 16
+eye-white-front
+	bounds: 1005, 1000, 22, 17
+	rotate: 90
+eyelashes-down-back
+	bounds: 232, 329, 11, 6
+	rotate: 90
+eyelashes-down-front
+	bounds: 913, 851, 15, 6
+	rotate: 90
+eyelashes-top-back
+	bounds: 408, 395, 18, 10
+eyelashes-top-front
+	bounds: 702, 179, 30, 16
+	rotate: 90
+face
+	bounds: 514, 26, 93, 102
+	rotate: 90
+feathers-back
+	bounds: 954, 625, 46, 46
+feathers-front
+	bounds: 706, 40, 72, 86
+fringe-middle-back
+	bounds: 200, 6, 33, 52
+	rotate: 90
+fringe-middle-front
+	bounds: 878, 76, 60, 50
+	rotate: 90
+fringe-side-back
+	bounds: 780, 41, 27, 94
+	rotate: 90
+fringe-side-front
+	bounds: 939, 161, 26, 93
+glove-bottom-back
+	bounds: 954, 572, 51, 41
+	rotate: 90
+glove-bottom-front
+	bounds: 916, 256, 47, 48
+hair-back-1
+	bounds: 444, 395, 132, 306
+	rotate: 90
+hair-back-2
+	bounds: 438, 211, 80, 285
+	rotate: 90
+hair-back-3
+	bounds: 719, 306, 70, 268
+	rotate: 90
+hair-back-4
+	bounds: 438, 121, 88, 262
+	rotate: 90
+hair-back-5
+	bounds: 438, 293, 88, 279
+	rotate: 90
+hair-back-6
+	bounds: 200, 41, 88, 286
+hair-hat-shadow
+	bounds: 232, 398, 90, 41
+hand-back
+	bounds: 954, 673, 60, 47
+	rotate: 90
+hand-front
+	bounds: 967, 172, 53, 60
+hat-back
+	bounds: 954, 802, 64, 45
+	rotate: 90
+hat-front
+	bounds: 780, 70, 96, 56
+head-back
+	bounds: 618, 17, 102, 86
+	rotate: 90
+jabot
+	bounds: 967, 234, 70, 55
+	rotate: 90
+leg-back
+	bounds: 232, 441, 210, 333
+leg-front
+	bounds: 444, 529, 258, 320
+logo-brooch
+	bounds: 954, 545, 16, 25
+mouth
+	bounds: 408, 121, 22, 6
+neck
+	bounds: 232, 342, 39, 56
+	rotate: 90
+nose
+	bounds: 742, 529, 6, 7
+	rotate: 90
+nose-highlight
+	bounds: 719, 300, 4, 4
+nose-shadow
+	bounds: 869, 128, 7, 8
+pupil-back
+	bounds: 730, 529, 10, 14
+pupil-front
+	bounds: 254, 21, 12, 18
+rope-back
+	bounds: 232, 383, 10, 492
+	rotate: 90
+rope-front
+	bounds: 232, 383, 10, 492
+	rotate: 90
+rope-front-bottom
+	bounds: 954, 735, 42, 65
+skirt
+	bounds: 2, 776, 440, 246
+sock-bow
+	bounds: 408, 407, 33, 32
+spine-logo-body
+	bounds: 879, 853, 13, 32
+	rotate: 90
+star-big
+	bounds: 939, 141, 18, 24
+	rotate: 90
+star-medium
+	bounds: 742, 537, 6, 8
+	rotate: 90
+star-small
+	bounds: 719, 378, 3, 4
+	rotate: 90
+underskirt
+	bounds: 2, 329, 445, 228
+	rotate: 90
+underskirt-back
+	bounds: 444, 851, 433, 171
+wing-back
+	bounds: 290, 129, 146, 252
+wing-front
+	bounds: 704, 545, 304, 248
+	rotate: 90

BIN
spine-ts/spine-threejs/example/assets/celestial-circus.png


+ 150 - 0
spine-ts/spine-threejs/example/physics.html

@@ -0,0 +1,150 @@
+<html>
+  <head>
+    <meta charset="UTF-8" />
+    <title>spine-threejs</title>
+    <script src="https://unpkg.com/[email protected]/build/three.js"></script>
+    <script src="../dist/iife/spine-threejs.js"></script>
+    <script src="./OrbitalControls.js"></script>
+  </head>
+  <style>
+    * {
+      margin: 0;
+      padding: 0;
+    }
+
+    body,
+    html {
+      height: 100%;
+    }
+
+    canvas {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+    }
+  </style>
+
+  <body>
+    <script>
+      (function () {
+        let scene, camera, renderer;
+        let geometry, material, mesh, skeletonMesh;
+        let assetManager;
+        let canvas;
+        let controls;
+        let lastFrameTime = Date.now() / 1000;
+
+        let pma = false;
+        let baseUrl = "assets/";
+        let skeletonFile = "celestial-circus-pro.json";
+        let atlasFile = `celestial-circus${pma ? "-pma" : ""}.atlas`;
+
+        function init() {
+          // create the THREE.JS camera, scene and renderer (WebGL)
+          let width = window.innerWidth,
+            height = window.innerHeight;
+          camera = new THREE.PerspectiveCamera(75, width / height, 1, 3000);
+          camera.position.y = 0;
+          camera.position.z = 800;
+          scene = new THREE.Scene();
+          renderer = new THREE.WebGLRenderer();
+          renderer.setSize(width, height);
+          document.body.appendChild(renderer.domElement);
+          canvas = renderer.domElement;
+          controls = new OrbitControls(camera, renderer.domElement);
+
+          // load the assets required to display the Raptor model
+          assetManager = new spine.AssetManager(baseUrl, undefined, pma);
+          assetManager.loadText(skeletonFile);
+          assetManager.loadTextureAtlas(atlasFile);
+
+          requestAnimationFrame(load);
+        }
+
+        function load(name, scale) {
+          if (assetManager.isLoadingComplete()) {
+            // Add a box to the scene to which we attach the skeleton mesh
+            geometry = new THREE.BoxGeometry(200, 200, 200);
+            material = new THREE.MeshBasicMaterial({
+              color: 0xff0000,
+              wireframe: true,
+            });
+            mesh = new THREE.Mesh(geometry, material);
+            scene.add(mesh);
+
+            // Load the texture atlas using name.atlas and name.png from the AssetManager.
+            // The function passed to TextureAtlas is used to resolve relative paths.
+            atlas = assetManager.require(atlasFile);
+
+            // Create a AtlasAttachmentLoader that resolves region, mesh, boundingbox and path attachments
+            atlasLoader = new spine.AtlasAttachmentLoader(atlas);
+
+            // Create a SkeletonJson instance for parsing the .json file.
+            let skeletonJson = new spine.SkeletonJson(atlasLoader);
+
+            // Set the scale to apply during parsing, parse the file, and create a new skeleton.
+            skeletonJson.scale = 0.4;
+            let skeletonData = skeletonJson.readSkeletonData(
+              assetManager.require(skeletonFile)
+            );
+
+            // Create a SkeletonMesh from the data and attach it to the scene
+            skeletonMesh = new spine.SkeletonMesh(
+              skeletonData,
+              (parameters) => {
+                parameters.depthTest = true;
+                parameters.depthWrite = true;
+                parameters.alphaTest = 0.001;
+              }
+            );
+            skeletonMesh.state.setAnimation(0, "swing", true);
+            skeletonMesh.state.setAnimation(1, "eyeblink-long", true);
+            mesh.add(skeletonMesh);
+
+            skeletonMesh.position.y = -300;
+
+            requestAnimationFrame(render);
+          } else requestAnimationFrame(load);
+        }
+
+        let lastTime = Date.now();
+        function render() {
+          // calculate delta time for animation purposes
+          let now = Date.now() / 1000;
+          let delta = now - lastFrameTime;
+          lastFrameTime = now;
+
+          // resize canvas to use full page, adjust camera/renderer
+          resize();
+
+          // Update orbital controls
+          controls.update();
+
+          // update the animation
+          skeletonMesh.update(delta);
+
+          // render the scene
+          renderer.render(scene, camera);
+
+          requestAnimationFrame(render);
+        }
+
+        function resize() {
+          let w = window.innerWidth;
+          let h = window.innerHeight;
+          if (canvas.width != w || canvas.height != h) {
+            canvas.width = w;
+            canvas.height = h;
+          }
+
+          camera.aspect = w / h;
+          camera.updateProjectionMatrix();
+
+          renderer.setSize(w, h);
+        }
+
+        init();
+      })();
+    </script>
+  </body>
+</html>

+ 2 - 2
spine-ts/spine-threejs/src/AssetManager.ts

@@ -31,9 +31,9 @@ import { AssetManagerBase, Downloader } from "@esotericsoftware/spine-core"
 import { ThreeJsTexture } from "./ThreeJsTexture.js";
 
 export class AssetManager extends AssetManagerBase {
-	constructor (pathPrefix: string = "", downloader: Downloader = new Downloader()) {
+	constructor (pathPrefix: string = "", downloader: Downloader = new Downloader(), pma = false) {
 		super((image: HTMLImageElement | ImageBitmap) => {
-			return new ThreeJsTexture(image);
+			return new ThreeJsTexture(image, pma);
 		}, pathPrefix, downloader);
 	}
 }

+ 13 - 9
spine-ts/spine-threejs/src/MeshBatcher.ts

@@ -29,7 +29,7 @@
 
 import { SkeletonMeshMaterial, SkeletonMeshMaterialParametersCustomizer } from "./SkeletonMesh.js";
 import * as THREE from "three"
-import { ThreeJsTexture } from "./ThreeJsTexture.js";
+import { ThreeJsTexture, ThreeBlendOptions } from "./ThreeJsTexture.js";
 import { BlendMode } from "@esotericsoftware/spine-core";
 
 export class MeshBatcher extends THREE.Mesh {
@@ -163,7 +163,7 @@ export class MeshBatcher extends THREE.Mesh {
 	}
 
 	findMaterialGroup (slotTexture: THREE.Texture, slotBlendMode: BlendMode) {
-		const blending = ThreeJsTexture.toThreeJsBlending(slotBlendMode);
+		const blendingObject = ThreeJsTexture.toThreeJsBlending(slotBlendMode);
 		let group = -1;
 
 		if (Array.isArray(this.material)) {
@@ -171,17 +171,23 @@ export class MeshBatcher extends THREE.Mesh {
 				const meshMaterial = this.material[i] as SkeletonMeshMaterial;
 
 				if (!meshMaterial.uniforms.map.value) {
-					updateMeshMaterial(meshMaterial, slotTexture, blending);
+					updateMeshMaterial(meshMaterial, slotTexture, blendingObject);
 					return i;
 				}
 
-				if (meshMaterial.uniforms.map.value === slotTexture && meshMaterial.blending === blending) {
+				if (meshMaterial.uniforms.map.value === slotTexture
+					&& blendingObject.blending === meshMaterial.blending
+					&& (blendingObject.blendSrc === undefined || blendingObject.blendSrc === meshMaterial.blendSrc)
+					&& (blendingObject.blendDst === undefined || blendingObject.blendDst === meshMaterial.blendDst)
+					&& (blendingObject.blendSrcAlpha === undefined || blendingObject.blendSrcAlpha === meshMaterial.blendSrcAlpha)
+					&& (blendingObject.blendDstAlpha === undefined || blendingObject.blendDstAlpha === meshMaterial.blendDstAlpha)
+				) {
 					return i;
 				}
 			}
 
 			const meshMaterial = new SkeletonMeshMaterial(this.materialCustomizer);
-			updateMeshMaterial(meshMaterial, slotTexture, blending);
+			updateMeshMaterial(meshMaterial, slotTexture, blendingObject);
 			this.material.push(meshMaterial);
 			group = this.material.length - 1;
 		} else {
@@ -192,10 +198,8 @@ export class MeshBatcher extends THREE.Mesh {
 	}
 }
 
-function updateMeshMaterial (meshMaterial: SkeletonMeshMaterial, slotTexture: THREE.Texture, blending: THREE.Blending) {
+function updateMeshMaterial (meshMaterial: SkeletonMeshMaterial, slotTexture: THREE.Texture, blending: ThreeBlendOptions) {
 	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;
+	Object.assign(meshMaterial, blending);
 	meshMaterial.needsUpdate = true;
 }

+ 5 - 3
spine-ts/spine-threejs/src/SkeletonMesh.ts

@@ -97,6 +97,8 @@ export class SkeletonMeshMaterial extends THREE.ShaderMaterial {
 			parameters.uniforms["alphaTest"] = { value: parameters.alphaTest };
 		}
 		super(parameters);
+		// non-pma textures are premultiply on upload, so we set premultipliedAlpha to true
+		this.premultipliedAlpha = true;
 	}
 }
 
@@ -241,9 +243,9 @@ export class SkeletonMesh extends THREE.Object3D {
 				let alpha = skeletonColor.a * slotColor.a * attachmentColor.a;
 				let color = this.tempColor;
 				color.set(
-					skeletonColor.r * slotColor.r * attachmentColor.r,
-					skeletonColor.g * slotColor.g * attachmentColor.g,
-					skeletonColor.b * slotColor.b * attachmentColor.b,
+					skeletonColor.r * slotColor.r * attachmentColor.r * alpha,
+					skeletonColor.g * slotColor.g * attachmentColor.g * alpha,
+					skeletonColor.b * slotColor.b * attachmentColor.b * alpha,
 					alpha
 				);
 

+ 28 - 6
spine-ts/spine-threejs/src/ThreeJsTexture.ts

@@ -33,12 +33,14 @@ import * as THREE from "three";
 export class ThreeJsTexture extends Texture {
 	texture: THREE.Texture;
 
-	constructor (image: HTMLImageElement | ImageBitmap) {
+	constructor (image: HTMLImageElement | ImageBitmap, pma = false) {
 		super(image);
 		if (image instanceof ImageBitmap)
 			this.texture = new THREE.CanvasTexture(image);
 		else
 			this.texture = new THREE.Texture(image);
+		// if the texture is not pma, we ask to threejs to premultiply on upload
+		this.texture.premultiplyAlpha = !pma;
 		this.texture.flipY = false;
 		this.texture.needsUpdate = true;
 	}
@@ -74,11 +76,31 @@ export class ThreeJsTexture extends Texture {
 		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;
+	static toThreeJsBlending (blend: BlendMode): ThreeBlendOptions {
+		if (blend === BlendMode.Normal) return { blending: THREE.NormalBlending };
+		else if (blend === BlendMode.Additive) return { blending: THREE.AdditiveBlending };
+		else if (blend === BlendMode.Multiply) return {
+			blending: THREE.CustomBlending,
+			blendSrc: THREE.DstColorFactor,
+			blendDst: THREE.OneMinusSrcAlphaFactor,
+			blendSrcAlpha: THREE.OneFactor,
+			blendDstAlpha: THREE.OneMinusSrcAlphaFactor,
+		}
+		else if (blend === BlendMode.Screen) return {
+			blending: THREE.CustomBlending,
+			blendSrc: THREE.OneFactor,
+			blendDst: THREE.OneMinusSrcColorFactor,
+			blendSrcAlpha: THREE.OneFactor,
+			blendDstAlpha: THREE.OneMinusSrcColorFactor,
+		}
 		else throw new Error("Unknown blendMode: " + blend);
 	}
 }
+
+export type ThreeBlendOptions = {
+	blending: THREE.Blending,
+	blendSrc?: THREE.BlendingDstFactor,
+	blendDst?: THREE.BlendingDstFactor,
+	blendSrcAlpha?: THREE.BlendingDstFactor,
+	blendDstAlpha?: THREE.BlendingDstFactor,
+}

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov