Newer
Older
DungeonShooting / DungeonShooting_Godot / src / framework / map / image / ImageCanvas.cs
@小李xl 小李xl on 18 Jun 2023 9 KB 备份
  1.  
  2. using System;
  3. using System.Collections.Generic;
  4. using Godot;
  5.  
  6. public partial class ImageCanvas : Sprite2D, IDestroy
  7. {
  8. /// <summary>
  9. /// 画布大小
  10. /// </summary>
  11. public int Width { get; }
  12. /// <summary>
  13. /// 画布高度
  14. /// </summary>
  15. public int Height { get; }
  16.  
  17. public bool IsDestroyed { get; private set; }
  18.  
  19. private Image _canvas;
  20. private ImageTexture _texture;
  21.  
  22. public ImageCanvas(int width, int height)
  23. {
  24. Width = width;
  25. Height = height;
  26.  
  27. _canvas = Image.Create(width, height, false, Image.Format.Rgba8);
  28. _texture = ImageTexture.CreateFromImage(_canvas);
  29.  
  30. _canvas.Fill(Colors.Gray);
  31. var w = _canvas.GetWidth();
  32. var h = _canvas.GetHeight();
  33. for (int i = 0; i < w; i++)
  34. {
  35. _canvas.SetPixel(i, h / 2, Colors.Green);
  36. }
  37. for (int j = 0; j < h; j++)
  38. {
  39. _canvas.SetPixel(w / 2, j, Colors.Green);
  40. }
  41. }
  42.  
  43. public override void _Ready()
  44. {
  45. Centered = false;
  46. Texture = _texture;
  47. }
  48.  
  49. /// <summary>
  50. /// 添加到预渲染队列中
  51. /// </summary>
  52. /// <param name="texture">需要渲染的纹理</param>
  53. /// <param name="x">离画布左上角x坐标</param>
  54. /// <param name="y">离画布左上角y坐标</param>
  55. /// <param name="angle">旋转角度, 角度制</param>
  56. /// <param name="centerX">旋转中心点x</param>
  57. /// <param name="centerY">旋转中心点y</param>
  58. /// <param name="flipY">是否翻转y轴</param>
  59. public void DrawImageInCanvas(Texture2D texture, int x, int y, float angle, int centerX, int centerY, bool flipY)
  60. {
  61. var item = new EnqueueItem();
  62. item.Canvas = this;
  63. item.Image = texture.GetImage();
  64. item.X = x;
  65. item.Y = y;
  66. item.Angle = angle;
  67. item.CenterX = centerX;
  68. item.CenterY = centerY;
  69. item.FlipY = flipY;
  70.  
  71. _enqueueItems.Enqueue(item);
  72. }
  73.  
  74. public void Destroy()
  75. {
  76. if (IsDestroyed)
  77. {
  78. return;
  79. }
  80.  
  81. IsDestroyed = true;
  82. }
  83.  
  84. private void Redraw()
  85. {
  86. _texture.Update(_canvas);
  87. }
  88.  
  89. private void HandlerDrawImageInCanvas(EnqueueItem item)
  90. {
  91. var image = item.Image;
  92. var newAngle = Mathf.RoundToInt(Utils.ConvertAngle(item.Angle));
  93.  
  94. if (newAngle == 0) //原图, 直接画上去
  95. {
  96. if (item.FlipY)
  97. {
  98. image.FlipY();
  99. }
  100. if (image.GetFormat() != Image.Format.Rgba8)
  101. {
  102. image.Convert(Image.Format.Rgba8);
  103. }
  104. _canvas.BlendRect(image, new Rect2I(0, 0, image.GetWidth(), image.GetHeight()),
  105. new Vector2I(item.X - item.CenterX, item.Y - item.CenterY));
  106. }
  107. else //其他角度
  108. {
  109. if (newAngle <= 180)
  110. {
  111. if (item.FlipY)
  112. {
  113. image.FlipY();
  114. }
  115.  
  116. DrawRotateImage(image, item.X, item.Y, newAngle, item.CenterX, item.CenterY);
  117. }
  118. else
  119. {
  120. image.FlipX();
  121. if (!item.FlipY)
  122. {
  123. image.FlipY();
  124. }
  125.  
  126. DrawRotateImage(
  127. image, item.X, item.Y,
  128. newAngle - 180,
  129. image.GetWidth() - item.CenterX - 1, image.GetHeight() - item.CenterY - 1
  130. );
  131. }
  132. }
  133.  
  134. image.Dispose();
  135. item.Image = null;
  136. }
  137.  
  138. private void DrawRotateImage(Image origin, int x, int y, int angle, int centerX, int centerY)
  139. {
  140. var rotation = Mathf.DegToRad(angle);
  141. var width = origin.GetWidth();
  142. var height = origin.GetHeight();
  143.  
  144. var cosAngle = Mathf.Cos(rotation);
  145. var sinAngle = Mathf.Sin(rotation);
  146. if (cosAngle == 0)
  147. {
  148. cosAngle = -1e-6f;
  149. }
  150.  
  151. if (sinAngle == 0)
  152. {
  153. sinAngle = 1e-6f;
  154. }
  155.  
  156. var newWidth = Mathf.RoundToInt(width * Mathf.Abs(cosAngle) + height * sinAngle);
  157. var newHeight = Mathf.RoundToInt(width * sinAngle + height * Mathf.Abs(cosAngle));
  158.  
  159. var offsetX =
  160. Mathf.RoundToInt((centerX / sinAngle +
  161. (0.5f * newWidth * sinAngle - 0.5f * newHeight * cosAngle + 0.5f * height) /
  162. cosAngle -
  163. (-0.5f * newWidth * cosAngle - 0.5f * newHeight * sinAngle + 0.5f * width) /
  164. sinAngle - centerY / cosAngle) /
  165. (cosAngle / sinAngle + sinAngle / cosAngle));
  166.  
  167.  
  168. var offsetY =
  169. Mathf.RoundToInt((centerX / cosAngle -
  170. (-0.5f * newWidth * cosAngle - 0.5f * newHeight * sinAngle + 0.5f * width) /
  171. cosAngle -
  172. (0.5f * newWidth * sinAngle - 0.5f * newHeight * cosAngle + 0.5f * height) /
  173. sinAngle + centerY / sinAngle) /
  174. (sinAngle / cosAngle + cosAngle / sinAngle));
  175.  
  176. var num1 = -0.5f * newWidth * cosAngle - 0.5f * newHeight * sinAngle + 0.5f * width;
  177. var num2 = 0.5f * newWidth * sinAngle - 0.5f * newHeight * cosAngle + 0.5f * height;
  178.  
  179. var cw = _canvas.GetWidth();
  180. var ch = _canvas.GetHeight();
  181. var numX = Mathf.Min(num1, (newWidth - 1) * cosAngle + (newHeight - 1) * sinAngle + num1);
  182. var numY = Mathf.Min(num2, -(newWidth - 1) * sinAngle + (newHeight - 1) * cosAngle + num2);
  183.  
  184. for (var x2 = 0; x2 < newWidth; x2++)
  185. {
  186. for (var y2 = 0; y2 < newHeight; y2++)
  187. {
  188. //映射到原图的位置
  189. // var x1 = x2 * cosAngle + y2 * sinAngle + num1;
  190. // var y1 = -x2 * sinAngle + y2 * cosAngle + num2;
  191. var x1 = Mathf.FloorToInt(x2 * cosAngle + y2 * sinAngle + num1 - numX);
  192. var y1 = Mathf.FloorToInt(-x2 * sinAngle + y2 * cosAngle + num2 - numY);
  193.  
  194. if (x1 < 0 || x1 >= width || y1 < 0 || y1 >= height)
  195. {
  196. //在图片外
  197. continue;
  198. }
  199.  
  200.  
  201. var cx = x2 + x - offsetX;
  202. if (cx < 0 || cx >= cw)
  203. {
  204. continue;
  205. }
  206.  
  207. var cy = y2 + y - offsetY;
  208. if (cy < 0 || cy >= ch)
  209. {
  210. continue;
  211. }
  212.  
  213. //_canvas.SetPixel(cx, cy, origin.GetPixel(x1, y1));
  214. _canvas.SetPixel(cx, cy, _canvas.GetPixel(cx, cy).Blend(origin.GetPixel(
  215. //Mathf.RoundToInt(x1), Mathf.RoundToInt(y1)
  216. x1, y1
  217. )));
  218. }
  219. }
  220. }
  221.  
  222. //--------------------------------------------------------------------------------------------------------------
  223. private class EnqueueItem
  224. {
  225. public ImageCanvas Canvas;
  226. public Image Image;
  227. public int X;
  228. public int Y;
  229. public float Angle;
  230. public int CenterX;
  231. public int CenterY;
  232. public bool FlipY;
  233. }
  234.  
  235. /// <summary>
  236. /// 同一帧下将队列里的image绘制到指定画布下最大消耗时间, 如果绘制的时间超过了这个值, 则队列中后面的image将会放到下一帧绘制
  237. /// </summary>
  238. public static float MaxHandlerTime { get; set; } = 2f;
  239. private static readonly Queue<EnqueueItem> _enqueueItems = new Queue<EnqueueItem>();
  240. private static readonly HashSet<ImageCanvas> _redrawCanvas = new HashSet<ImageCanvas>();
  241.  
  242. /// <summary>
  243. /// 根据重绘队列更新画布
  244. /// </summary>
  245. public static void UpdateImageCanvas(float delta)
  246. {
  247. if (_enqueueItems.Count > 0)
  248. {
  249. _redrawCanvas.Clear();
  250. var startTime = DateTime.Now;
  251.  
  252. var c = _enqueueItems.Count;
  253. //执行绘制操作
  254. //绘制的总时间不能超过MaxHandlerTime, 如果超过了, 那就下一帧再画其它的吧
  255. do
  256. {
  257. var item = _enqueueItems.Dequeue();
  258. if (!item.Canvas.IsDestroyed)
  259. {
  260. item.Canvas.HandlerDrawImageInCanvas(item);
  261. _redrawCanvas.Add(item.Canvas);
  262. }
  263. } while (_enqueueItems.Count > 0 && (DateTime.Now - startTime).TotalMilliseconds < MaxHandlerTime);
  264.  
  265. if (_enqueueItems.Count > 0)
  266. {
  267. GD.Print($"ImageCanvas: 还剩下{_enqueueItems.Count}个, 已经处理了{c - _enqueueItems.Count}个, 重绘画布{_redrawCanvas.Count}张");
  268. }
  269. else
  270. {
  271. GD.Print($"ImageCanvas: 全部画完, 已经处理了{c - _enqueueItems.Count}个, 重绘画布{_redrawCanvas.Count}张");
  272. }
  273.  
  274. //重绘画布
  275. foreach (var drawCanvas in _redrawCanvas)
  276. {
  277. drawCanvas.Redraw();
  278. }
  279.  
  280. _redrawCanvas.Clear();
  281. }
  282. }
  283. }