mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 09:16:01 +08:00
[xna][monogame] Added support for vertex effects, see #898
This commit is contained in:
parent
c06bd8f181
commit
b5f79f80c3
@ -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,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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\";
|
||||
@ -78,23 +81,22 @@ namespace Spine {
|
||||
skeletonRenderer = new SkeletonRenderer(GraphicsDevice);
|
||||
skeletonRenderer.PremultipliedAlpha = false;
|
||||
skeletonRenderer.Effect = spineEffect;
|
||||
skeletonRenderer.VertexEffect = swirl;
|
||||
|
||||
// String name = "spineboy";
|
||||
// String name = "goblins-mesh";
|
||||
// String name = "raptor";
|
||||
// String name = "tank";
|
||||
// String name = "coin";
|
||||
String name = "TwoColorTest";
|
||||
// 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);
|
||||
|
||||
@ -123,30 +125,26 @@ namespace Spine {
|
||||
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,7 +166,13 @@ 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) {
|
||||
@ -176,6 +180,7 @@ namespace Spine {
|
||||
} 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();
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
spine-xna/src/VertexEffect.cs
Normal file
99
spine-xna/src/VertexEffect.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user