416 lines
16 KiB
C#
416 lines
16 KiB
C#
using Barotrauma.SpriteDeformations;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
partial class DeformableSprite
|
|
{
|
|
private static List<DeformableSprite> list = new List<DeformableSprite>();
|
|
|
|
private bool initialized = false;
|
|
|
|
private int triangleCount;
|
|
|
|
private VertexBuffer vertexBuffer, flippedVertexBuffer;
|
|
private IndexBuffer indexBuffer;
|
|
|
|
private Vector2 uvTopLeft, uvBottomRight;
|
|
private Vector2 uvTopLeftFlipped, uvBottomRightFlipped;
|
|
|
|
private Vector2[] deformAmount;
|
|
private int deformArrayWidth, deformArrayHeight;
|
|
|
|
private int subDivX, subDivY;
|
|
|
|
private static Effect effect;
|
|
public static Effect Effect
|
|
{
|
|
get { return effect; }
|
|
}
|
|
|
|
public bool Invert { get; set; }
|
|
|
|
private Point spritePos;
|
|
private Point spriteSize;
|
|
|
|
partial void InitProjSpecific(XElement element, int? subdivisionsX, int? subdivisionsY, bool lazyLoad, bool invert)
|
|
{
|
|
if (effect == null)
|
|
{
|
|
effect = EffectLoader.Load("Effects/deformshader");
|
|
}
|
|
|
|
Invert = invert;
|
|
|
|
//use subdivisions configured in the xml if the arguments passed to the method are null
|
|
Vector2 subdivisionsInXml = element.GetAttributeVector2("subdivisions", Vector2.One);
|
|
subDivX = subdivisionsX ?? (int)subdivisionsInXml.X;
|
|
subDivY = subdivisionsY ?? (int)subdivisionsInXml.Y;
|
|
|
|
if (subDivX <= 0 || subDivY <= 0)
|
|
{
|
|
throw new ArgumentException("Deformable sprites must have one or more subdivisions on each axis.");
|
|
}
|
|
|
|
if (!lazyLoad)
|
|
{
|
|
Init();
|
|
}
|
|
|
|
list.Add(this);
|
|
}
|
|
|
|
public void EnsureLazyLoaded()
|
|
{
|
|
if (!initialized) { Init(); }
|
|
}
|
|
|
|
private void Init()
|
|
{
|
|
if (initialized) { return; }
|
|
initialized = true;
|
|
|
|
foreach (DeformableSprite existing in list)
|
|
{
|
|
if (!existing.initialized || existing == this) { continue; }
|
|
//share vertex and index buffers if there's already
|
|
//an existing sprite with the same texture and subdivisions
|
|
if (existing.Sprite.Texture == Sprite.Texture &&
|
|
existing.subDivX == subDivX &&
|
|
existing.subDivY == subDivY &&
|
|
existing.Sprite.SourceRect == Sprite.SourceRect)
|
|
{
|
|
vertexBuffer = existing.vertexBuffer;
|
|
flippedVertexBuffer = existing.flippedVertexBuffer;
|
|
indexBuffer = existing.indexBuffer;
|
|
triangleCount = existing.triangleCount;
|
|
uvTopLeft = existing.uvTopLeft;
|
|
uvBottomRight = existing.uvBottomRight;
|
|
uvTopLeftFlipped = existing.uvTopLeftFlipped;
|
|
uvBottomRightFlipped = existing.uvBottomRightFlipped;
|
|
|
|
Deform(new Vector2[,]
|
|
{
|
|
{ Vector2.Zero, Vector2.Zero },
|
|
{ Vector2.Zero, Vector2.Zero }
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (Sprite.Texture != null)
|
|
{
|
|
SetupVertexBuffers();
|
|
SetupIndexBuffer();
|
|
}
|
|
}
|
|
|
|
private void SetupVertexBuffers()
|
|
{
|
|
Vector2 textureSize = new Vector2(Sprite.Texture.Width, Sprite.Texture.Height);
|
|
var pos = Sprite.SourceRect.Location;
|
|
var size = Sprite.SourceRect.Size;
|
|
|
|
uvTopLeft = Vector2.Divide(pos.ToVector2(), textureSize);
|
|
uvBottomRight = Vector2.Divide((pos + size).ToVector2(), textureSize);
|
|
uvTopLeftFlipped = Vector2.Divide(new Vector2(pos.X + size.X, pos.Y), textureSize);
|
|
uvBottomRightFlipped = Vector2.Divide(new Vector2(pos.X, pos.Y + size.Y), textureSize);
|
|
if (Invert)
|
|
{
|
|
var temp = uvBottomRightFlipped;
|
|
uvBottomRightFlipped = uvTopLeftFlipped;
|
|
uvTopLeftFlipped = temp;
|
|
}
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
bool flip = i == 1;
|
|
|
|
var vertices = new VertexPositionColorTexture[(subDivX + 1) * (subDivY + 1)];
|
|
for (int x = 0; x <= subDivX; x++)
|
|
{
|
|
for (int y = 0; y <= subDivY; y++)
|
|
{
|
|
//{0,0} -> {1,1}
|
|
Vector2 relativePos = new Vector2(x / (float)subDivX, y / (float)subDivY);
|
|
|
|
Vector2 uvCoord = flip ?
|
|
uvTopLeftFlipped + (uvBottomRightFlipped - uvTopLeftFlipped) * relativePos :
|
|
uvTopLeft + (uvBottomRight - uvTopLeft) * relativePos;
|
|
|
|
vertices[x + y * (subDivX + 1)] = new VertexPositionColorTexture(
|
|
position: new Vector3(relativePos.X * Sprite.SourceRect.Width, relativePos.Y * Sprite.SourceRect.Height, 0.0f),
|
|
color: Color.White,
|
|
textureCoordinate: uvCoord);
|
|
}
|
|
}
|
|
|
|
if (flip)
|
|
{
|
|
if (flippedVertexBuffer != null && flippedVertexBuffer.VertexCount != vertices.Length)
|
|
{
|
|
flippedVertexBuffer.Dispose();
|
|
flippedVertexBuffer = null;
|
|
}
|
|
if (flippedVertexBuffer == null)
|
|
{
|
|
flippedVertexBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.None);
|
|
}
|
|
flippedVertexBuffer.SetData(vertices);
|
|
}
|
|
else
|
|
{
|
|
if (vertexBuffer != null && vertexBuffer.VertexCount != vertices.Length)
|
|
{
|
|
vertexBuffer.Dispose();
|
|
vertexBuffer = null;
|
|
}
|
|
if (vertexBuffer == null)
|
|
{
|
|
vertexBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.None);
|
|
}
|
|
vertexBuffer.SetData(vertices);
|
|
}
|
|
}
|
|
|
|
spritePos = Sprite.SourceRect.Location;
|
|
spriteSize = Sprite.SourceRect.Size;
|
|
}
|
|
|
|
private void SetupIndexBuffer()
|
|
{
|
|
triangleCount = subDivX * subDivY * 2;
|
|
var indices = new ushort[triangleCount * 3];
|
|
int offset = 0;
|
|
for (int i = 0; i < triangleCount / 2; i++)
|
|
{
|
|
indices[i * 6] = (ushort)(i + offset + 1);
|
|
indices[i * 6 + 1] = (ushort)(i + offset + (subDivX + 1) + 1);
|
|
indices[i * 6 + 2] = (ushort)(i + offset + (subDivX + 1));
|
|
|
|
indices[i * 6 + 3] = (ushort)(i + offset);
|
|
indices[i * 6 + 4] = (ushort)(i + offset + 1);
|
|
indices[i * 6 + 5] = (ushort)(i + offset + (subDivX + 1));
|
|
|
|
if ((i + 1) % subDivX == 0) offset++;
|
|
}
|
|
|
|
indexBuffer?.Dispose();
|
|
indexBuffer = null;
|
|
|
|
indexBuffer = new IndexBuffer(GameMain.Instance.GraphicsDevice, IndexElementSize.SixteenBits, indices.Length, BufferUsage.None);
|
|
indexBuffer.SetData(indices);
|
|
|
|
Deform(new Vector2[,]
|
|
{
|
|
{ Vector2.Zero, Vector2.Zero },
|
|
{ Vector2.Zero, Vector2.Zero }
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deform the vertices of the sprite using an arbitrary function. The in-parameter of the function is the
|
|
/// normalized position of the vertex (i.e. 0,0 = top-left corner of the sprite, 1,1 = bottom-right) and the output
|
|
/// is the amount of deformation.
|
|
/// </summary>
|
|
public void Deform(Func<Vector2, Vector2> deformFunction)
|
|
{
|
|
if (!initialized) { Init(); }
|
|
|
|
var deformAmount = new Vector2[subDivX + 1, subDivY + 1];
|
|
for (int x = 0; x <= subDivX; x++)
|
|
{
|
|
for (int y = 0; y <= subDivY; y++)
|
|
{
|
|
deformAmount[x, y] = deformFunction(new Vector2(x / (float)subDivX, y / (float)subDivY));
|
|
}
|
|
}
|
|
Deform(deformAmount);
|
|
}
|
|
|
|
public void Deform(Vector2[,] deform)
|
|
{
|
|
if (!initialized) { Init(); }
|
|
|
|
deformArrayWidth = deform.GetLength(0);
|
|
deformArrayHeight = deform.GetLength(1);
|
|
if (deformAmount == null || deformAmount.Length != deformArrayWidth * deformArrayHeight)
|
|
{
|
|
deformAmount = new Vector2[deformArrayWidth * deformArrayHeight];
|
|
}
|
|
for (int x = 0; x < deformArrayWidth; x++)
|
|
{
|
|
for (int y = 0; y < deformArrayHeight; y++)
|
|
{
|
|
deformAmount[x + y * deformArrayWidth] = deform[x, y];
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
Deform(new Vector2[,]
|
|
{
|
|
{ Vector2.Zero, Vector2.Zero },
|
|
{ Vector2.Zero, Vector2.Zero }
|
|
});
|
|
}
|
|
|
|
public Matrix GetTransform(Vector3 pos, Vector2 origin, float rotate, Vector2 scale)
|
|
{
|
|
if (!initialized) { Init(); }
|
|
|
|
return
|
|
Matrix.CreateTranslation(-origin.X, -origin.Y, 0) *
|
|
Matrix.CreateScale(scale.X, -scale.Y, 1.0f) *
|
|
Matrix.CreateRotationZ(-rotate) *
|
|
Matrix.CreateTranslation(pos);
|
|
}
|
|
|
|
public void Draw(Camera cam, Vector3 pos, Vector2 origin, float rotate, Vector2 scale, Color color, bool mirror = false, bool invert = false)
|
|
{
|
|
if (Sprite.Texture == null) { return; }
|
|
if (!initialized) { Init(); }
|
|
|
|
// If the source rect is modified, we should recalculate the vertex buffer.
|
|
if (Sprite.SourceRect.Location != spritePos || Sprite.SourceRect.Size != spriteSize)
|
|
{
|
|
SetupVertexBuffers();
|
|
}
|
|
|
|
#if (LINUX || OSX)
|
|
effect.Parameters["TextureSampler+xTexture"].SetValue(Sprite.Texture);
|
|
#else
|
|
effect.Parameters["xTexture"].SetValue(Sprite.Texture);
|
|
#endif
|
|
|
|
Matrix matrix = GetTransform(pos, origin, rotate, scale);
|
|
effect.Parameters["xTransform"].SetValue(matrix * cam.ShaderTransform
|
|
* Matrix.CreateOrthographic(cam.Resolution.X, cam.Resolution.Y, -1, 1) * 0.5f);
|
|
effect.Parameters["tintColor"].SetValue(color.ToVector4());
|
|
effect.Parameters["deformArray"].SetValue(deformAmount);
|
|
effect.Parameters["deformArrayWidth"].SetValue(deformArrayWidth);
|
|
effect.Parameters["deformArrayHeight"].SetValue(deformArrayHeight);
|
|
if (invert)
|
|
{
|
|
mirror = !mirror;
|
|
}
|
|
effect.Parameters["uvTopLeft"].SetValue(mirror ? uvTopLeftFlipped : uvTopLeft);
|
|
effect.Parameters["uvBottomRight"].SetValue(mirror ? uvBottomRightFlipped : uvBottomRight);
|
|
effect.GraphicsDevice.SetVertexBuffer(mirror ? flippedVertexBuffer : vertexBuffer);
|
|
effect.GraphicsDevice.Indices = indexBuffer;
|
|
effect.CurrentTechnique.Passes[0].Apply();
|
|
effect.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, triangleCount);
|
|
}
|
|
|
|
public void Remove()
|
|
{
|
|
Sprite?.Remove();
|
|
Sprite = null;
|
|
|
|
list.Remove(this);
|
|
|
|
foreach (DeformableSprite otherSprite in list)
|
|
{
|
|
//another sprite is using the same vertex buffer, cannot dispose it yet
|
|
if (otherSprite.vertexBuffer == vertexBuffer) return;
|
|
}
|
|
|
|
vertexBuffer?.Dispose();
|
|
vertexBuffer = null;
|
|
flippedVertexBuffer?.Dispose();
|
|
flippedVertexBuffer = null;
|
|
indexBuffer?.Dispose();
|
|
indexBuffer = null;
|
|
}
|
|
|
|
#region Editing
|
|
|
|
public GUIComponent CreateEditor(GUIComponent parent, List<SpriteDeformation> deformations, string parentDebugName)
|
|
{
|
|
var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform))
|
|
{
|
|
AbsoluteSpacing = 5,
|
|
CanBeFocused = true
|
|
};
|
|
|
|
new GUITextBlock(new RectTransform(new Point(container.Rect.Width, (int)(60 * GUI.Scale)), container.RectTransform) { IsFixedSize = true },
|
|
"Sprite Deformations", textAlignment: Alignment.BottomCenter, font: GUIStyle.LargeFont);
|
|
|
|
var resolutionField = GUI.CreatePointField(new Point(subDivX + 1, subDivY + 1), (int)(30 * GUI.Scale), "Resolution", container.RectTransform,
|
|
"How many vertices the deformable sprite has on the x and y axes. Larger values make the deformations look smoother, but are more performance intensive.");
|
|
resolutionField.RectTransform.IsFixedSize = true;
|
|
GUINumberInput xField = null, yField = null;
|
|
|
|
foreach (GUIComponent child in resolutionField.GetAllChildren())
|
|
{
|
|
if (yField == null)
|
|
{
|
|
yField = child as GUINumberInput;
|
|
}
|
|
else
|
|
{
|
|
xField = child as GUINumberInput;
|
|
if (xField != null) break;
|
|
}
|
|
}
|
|
xField.MinValueInt = 2;
|
|
xField.MaxValueInt = SpriteDeformationParams.ShaderMaxResolution.X - 1;
|
|
xField.OnValueChanged = (numberInput) => { ChangeResolution(); };
|
|
yField.MinValueInt = 2;
|
|
yField.MaxValueInt = SpriteDeformationParams.ShaderMaxResolution.Y - 1;
|
|
yField.OnValueChanged = (numberInput) => { ChangeResolution(); };
|
|
|
|
void ChangeResolution()
|
|
{
|
|
subDivX = xField.IntValue - 1;
|
|
subDivY = yField.IntValue - 1;
|
|
|
|
foreach (SpriteDeformation deformation in deformations)
|
|
{
|
|
deformation.SetResolution(new Point(xField.IntValue, yField.IntValue));
|
|
}
|
|
SetupVertexBuffers();
|
|
SetupIndexBuffer();
|
|
}
|
|
|
|
foreach (SpriteDeformation deformation in deformations)
|
|
{
|
|
var deformEditor = new SerializableEntityEditor(container.RectTransform, deformation.Params,
|
|
inGame: false, showName: true, titleFont: GUIStyle.SubHeadingFont);
|
|
deformEditor.RectTransform.MinSize = new Point(deformEditor.Rect.Width, deformEditor.Rect.Height);
|
|
}
|
|
|
|
var deformationDD = new GUIDropDown(new RectTransform(new Point(container.Rect.Width, 30), container.RectTransform), "Add new sprite deformation");
|
|
deformationDD.OnSelected = (selected, userdata) =>
|
|
{
|
|
deformations.Add(SpriteDeformation.Load((string)userdata, parentDebugName));
|
|
deformationDD.Text = "Add new sprite deformation";
|
|
return false;
|
|
};
|
|
|
|
foreach (string deformationType in SpriteDeformation.DeformationTypes)
|
|
{
|
|
deformationDD.AddItem(deformationType, deformationType);
|
|
}
|
|
|
|
container.RectTransform.Resize(new Point(
|
|
container.Rect.Width, container.Children.Sum(c => c.Rect.Height + container.AbsoluteSpacing)), false);
|
|
|
|
container.RectTransform.MinSize = new Point(0, container.Rect.Height);
|
|
container.RectTransform.MaxSize = new Point(int.MaxValue, container.Rect.Height);
|
|
container.RectTransform.IsFixedSize = true;
|
|
container.Recalculate();
|
|
|
|
return container;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|