Client LuaCsForBarotrauma
CircuitBoxWireRenderer.cs
1 #nullable enable
2 
3 using System;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using Microsoft.Xna.Framework;
7 using Microsoft.Xna.Framework.Graphics;
8 
9 namespace Barotrauma
10 {
11  internal class CircuitBoxWireRenderer
12  {
13  private const int VertsPerQuad = 4, // how many points per quad
14  QuadsPerLine = 10, // how many quads per line
15  VertsPerLine = QuadsPerLine * VertsPerQuad, // how many points we need to draw all the quads for a single line
16  TotalVertsPerWire = VertsPerLine * 2; // we are drawing 2 lines
17 
18  private readonly Texture2D texture;
19 
20  private VertexPositionColorTexture[] verts = new VertexPositionColorTexture[TotalVertsPerWire];
21  private readonly Vector2[][] colliders = new Vector2[2][];
22  private SquareLine skeleton;
23 
24  private Vector2 lastStart, lastEnd;
25  private Color lastColor;
26  private readonly Option<CircuitBoxWire> wire;
27 
28  public CircuitBoxWireRenderer(Option<CircuitBoxWire> wire, Vector2 start, Vector2 end, Color color, Sprite? wireSprite)
29  {
30  this.wire = wire;
31  texture = wireSprite?.Texture ?? GUI.WhiteTexture;
32  Recompute(start, end, color);
33  }
34 
35  private void UpdateColor(Color color)
36  {
37  for (int i = 0; i < TotalVertsPerWire; i++)
38  {
39  verts[i].Color = color;
40  }
41 
42  lastColor = color;
43  }
44 
45  public void Recompute(Vector2 start, Vector2 end, Color color)
46  {
47  if (MathUtils.NearlyEqual(lastStart, start) && MathUtils.NearlyEqual(lastEnd, end))
48  {
49  if (lastColor == color) { return; }
50 
51  UpdateColor(color);
52  return;
53  }
54 
55  lastStart = start;
56  lastEnd = end;
57  lastColor = color;
58 
59  skeleton = ToolBox.GetSquareLineBetweenPoints(start, end, CircuitBoxSizes.WireKnobLength);
60  var points = skeleton.Points;
61 
62  Vector2 centerOfLine = (points[2] + points[3]) / 2f;
63 
64  ImmutableArray<Vector2> points1 = GetLinePoints(points[1], points[2], centerOfLine),
65  points2 = GetLinePoints(centerOfLine, points[3], points[4]);
66 
67  colliders[0] = ConstructQuads(ref verts, 0, points1, color);
68  colliders[1] = ConstructQuads(ref verts, VertsPerLine, points2, color);
69 
70  static ImmutableArray<Vector2> GetLinePoints(Vector2 start, Vector2 control, Vector2 end)
71  {
72  var points = ImmutableArray.CreateBuilder<Vector2>(QuadsPerLine);
73  for (int i = 0; i < QuadsPerLine; i++)
74  {
75  float t = (float)i / (QuadsPerLine - 1);
76  Vector2 pos = MathUtils.Bezier(start, control, end, t);
77  points.Add(pos);
78  }
79 
80  return points.ToImmutable();
81  }
82 
83  static Vector2[] ConstructQuads(ref VertexPositionColorTexture[] verts, int startOffset, IReadOnlyList<Vector2> points, Color color)
84  {
85  // ok I don't know why this needs to be one quad less, maybe we are drawing with only 9 quads lol
86  var collider = new Vector2[VertsPerLine - VertsPerQuad];
87 
88  int leftIndex = collider.Length - 1,
89  rightIndex = 0;
90 
91  // we need to calculate half of the width since the way we expand the quads from origin, otherwise the line will be twice as wide
92  const float halfWidth = CircuitBoxSizes.WireWidth / 2f;
93 
94  // draw the line using quads
95  for (int i = 0; i < points.Count - 1; i++)
96  {
97  bool isFirst = i == 0 && startOffset == 0,
98  isLast = i == points.Count - 2 && startOffset > 0;
99 
100  Vector2 start = points[i],
101  end = points[i + 1];
102 
103  Vector2 dir = Vector2.Normalize(end - start);
104  Vector2 length = new Vector2(dir.Y, -dir.X) * halfWidth;
105 
106  int vertIndex = startOffset + i * 4;
107 
108  Vector2 topRight = end + length;
109  Vector2 topLeft = end - length;
110 
111  Vector2 bottomRight;
112  Vector2 bottomLeft;
113 
114  // get previous points if any
115  int prevIndex = vertIndex - 4;
116 
117  if ((prevIndex - startOffset) >= 0)
118  {
119  // connect the previous "upper" corners into the current "lower" corners to stitch the line together
120  Vector3 prevTopRight = verts[TopRight(prevIndex)].Position,
121  prevTopLeft = verts[TopLeft(prevIndex)].Position;
122 
123  bottomRight = ToVector2(prevTopRight);
124  bottomLeft = ToVector2(prevTopLeft);
125  }
126  else
127  {
128  bottomRight = start + length;
129  bottomLeft = start - length;
130  }
131 
132  if (isFirst)
133  {
134  if (MathF.Abs(dir.Y) > MathF.Abs(dir.X))
135  {
136  float offset = dir.Y < 0 ? halfWidth : -halfWidth;
137  // if the line is more vertical than horizontal, we want to move the bottom corners to the left
138  bottomRight.Y = start.Y - offset;
139  bottomLeft.Y = start.Y - offset;
140  }
141  else
142  {
143  // otherwise we want to move the bottom corners to the top
144  bottomRight.X = start.X;
145  bottomLeft.X = start.X;
146  }
147  }
148  else if (isLast)
149  {
150  if (MathF.Abs(dir.Y) > MathF.Abs(dir.X))
151  {
152  float offset = dir.Y < 0 ? halfWidth : -halfWidth;
153  // if the line is more vertical than horizontal, we want to move the bottom corners to the left
154  topRight.Y = end.Y + offset;
155  topLeft.Y = end.Y + offset;
156  }
157  else
158  {
159  // otherwise we want to move the bottom corners to the top
160  topRight.X = end.X;
161  topLeft.X = end.X;
162  }
163  }
164 
165  collider[rightIndex++] = bottomLeft;
166  collider[rightIndex++] = topLeft;
167 
168  collider[leftIndex--] = bottomRight;
169  collider[leftIndex--] = topRight;
170 
171  // adjust this if we want sprites to support sourceRects
172  Vector2 uvTopRight = new Vector2(0, 1),
173  uvTopLeft = new Vector2(0, 0),
174  uvBottomRight = new Vector2(1, 1),
175  uvBottomLeft = new Vector2(1, 0);
176 
177  SetPos(ref verts, TopRight(vertIndex), topRight, color, uvTopRight);
178  SetPos(ref verts, TopLeft(vertIndex), topLeft, color, uvTopLeft);
179  SetPos(ref verts, BottomRight(vertIndex), bottomRight, color, uvBottomRight);
180  SetPos(ref verts, BottomLeft(vertIndex), bottomLeft, color, uvBottomLeft);
181 
182  static void SetPos(ref VertexPositionColorTexture[] verts, int index, Vector2 pos, Color color, Vector2 uv)
183  {
184  verts[index].Position = ToVector3(pos);
185  verts[index].Color = color;
186  verts[index].TextureCoordinate = uv;
187  static Vector3 ToVector3(Vector2 v) => new Vector3(v.X, v.Y, 0f);
188  }
189 
190  static int TopRight(int vertIndex) => vertIndex;
191  static int TopLeft(int vertIndex) => vertIndex + 1;
192  static int BottomRight(int vertIndex) => vertIndex + 2;
193  static int BottomLeft(int vertIndex) => vertIndex + 3;
194 
195  static Vector2 ToVector2(Vector3 v) => new Vector2(v.X, v.Y);
196  }
197 
198  return collider;
199  }
200  }
201 
202  public bool Contains(Vector2 pos)
203  {
204  pos.Y = -pos.Y;
205  foreach (Vector2[] collider in colliders)
206  {
207  if (ToolBox.PointIntersectsWithPolygon(pos, collider, checkBoundingBox: false)) { return true; }
208  }
209 
210  return false;
211  }
212 
213  public void Draw(SpriteBatch spriteBatch, Color selectionColor)
214  {
215  if (GameMain.DebugDraw)
216  {
217  for (int i = 0; i < skeleton.Points.Length; i++)
218  {
219  Vector2 point = skeleton.Points[i];
220  spriteBatch.DrawPoint(point, Color.White, 25f);
221  GUI.DrawString(spriteBatch, point - new Vector2(5f, 17f), i.ToString(), Color.Black, font: GUIStyle.LargeFont);
222  }
223 
224  spriteBatch.DrawLine(skeleton.Points[0], skeleton.Points[1], GUIStyle.Green, thickness: 2f);
225  spriteBatch.DrawLine(skeleton.Points[1], skeleton.Points[2], GUIStyle.Green, thickness: 2f);
226  spriteBatch.DrawLine(skeleton.Points[2], skeleton.Points[3], GUIStyle.Green, thickness: 2f);
227  spriteBatch.DrawLine(skeleton.Points[3], skeleton.Points[4], GUIStyle.Green, thickness: 2f);
228  spriteBatch.DrawLine(skeleton.Points[4], skeleton.Points[5], GUIStyle.Green, thickness: 2f);
229  }
230 
231  bool isSelected = wire.TryUnwrap(out var w) && w.IsSelected;
232 
233  if (isSelected)
234  {
235  foreach (var colliderPolys in colliders)
236  {
237  spriteBatch.DrawPolygon(Vector2.Zero, colliderPolys, selectionColor, 5f);
238  }
239  }
240 
241  spriteBatch.Draw(texture, verts, 0f);
242 
243  if (skeleton.Type is SquareLine.LineType.SixPointBackwardsLine)
244  {
245  // we need to expand the start and end points to make the line look like it's connected to the "smooth" part of the line
246  Vector2 expandedEnd = skeleton.Points[1],
247  expandedStart = skeleton.Points[4];
248 
249  expandedEnd.X += CircuitBoxSizes.WireWidth / 2f;
250  expandedStart.X -= CircuitBoxSizes.WireWidth / 2f;
251 
252  spriteBatch.DrawLineWithTexture(texture, skeleton.Points[0], expandedEnd, lastColor, thickness: CircuitBoxSizes.WireWidth);
253  spriteBatch.DrawLineWithTexture(texture, expandedStart, skeleton.Points[5], lastColor, thickness: CircuitBoxSizes.WireWidth);
254 
255  const float rectSize = CircuitBoxSizes.WireWidth * 1.5f;
256  RectangleF startKnob = new RectangleF(skeleton.Points[1] - new Vector2(rectSize / 2f), new Vector2(rectSize)),
257  endKnob = new RectangleF(skeleton.Points[4] - new Vector2(rectSize / 2f), new Vector2(rectSize));
258 
259  GUI.DrawFilledRectangle(spriteBatch, startKnob, lastColor);
260  GUI.DrawFilledRectangle(spriteBatch, endKnob, lastColor);
261  }
262 
263  if (!GameMain.DebugDraw) { return; }
264 
265  foreach (var colliderPolys in colliders)
266  {
267  spriteBatch.DrawPolygonInner(Vector2.Zero, colliderPolys, Color.Lime, 1f);
268  }
269  }
270  }
271 }