Explorar o código

[xna][monogame] Added support for vertex effects, see #898

badlogic %!s(int64=8) %!d(string=hai) anos
pai
achega
b5f79f80c3

+ 1 - 0
CHANGELOG.md

@@ -137,6 +137,7 @@
  * Removed `RegionBatcher` and `SkeletonRegionRenderer`, renamed `SkeletonMeshRenderer` to `SkeletonRenderer`
  * Added support for two color tint. For it to work, you need to add the `SpineEffect.fx` file to your content project, then load it via `var effect = Content.Load<Effect>("SpineEffect");`, and set it on the `SkeletonRenderer`. See the example project for code.
  * Added support for any `Effect` to be used by `SkeletonRenderer`
+ * Added support for `IVertexEffect` to modify vertices of skeletons on the CPU. `IVertexEffect` instances can be set on the `SkeletonRenderer`. See example project.
 
 ## Java
  * **Breaking changes**

+ 46 - 0
spine-csharp/src/MathUtils.cs

@@ -46,6 +46,8 @@ namespace Spine {
 		const float DegToIndex = SIN_COUNT / DegFull;
 		static float[] sin = new float[SIN_COUNT];
 
+		static Random random = new Random();
+
 		static MathUtils () {
 			for (int i = 0; i < SIN_COUNT; i++)
 				sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull);
@@ -96,5 +98,49 @@ namespace Spine {
 			if (value > max) return max;
 			return value;
 		}
+
+		static public float RandomTriangle(float min, float max) {
+			return RandomTriangle(min, max, (min + max) * 0.5f);
+		}
+
+		static public float RandomTriangle(float min, float max, float mode) {
+			float u = (float)random.NextDouble();
+			float d = max - min;
+			if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min));
+			return max - (float)Math.Sqrt((1 - u) * d * (max - mode));
+		}
+	}
+
+	public abstract class IInterpolation {
+		public static IInterpolation Pow2 = new Pow(2);
+		public static IInterpolation Pow2Out = new PowOut(2);
+
+		protected abstract float Apply(float a);
+
+		public float Apply(float start, float end, float a) {
+			return start + (end - start) * Apply(a);
+		}
+	}
+
+	public class Pow: IInterpolation {
+		public float Power { get; set; }
+
+		public Pow(float power) {
+			Power = power;
+		}
+
+		protected override float Apply(float a) {
+			if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2;
+			return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1;
+		}
+	}
+
+	public class PowOut : Pow {
+		public PowOut(float power) : base(power) {
+		}
+
+		protected override float Apply(float a) {
+			return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1;
+		}
 	}
 }

+ 8 - 17
spine-xna/example/spine-xna-example.csproj

@@ -119,7 +119,7 @@
     <None Include="data\coin.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Include="data\coin.skel">
+    <None Include="data\coin-pro.skel">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
     <None Include="data\goblins.png">
@@ -134,26 +134,17 @@
     <Content Include="data\tank.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="data\TwoColorTest.png">
-      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
-    </Content>
     <Content Include="Game.ico" />
     <Content Include="GameThumbnail.png" />
     <None Include="data\tank.atlas">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Include="data\tank.json">
+    <None Include="data\tank-pro.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Include="data\tank.skel">
+    <None Include="data\tank-pro.skel">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Include="data\TwoColorTest.atlas">
-      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
-    </None>
-    <None Include="data\TwoColorTest.json">
-      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
-    </None>
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\spine-csharp\spine-csharp.csproj">
@@ -201,28 +192,28 @@
     <None Include="data\coin.atlas">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Include="data\coin.json">
+    <None Include="data\coin-pro.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
     <None Include="data\goblins-mesh.atlas">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Include="data\goblins-mesh.json">
+    <None Include="data\goblins-pro.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Include="data\goblins-mesh.skel">
+    <None Include="data\goblins-pro.skel">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
     <None Include="data\raptor.atlas">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Include="data\raptor.json">
+    <None Include="data\raptor-pro.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
     <None Include="data\spineboy.atlas">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Include="data\spineboy.json">
+    <None Include="data\spineboy-ess.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
   </ItemGroup>

+ 34 - 29
spine-xna/example/src/ExampleGame.cs

@@ -47,6 +47,9 @@ namespace Spine {
 		Slot headSlot;
 		AnimationState state;
 		SkeletonBounds bounds = new SkeletonBounds();
+		JitterEffect jitter = new JitterEffect(10, 10);
+		SwirlEffect swirl = new SwirlEffect(600);
+		float swirlTime;
 
 #if WINDOWS_STOREAPP
 		private string assetsFolder = @"Assets\";
@@ -77,24 +80,23 @@ namespace Spine {
 
 			skeletonRenderer = new SkeletonRenderer(GraphicsDevice);
 			skeletonRenderer.PremultipliedAlpha = false;
-			skeletonRenderer.Effect = spineEffect;
-
-			// String name = "spineboy";
-			// String name = "goblins-mesh";
-			// String name = "raptor";
-			// String name = "tank";
-			// String name = "coin";
-			String name = "TwoColorTest";
+			skeletonRenderer.Effect = spineEffect;			
+			skeletonRenderer.VertexEffect = swirl;
+
+			// String name = "spineboy-ess";
+			// String name = "goblins-pro";
+			String name = "raptor-pro";
+			// String name = "tank-pro";
+			// String name = "coin-pro";			
 			bool binaryData = false;
 
-			Atlas atlas = new Atlas(assetsFolder + name + ".atlas", new XnaTextureLoader(GraphicsDevice));			
+			Atlas atlas = new Atlas(assetsFolder + name.Replace("-ess", "").Replace("-pro", "") + ".atlas", new XnaTextureLoader(GraphicsDevice));			
 
 			float scale = 1;
-			if (name == "spineboy") scale = 0.6f;
-			if (name == "raptor") scale = 0.5f;
-			if (name == "tank") scale = 0.3f;
-			if (name == "coin") scale = 1;
-			if (name == "TwoColorTest") scale = 0.5f;
+			if (name == "spineboy-ess") scale = 0.6f;
+			if (name == "raptor-pro") scale = 0.5f;
+			if (name == "tank-pro") scale = 0.3f;
+			if (name == "coin-pro") scale = 1;
 
 			SkeletonData skeletonData;
 			if (binaryData) {
@@ -107,13 +109,13 @@ namespace Spine {
 				skeletonData = json.ReadSkeletonData(assetsFolder + name + ".json");
 			}
 			skeleton = new Skeleton(skeletonData);
-			if (name == "goblins-mesh") skeleton.SetSkin("goblin");
+			if (name == "goblins-pro") skeleton.SetSkin("goblin");
 
 			// Define mixing between animations.
 			AnimationStateData stateData = new AnimationStateData(skeleton.Data);
 			state = new AnimationState(stateData);
 
-			if (name == "spineboy") {
+			if (name == "spineboy-ess") {
 				stateData.SetMix("run", "jump", 0.2f);
 				stateData.SetMix("jump", "run", 0.4f);
 
@@ -122,31 +124,27 @@ namespace Spine {
 				state.End += End;
 				state.Complete += Complete;
 				state.Event += Event;
-
-				state.SetAnimation(0, "test", false);
-				TrackEntry entry = state.AddAnimation(0, "jump", false, 0);
+			
+				TrackEntry entry = state.SetAnimation(0, "jump", false);
 				entry.End += End; // Event handling for queued animations.
 				state.AddAnimation(0, "run", true, 0);
 			}
-			else if (name == "raptor") {
+			else if (name == "raptor-pro") {
 				state.SetAnimation(0, "walk", true);
-				state.AddAnimation(1, "gungrab", false, 2);
+				state.AddAnimation(1, "gun-grab", false, 2);
 			}
-			else if (name == "coin") {
+			else if (name == "coin-pro") {
 				state.SetAnimation(0, "rotate", true);
 			}
-			else if (name == "tank") {
+			else if (name == "tank-pro") {
 				state.SetAnimation(0, "drive", true);
 			}
-			else if (name == "TwoColorTest") {
-				state.SetAnimation(0, "animation", true);
-			}
 			else {
 				state.SetAnimation(0, "walk", true);
 			}
 
 			skeleton.X = 400 + (name == "tank" ? 300: 0);
-			skeleton.Y = 580 + (name == "TwoColorTest" ? -400 : 0);
+			skeleton.Y = 600;		
 			skeleton.UpdateWorldTransform();
 
 			headSlot = skeleton.FindSlot("head");
@@ -168,14 +166,21 @@ namespace Spine {
 		protected override void Draw (GameTime gameTime) {
 			GraphicsDevice.Clear(Color.Black);
 
-			state.Update(gameTime.ElapsedGameTime.Milliseconds / 1000f);
+			float delta = gameTime.ElapsedGameTime.Milliseconds / 1000f;
+			swirlTime += delta;
+			float percent = swirlTime % 2;
+			if (percent > 1) percent = 1 - (percent - 1);
+			swirl.Angle = (IInterpolation.Pow2.Apply(-60, 60, percent));
+
+			state.Update(delta);
 			state.Apply(skeleton);			
 			skeleton.UpdateWorldTransform();
 			if (skeletonRenderer.Effect is BasicEffect) {
 				((BasicEffect)skeletonRenderer.Effect).Projection = Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 1, 0);
 			} else {
 				skeletonRenderer.Effect.Parameters["Projection"].SetValue(Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 1, 0));
-			}			
+			}
+			
 			skeletonRenderer.Begin();
 			skeletonRenderer.Draw(skeleton);
 			skeletonRenderer.End();

+ 1 - 0
spine-xna/spine-xna.csproj

@@ -110,6 +110,7 @@
   <ItemGroup>
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="src\MeshBatcher.cs" />
+    <Compile Include="src\VertexEffect.cs" />
     <Compile Include="src\XnaTextureLoader.cs" />
     <Compile Include="src\Util.cs" />
   </ItemGroup>

+ 2 - 0
spine-xna/spine-xna.sln

@@ -25,11 +25,13 @@ Global
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Debug|Any CPU.Build.0 = Debug|x86
 		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
 		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Debug|Mixed Platforms.Build.0 = Debug|x86
 		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Debug|x86.ActiveCfg = Debug|x86
 		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Debug|x86.Build.0 = Debug|x86
 		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Release|Any CPU.ActiveCfg = Release|x86
+		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Release|Any CPU.Build.0 = Release|x86
 		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Release|Mixed Platforms.ActiveCfg = Release|x86
 		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Release|Mixed Platforms.Build.0 = Release|x86
 		{7F8F2327-C016-49C8-BB4D-F3F77971961E}.Release|x86.ActiveCfg = Release|x86

+ 5 - 0
spine-xna/src/SkeletonRenderer.cs

@@ -52,6 +52,7 @@ namespace Spine {
 
 		Effect effect;
 		public Effect Effect { get { return effect; } set { effect = value; } }
+		public IVertexEffect VertexEffect { get; set; }
 
 		private bool premultipliedAlpha;
 		public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } }
@@ -94,6 +95,8 @@ namespace Spine {
 			float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A;
 			Color color = new Color();
 
+			if (VertexEffect != null) VertexEffect.Begin(skeleton);
+
 			for (int i = 0, n = drawOrder.Count; i < n; i++) {
 				Slot slot = drawOrderItems[i];
 				Attachment attachment = slot.Attachment;
@@ -194,11 +197,13 @@ namespace Spine {
 					itemVertices[ii].Position.Z = 0;
 					itemVertices[ii].TextureCoordinate.X = uvs[v];
 					itemVertices[ii].TextureCoordinate.Y = uvs[v + 1];
+					if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]);
 				}
 
 				clipper.ClipEnd(slot);
 			}
 			clipper.ClipEnd();
+			if (VertexEffect != null) VertexEffect.End();
 		}
 	}
 }

+ 99 - 0
spine-xna/src/VertexEffect.cs

@@ -0,0 +1,99 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
+ * or other intellectual property or proprietary rights notices on or in the
+ * Software, including any copy thereof. Redistributions in binary or source
+ * form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Spine {
+	public interface IVertexEffect {
+		void Begin(Skeleton skeleton);
+		void Transform(ref VertexPositionColorTextureColor vertex);
+		void End();
+	}
+
+	public class JitterEffect : IVertexEffect {
+		public float JitterX { get; set; }
+		public float JitterY { get; set; }
+
+		public JitterEffect(float jitterX, float jitterY) {
+			JitterX = jitterX;
+			JitterY = jitterY;
+		}
+
+		public void Begin(Skeleton skeleton) {
+		}
+
+		public void End() {
+		}
+
+		public void Transform(ref VertexPositionColorTextureColor vertex) {
+			vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY);
+			vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY);
+		}
+	}
+
+	public class SwirlEffect : IVertexEffect {
+		private float worldX, worldY, angle;		
+
+		public float Radius { get; set; }
+		public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } }
+		public float CenterX { get; set; }
+		public float CenterY { get; set; }
+		public IInterpolation Interpolation { get; set; }
+
+		public SwirlEffect(float radius) {
+			Radius = radius;
+			Interpolation = IInterpolation.Pow2;
+		}
+
+		public void Begin(Skeleton skeleton) {
+			worldX = skeleton.X + CenterX;
+			worldY = skeleton.Y + CenterY;		
+		}
+
+		public void End() {			
+		}
+
+		public void Transform(ref VertexPositionColorTextureColor vertex) {
+			float x = vertex.Position.X - worldX;
+			float y = vertex.Position.Y - worldY;
+			float dist = (float)Math.Sqrt(x * x + y * y);
+			if (dist < Radius) {
+				float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius);
+				float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta);
+				vertex.Position.X = cos * x - sin * y + worldX;
+				vertex.Position.Y = sin * x + cos * y + worldY;
+			}
+		}
+	}
+}