Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Sprite/DeformableSprite.cs
2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
4 using System;
5 using System.Collections.Generic;
6 using System.Linq;
7 using System.Xml.Linq;
8 
9 namespace Barotrauma
10 {
11  partial class DeformableSprite
12  {
13  private static List<DeformableSprite> list = new List<DeformableSprite>();
14 
15  private bool initialized = false;
16 
17  private int triangleCount;
18 
19  private VertexBuffer vertexBuffer, flippedVertexBuffer;
20  private IndexBuffer indexBuffer;
21 
22  private Vector2 uvTopLeft, uvBottomRight;
23  private Vector2 uvTopLeftFlipped, uvBottomRightFlipped;
24 
25  private Vector2[] deformAmount;
26  private int deformArrayWidth, deformArrayHeight;
27 
28  private int subDivX, subDivY;
29 
30  private static Effect effect;
31  public static Effect Effect
32  {
33  get { return effect; }
34  }
35 
36  public bool Invert { get; set; }
37 
38  private Point spritePos;
39  private Point spriteSize;
40 
41  partial void InitProjSpecific(XElement element, int? subdivisionsX, int? subdivisionsY, bool lazyLoad, bool invert)
42  {
43  if (effect == null)
44  {
45  effect = EffectLoader.Load("Effects/deformshader");
46  }
47 
48  Invert = invert;
49 
50  //use subdivisions configured in the xml if the arguments passed to the method are null
51  Vector2 subdivisionsInXml = element.GetAttributeVector2("subdivisions", Vector2.One);
52  subDivX = subdivisionsX ?? (int)subdivisionsInXml.X;
53  subDivY = subdivisionsY ?? (int)subdivisionsInXml.Y;
54 
55  if (subDivX <= 0 || subDivY <= 0)
56  {
57  throw new ArgumentException("Deformable sprites must have one or more subdivisions on each axis.");
58  }
59 
60  if (!lazyLoad)
61  {
62  Init();
63  }
64 
65  list.Add(this);
66  }
67 
68  public void EnsureLazyLoaded()
69  {
70  if (!initialized) { Init(); }
71  }
72 
73  private void Init()
74  {
75  if (initialized) { return; }
76  initialized = true;
77 
78  foreach (DeformableSprite existing in list)
79  {
80  if (!existing.initialized || existing == this) { continue; }
81  //share vertex and index buffers if there's already
82  //an existing sprite with the same texture and subdivisions
83  if (existing.Sprite.Texture == Sprite.Texture &&
84  existing.subDivX == subDivX &&
85  existing.subDivY == subDivY &&
86  existing.Sprite.SourceRect == Sprite.SourceRect)
87  {
88  vertexBuffer = existing.vertexBuffer;
89  flippedVertexBuffer = existing.flippedVertexBuffer;
90  indexBuffer = existing.indexBuffer;
91  triangleCount = existing.triangleCount;
92  uvTopLeft = existing.uvTopLeft;
93  uvBottomRight = existing.uvBottomRight;
94  uvTopLeftFlipped = existing.uvTopLeftFlipped;
95  uvBottomRightFlipped = existing.uvBottomRightFlipped;
96 
97  Deform(new Vector2[,]
98  {
99  { Vector2.Zero, Vector2.Zero },
100  { Vector2.Zero, Vector2.Zero }
101  });
102  return;
103  }
104  }
105 
106  if (Sprite.Texture != null)
107  {
108  SetupVertexBuffers();
109  SetupIndexBuffer();
110  }
111  }
112 
113  private void SetupVertexBuffers()
114  {
115  Vector2 textureSize = new Vector2(Sprite.Texture.Width, Sprite.Texture.Height);
116  var pos = Sprite.SourceRect.Location;
117  var size = Sprite.SourceRect.Size;
118 
119  uvTopLeft = Vector2.Divide(pos.ToVector2(), textureSize);
120  uvBottomRight = Vector2.Divide((pos + size).ToVector2(), textureSize);
121  uvTopLeftFlipped = Vector2.Divide(new Vector2(pos.X + size.X, pos.Y), textureSize);
122  uvBottomRightFlipped = Vector2.Divide(new Vector2(pos.X, pos.Y + size.Y), textureSize);
123  if (Invert)
124  {
125  var temp = uvBottomRightFlipped;
126  uvBottomRightFlipped = uvTopLeftFlipped;
127  uvTopLeftFlipped = temp;
128  }
129 
130  for (int i = 0; i < 2; i++)
131  {
132  bool flip = i == 1;
133 
134  var vertices = new VertexPositionColorTexture[(subDivX + 1) * (subDivY + 1)];
135  for (int x = 0; x <= subDivX; x++)
136  {
137  for (int y = 0; y <= subDivY; y++)
138  {
139  //{0,0} -> {1,1}
140  Vector2 relativePos = new Vector2(x / (float)subDivX, y / (float)subDivY);
141 
142  Vector2 uvCoord = flip ?
143  uvTopLeftFlipped + (uvBottomRightFlipped - uvTopLeftFlipped) * relativePos :
144  uvTopLeft + (uvBottomRight - uvTopLeft) * relativePos;
145 
146  vertices[x + y * (subDivX + 1)] = new VertexPositionColorTexture(
147  position: new Vector3(relativePos.X * Sprite.SourceRect.Width, relativePos.Y * Sprite.SourceRect.Height, 0.0f),
148  color: Color.White,
149  textureCoordinate: uvCoord);
150  }
151  }
152 
153  if (flip)
154  {
155  if (flippedVertexBuffer != null && flippedVertexBuffer.VertexCount != vertices.Length)
156  {
157  flippedVertexBuffer.Dispose();
158  flippedVertexBuffer = null;
159  }
160  if (flippedVertexBuffer == null)
161  {
162  flippedVertexBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.None);
163  }
164  flippedVertexBuffer.SetData(vertices);
165  }
166  else
167  {
168  if (vertexBuffer != null && vertexBuffer.VertexCount != vertices.Length)
169  {
170  vertexBuffer.Dispose();
171  vertexBuffer = null;
172  }
173  if (vertexBuffer == null)
174  {
175  vertexBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vertices.Length, BufferUsage.None);
176  }
177  vertexBuffer.SetData(vertices);
178  }
179  }
180 
181  spritePos = Sprite.SourceRect.Location;
182  spriteSize = Sprite.SourceRect.Size;
183  }
184 
185  private void SetupIndexBuffer()
186  {
187  triangleCount = subDivX * subDivY * 2;
188  var indices = new ushort[triangleCount * 3];
189  int offset = 0;
190  for (int i = 0; i < triangleCount / 2; i++)
191  {
192  indices[i * 6] = (ushort)(i + offset + 1);
193  indices[i * 6 + 1] = (ushort)(i + offset + (subDivX + 1) + 1);
194  indices[i * 6 + 2] = (ushort)(i + offset + (subDivX + 1));
195 
196  indices[i * 6 + 3] = (ushort)(i + offset);
197  indices[i * 6 + 4] = (ushort)(i + offset + 1);
198  indices[i * 6 + 5] = (ushort)(i + offset + (subDivX + 1));
199 
200  if ((i + 1) % subDivX == 0) offset++;
201  }
202 
203  indexBuffer?.Dispose();
204  indexBuffer = null;
205 
206  indexBuffer = new IndexBuffer(GameMain.Instance.GraphicsDevice, IndexElementSize.SixteenBits, indices.Length, BufferUsage.None);
207  indexBuffer.SetData(indices);
208 
209  Deform(new Vector2[,]
210  {
211  { Vector2.Zero, Vector2.Zero },
212  { Vector2.Zero, Vector2.Zero }
213  });
214  }
215 
221  public void Deform(Func<Vector2, Vector2> deformFunction)
222  {
223  if (!initialized) { Init(); }
224 
225  var deformAmount = new Vector2[subDivX + 1, subDivY + 1];
226  for (int x = 0; x <= subDivX; x++)
227  {
228  for (int y = 0; y <= subDivY; y++)
229  {
230  deformAmount[x, y] = deformFunction(new Vector2(x / (float)subDivX, y / (float)subDivY));
231  }
232  }
233  Deform(deformAmount);
234  }
235 
236  public void Deform(Vector2[,] deform)
237  {
238  if (!initialized) { Init(); }
239 
240  deformArrayWidth = deform.GetLength(0);
241  deformArrayHeight = deform.GetLength(1);
242  if (deformAmount == null || deformAmount.Length != deformArrayWidth * deformArrayHeight)
243  {
244  deformAmount = new Vector2[deformArrayWidth * deformArrayHeight];
245  }
246  for (int x = 0; x < deformArrayWidth; x++)
247  {
248  for (int y = 0; y < deformArrayHeight; y++)
249  {
250  deformAmount[x + y * deformArrayWidth] = deform[x, y];
251  }
252  }
253  }
254 
255  public void Reset()
256  {
257  Deform(new Vector2[,]
258  {
259  { Vector2.Zero, Vector2.Zero },
260  { Vector2.Zero, Vector2.Zero }
261  });
262  }
263 
264  public Matrix GetTransform(Vector3 pos, Vector2 origin, float rotate, Vector2 scale)
265  {
266  if (!initialized) { Init(); }
267 
268  return
269  Matrix.CreateTranslation(-origin.X, -origin.Y, 0) *
270  Matrix.CreateScale(scale.X, -scale.Y, 1.0f) *
271  Matrix.CreateRotationZ(-rotate) *
272  Matrix.CreateTranslation(pos);
273  }
274 
275  public void Draw(Camera cam, Vector3 pos, Vector2 origin, float rotate, Vector2 scale, Color color, bool mirror = false, bool invert = false)
276  {
277  if (Sprite.Texture == null) { return; }
278  if (!initialized) { Init(); }
279 
280  // If the source rect is modified, we should recalculate the vertex buffer.
281  if (Sprite.SourceRect.Location != spritePos || Sprite.SourceRect.Size != spriteSize)
282  {
283  SetupVertexBuffers();
284  }
285 
286 #if (LINUX || OSX)
287  effect.Parameters["TextureSampler+xTexture"].SetValue(Sprite.Texture);
288 #else
289  effect.Parameters["xTexture"].SetValue(Sprite.Texture);
290 #endif
291 
292  Matrix matrix = GetTransform(pos, origin, rotate, scale);
293  effect.Parameters["xTransform"].SetValue(matrix * cam.ShaderTransform
294  * Matrix.CreateOrthographic(cam.Resolution.X, cam.Resolution.Y, -1, 1) * 0.5f);
295  effect.Parameters["tintColor"].SetValue(color.ToVector4());
296  effect.Parameters["deformArray"].SetValue(deformAmount);
297  effect.Parameters["deformArrayWidth"].SetValue(deformArrayWidth);
298  effect.Parameters["deformArrayHeight"].SetValue(deformArrayHeight);
299  if (invert)
300  {
301  mirror = !mirror;
302  }
303  effect.Parameters["uvTopLeft"].SetValue(mirror ? uvTopLeftFlipped : uvTopLeft);
304  effect.Parameters["uvBottomRight"].SetValue(mirror ? uvBottomRightFlipped : uvBottomRight);
305  effect.GraphicsDevice.SetVertexBuffer(mirror ? flippedVertexBuffer : vertexBuffer);
306  effect.GraphicsDevice.Indices = indexBuffer;
307  effect.CurrentTechnique.Passes[0].Apply();
308  effect.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, triangleCount);
309  }
310 
311  public void Remove()
312  {
313  Sprite?.Remove();
314  Sprite = null;
315 
316  list.Remove(this);
317 
318  foreach (DeformableSprite otherSprite in list)
319  {
320  //another sprite is using the same vertex buffer, cannot dispose it yet
321  if (otherSprite.vertexBuffer == vertexBuffer) return;
322  }
323 
324  vertexBuffer?.Dispose();
325  vertexBuffer = null;
326  flippedVertexBuffer?.Dispose();
327  flippedVertexBuffer = null;
328  indexBuffer?.Dispose();
329  indexBuffer = null;
330  }
331 
332  #region Editing
333 
334  public GUIComponent CreateEditor(GUIComponent parent, List<SpriteDeformation> deformations, string parentDebugName)
335  {
336  var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform))
337  {
338  AbsoluteSpacing = 5,
339  CanBeFocused = true
340  };
341 
342  new GUITextBlock(new RectTransform(new Point(container.Rect.Width, (int)(60 * GUI.Scale)), container.RectTransform) { IsFixedSize = true },
343  "Sprite Deformations", textAlignment: Alignment.BottomCenter, font: GUIStyle.LargeFont);
344 
345  var resolutionField = GUI.CreatePointField(new Point(subDivX + 1, subDivY + 1), (int)(30 * GUI.Scale), "Resolution", container.RectTransform,
346  "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.");
347  resolutionField.RectTransform.IsFixedSize = true;
348  GUINumberInput xField = null, yField = null;
349 
350  foreach (GUIComponent child in resolutionField.GetAllChildren())
351  {
352  if (yField == null)
353  {
354  yField = child as GUINumberInput;
355  }
356  else
357  {
358  xField = child as GUINumberInput;
359  if (xField != null) break;
360  }
361  }
362  xField.MinValueInt = 2;
363  xField.MaxValueInt = SpriteDeformationParams.ShaderMaxResolution.X - 1;
364  xField.OnValueChanged = (numberInput) => { ChangeResolution(); };
365  yField.MinValueInt = 2;
366  yField.MaxValueInt = SpriteDeformationParams.ShaderMaxResolution.Y - 1;
367  yField.OnValueChanged = (numberInput) => { ChangeResolution(); };
368 
369  void ChangeResolution()
370  {
371  subDivX = xField.IntValue - 1;
372  subDivY = yField.IntValue - 1;
373 
374  foreach (SpriteDeformation deformation in deformations)
375  {
376  deformation.SetResolution(new Point(xField.IntValue, yField.IntValue));
377  }
378  SetupVertexBuffers();
379  SetupIndexBuffer();
380  }
381 
382  foreach (SpriteDeformation deformation in deformations)
383  {
384  var deformEditor = new SerializableEntityEditor(container.RectTransform, deformation.Params,
385  inGame: false, showName: true, titleFont: GUIStyle.SubHeadingFont);
386  deformEditor.RectTransform.MinSize = new Point(deformEditor.Rect.Width, deformEditor.Rect.Height);
387  }
388 
389  var deformationDD = new GUIDropDown(new RectTransform(new Point(container.Rect.Width, 30), container.RectTransform), "Add new sprite deformation");
390  deformationDD.OnSelected = (selected, userdata) =>
391  {
392  deformations.Add(SpriteDeformation.Load((string)userdata, parentDebugName));
393  deformationDD.Text = "Add new sprite deformation";
394  return false;
395  };
396 
397  foreach (string deformationType in SpriteDeformation.DeformationTypes)
398  {
399  deformationDD.AddItem(deformationType, deformationType);
400  }
401 
402  container.RectTransform.Resize(new Point(
403  container.Rect.Width, container.Children.Sum(c => c.Rect.Height + container.AbsoluteSpacing)), false);
404 
405  container.RectTransform.MinSize = new Point(0, container.Rect.Height);
406  container.RectTransform.MaxSize = new Point(int.MaxValue, container.Rect.Height);
407  container.RectTransform.IsFixedSize = true;
408  container.Recalculate();
409 
410  return container;
411  }
412 
413  #endregion
414  }
415 }
Point Resolution
Definition: Camera.cs:120
Matrix ShaderTransform
Definition: Camera.cs:141
GUIComponent CreateEditor(GUIComponent parent, List< SpriteDeformation > deformations, string parentDebugName)
Matrix GetTransform(Vector3 pos, Vector2 origin, float rotate, Vector2 scale)
DeformableSprite(ContentXElement element, int? subdivisionsX=null, int? subdivisionsY=null, string filePath="", bool lazyLoad=false, bool invert=false, float sourceRectScale=1)
void Deform(Func< Vector2, Vector2 > deformFunction)
Deform the vertices of the sprite using an arbitrary function. The in-parameter of the function is th...
void Draw(Camera cam, Vector3 pos, Vector2 origin, float rotate, Vector2 scale, Color color, bool mirror=false, bool invert=false)
IEnumerable< GUIComponent > GetAllChildren()
Returns all child elements in the hierarchy.
Definition: GUIComponent.cs:49
RectTransform RectTransform
static SpriteDeformation Load(string deformationType, string parentDebugName)
static readonly Point ShaderMaxResolution
Defined in the shader.