问题背景:

我这边最近需要实现动态去画多边形(不规则的),类似于高德地图中那种面积测量工具一般。

方案:

”割耳“算法实现三角化平面。

具体实现:

割耳算法类:

  1. /*
  2. *******************************************************
  3. *
  4. * 文件名称:EarCut
  5. * 文件描述:三角化相关算法集合
  6. *
  7. * 版本:V1.0.0
  8. * 支持带洞的多边形,需要保证多边形为顺时针,而洞为逆时针顺序
  9. * *****************************************************
  10. */
  11.  
  12. using System;
  13. using System.Collections;
  14. using System.Collections.Generic;
  15. using UnityEngine;
  16.  
  17. namespace Tx3d.Framework
  18. {
  19. public class EarCut
  20. {
  21. #region Sub Class
  22.  
  23. /// <summary>
  24. /// “割耳”点
  25. /// </summary>
  26. private class Node : IComparable
  27. {
  28. #region Members & Properties
  29.  
  30. /// <summary>
  31. /// vertice index in coordinates array
  32. /// </summary>
  33. public int i = -1;
  34.  
  35. /// <summary>
  36. /// vertex coordinates
  37. /// </summary>
  38. public float x = 0.0f;
  39. public float z = 0.0f;
  40.  
  41. /// <summary>
  42. /// previous and next vertice nodes in a polygon ring
  43. /// </summary>
  44. public Node prev = null;
  45. public Node next = null;
  46.  
  47. /// <summary>
  48. /// z-order curve value
  49. /// </summary>
  50. public int zOrder = -1;
  51.  
  52. /// <summary>
  53. /// previous and next nodes in z-order
  54. /// </summary>
  55. public Node prevZ = null;
  56. public Node nextZ = null;
  57.  
  58. /// <summary>
  59. /// indicates whether this is a steiner point
  60. /// </summary>
  61. public bool steiner = false;
  62.  
  63. #endregion
  64.  
  65. #region IComparable Implemention
  66.  
  67. public int CompareTo(object obj)
  68. {
  69. try
  70. {
  71. Node node = obj as Node;
  72.  
  73. if (this.x > node.x)
  74. {
  75. return 1;
  76. }
  77. else
  78. {
  79. return 0;
  80. }
  81. }
  82. catch (Exception ex)
  83. {
  84. throw new Exception(ex.Message);
  85. }
  86. }
  87.  
  88. #endregion
  89. }
  90.  
  91. #endregion
  92.  
  93. #region Members & Properties
  94.  
  95. private static float EPSINON = 0.1f;
  96.  
  97. #endregion
  98.  
  99. #region Public Methods
  100.  
  101. /// <summary>
  102. /// “割耳”
  103. /// </summary>
  104. public static List<int> CutEar(List<Vector3> data, List<int> holeIndices)
  105. {
  106. var triangles = new List<int>();
  107.  
  108. bool hasHoles = holeIndices != null && holeIndices.Count > 0;
  109. int outerLength = hasHoles ? holeIndices[0] : data.Count;
  110. Node outerNode = LinkedList(data, 0, outerLength, true);
  111.  
  112. if (outerNode == null)
  113. {
  114. return triangles;
  115. }
  116.  
  117. if (hasHoles)
  118. {
  119. outerNode = EliminateHoles(data, holeIndices, outerNode);
  120. }
  121.  
  122. float minX = 0.0f;
  123. float minZ = 0.0f;
  124. float maxX = 0.0f;
  125. float maxZ = 0.0f;
  126. float size = 0.0f;
  127.  
  128. // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
  129. // if (data.Count > 80)
  130. if (data.Count > 100)
  131. {
  132. minX = maxX = data[0].x;
  133. minZ = maxZ = data[0].z;
  134.  
  135. for (int i = 1; i < outerLength; i++)
  136. {
  137. float x = data[i].x;
  138. float z = data[i].z;
  139. if (x < minX) minX = x;
  140. if (z < minZ) minZ = z;
  141. if (x > maxX) maxX = x;
  142. if (z > maxZ) maxZ = z;
  143. }
  144.  
  145. // minX, minY and size are later used to transform coords into integers for z-order calculation
  146. size = Mathf.Max(maxX - minX, maxZ - minZ);
  147. }
  148.  
  149. EarCutLinked(outerNode, triangles, minX, minZ, size, 0);
  150.  
  151. return triangles;
  152. }
  153.  
  154. #endregion
  155.  
  156. #region Private Methods
  157.  
  158. /// <summary>
  159. /// 使用多边形顶点按照指定顺序创建一个双向循环链表
  160. /// </summary>
  161. private static Node LinkedList(List<Vector3> data, int start, int end, bool clockwise)
  162. {
  163. Node last = null;
  164.  
  165. if (clockwise == (SignedArea(data, start, end) >= 0.0))
  166. {
  167. for (int i = start; i < end; i++)
  168. {
  169. last = InsertNode(i, data[i].x, data[i].z, last);
  170. }
  171. }
  172. else
  173. {
  174. for (int i = end - 1; i >= start; i--)
  175. {
  176. last = InsertNode(i, data[i].x, data[i].z, last);
  177. }
  178. }
  179.  
  180. if (last != null && Equals(last, last.next))
  181. {
  182. var next = last.next;
  183. RemoveNode(last);
  184. last = next;
  185. }
  186.  
  187. return last;
  188. }
  189.  
  190. /// <summary>
  191. /// “割耳”主循环
  192. /// </summary>
  193. /// <remarks>
  194. /// main ear slicing loop which triangulates a polygon (given as a linked list)
  195. /// </remarks>
  196. private static void EarCutLinked(Node ear, List<int> triangles, float minX, float minZ, float size, int pass)
  197. {
  198. if (ear == null) return;
  199.  
  200. // interlink polygon nodes in z-order
  201. if (pass == 0 && size > 0.0f)
  202. {
  203. IndexCurve(ear, minX, minZ, size);
  204. }
  205.  
  206. Node stop = ear;
  207. Node prev = null;
  208. Node next = null;
  209.  
  210. // iterate through ears, slicing them one by one
  211. while (ear.prev != ear.next)
  212. {
  213. prev = ear.prev;
  214. next = ear.next;
  215.  
  216. if (size > 0.0f ? IsEarHashed(ear, minX, minZ, size) : IsEar(ear))
  217. {
  218. // cut off the triangle
  219. triangles.Add(prev.i);
  220. triangles.Add(ear.i);
  221. triangles.Add(next.i);
  222.  
  223. RemoveNode(ear);
  224.  
  225. // skipping the next vertice leads to less sliver triangles
  226. ear = next.next;
  227. stop = next.next;
  228.  
  229. continue;
  230. }
  231.  
  232. ear = next;
  233.  
  234. // if we looped through the whole remaining polygon and can't find any more ears
  235. if (ear == stop)
  236. {
  237. // try filtering points and slicing again
  238. if (pass == 0)
  239. {
  240. EarCutLinked(FilterPoints(ear, null), triangles, minX, minZ, size, 1);
  241. }
  242. else if (pass == 1) // if this didn't work, try curing all small self-intersections locally
  243. {
  244. ear = CureLocalIntersections(ear, triangles);
  245. EarCutLinked(ear, triangles, minX, minZ, size, 2);
  246. }
  247. else if (pass == 2) // as a last resort, try splitting the remaining polygon into two
  248. {
  249. SplitEarCut(ear, triangles, minX, minZ, size);
  250. }
  251.  
  252. return;
  253. }
  254. }
  255. }
  256.  
  257. /// <summary>
  258. /// 尝试将多边形分割成两个,并分别进行三角化
  259. /// </summary>
  260. private static void SplitEarCut(Node start, List<int> triangles, float minX, float minZ, float size)
  261. {
  262. // look for a valid diagonal that divides the polygon into two
  263. var a = start;
  264.  
  265. do
  266. {
  267. var b = a.next.next;
  268.  
  269. while (b != a.prev)
  270. {
  271. if (a.i != b.i && IsValidDiagonal(a, b))
  272. {
  273. // split the polygon in two by the diagonal
  274. var c = SplitPolygon(a, b);
  275.  
  276. // filter colinear points around the cuts
  277. a = FilterPoints(a, a.next);
  278. c = FilterPoints(c, c.next);
  279.  
  280. // run earcut on each half
  281. EarCutLinked(a, triangles, minX, minZ, size, 0);
  282. EarCutLinked(c, triangles, minX, minZ, size, 0);
  283.  
  284. return;
  285. }
  286.  
  287. b = b.next;
  288. }
  289.  
  290. a = a.next;
  291. } while (a != start);
  292. }
  293.  
  294. /// <summary>
  295. /// link every hole into the outer loop, producing a single-ring polygon without holes
  296. /// </summary>
  297. private static Node EliminateHoles(List<Vector3> data, List<int> holeIndices, Node outerNode)
  298. {
  299. var queue = new List<Node>();
  300.  
  301. for (int i = 0, len = holeIndices.Count; i < len; i++)
  302. {
  303. var start = holeIndices[i];
  304. var end = i < len - 1 ? holeIndices[i + 1] : data.Count;
  305.  
  306. var list = LinkedList(data, start, end, false);
  307.  
  308. if (list == list.next)
  309. {
  310. list.steiner = true;
  311. }
  312.  
  313. queue.Add(GetLeftmost(list));
  314. }
  315.  
  316. // Sort
  317. queue.Sort();
  318.  
  319. // process holes from left to right
  320. for (int i = 0; i < queue.Count; i++)
  321. {
  322. var node = EliminateHole(queue[i], outerNode);
  323. if (node != null)
  324. {
  325. outerNode = FilterPoints(node, node.next);
  326. }
  327. }
  328.  
  329. return outerNode;
  330. }
  331.  
  332. /// <summary>
  333. /// find a bridge between vertices that connects hole with an outer ring and and link it
  334. /// </summary>
  335. private static Node EliminateHole(Node hole, Node outerNode)
  336. {
  337. outerNode = FindHoleBridge(hole, outerNode);
  338. if (outerNode != null)
  339. {
  340. var b = SplitPolygon(outerNode, hole);
  341. return FilterPoints(b, b.next);
  342. }
  343.  
  344. return null;
  345. }
  346.  
  347. /// <summary>
  348. /// 遍历多边形所有结点,校正局部自相交情形
  349. /// </summary>
  350. private static Node CureLocalIntersections(Node start, List<int> triangles)
  351. {
  352. var p = start;
  353.  
  354. do
  355. {
  356. var a = p.prev;
  357. var b = p.next.next;
  358.  
  359. if (!Equals(a, b) &&
  360. Intersects(a, p, p.next, b) &&
  361. LocallyInside(a, b) &&
  362. LocallyInside(b, a))
  363. {
  364. triangles.Add(a.i);
  365. triangles.Add(p.i);
  366. triangles.Add(b.i);
  367.  
  368. var next = p.next;
  369.  
  370. // remove two nodes involved
  371. RemoveNode(p);
  372. RemoveNode(next);
  373.  
  374. p = start = b;
  375. }
  376.  
  377. p = p.next;
  378. } while (p != start);
  379.  
  380. return p;
  381. }
  382.  
  383. /// <summary>
  384. /// 插入一个结点
  385. /// </summary>
  386. private static Node InsertNode(int i, float x, float z, Node last)
  387. {
  388. var p = new Node
  389. {
  390. i = i,
  391. x = x,
  392. z = z
  393. };
  394.  
  395. if (last == null)
  396. {
  397. p.prev = p;
  398. p.next = p;
  399. }
  400. else
  401. {
  402. p.next = last.next;
  403. p.prev = last;
  404.  
  405. last.next.prev = p;
  406. last.next = p;
  407. }
  408.  
  409. return p;
  410. }
  411.  
  412. /// <summary>
  413. /// 移除一个结点
  414. /// </summary>
  415. private static void RemoveNode(Node p)
  416. {
  417. p.next.prev = p.prev;
  418. p.prev.next = p.next;
  419.  
  420. if (p.prevZ != null)
  421. {
  422. p.prevZ.nextZ = p.nextZ;
  423. }
  424.  
  425. if (p.nextZ != null)
  426. {
  427. p.nextZ.prevZ = p.prevZ;
  428. }
  429. }
  430.  
  431. /// <summary>
  432. /// 判断两个结点是否相等
  433. /// </summary>
  434. /// <returns>true相等,false不相等</returns>
  435. private static bool Equals(Node p1, Node p2)
  436. {
  437. if (p1 == null || p2 == null)
  438. {
  439. Debug.Log("null");
  440. }
  441.  
  442. return p1.x == p2.x && p1.z == p2.z;
  443. }
  444.  
  445. /// <summary>
  446. /// 判断是否是“耳朵”
  447. /// </summary>
  448. /// <param name="ear"></param>
  449. /// <returns></returns>
  450. private static bool IsEar(Node ear)
  451. {
  452. var a = ear.prev;
  453. var b = ear;
  454. var c = ear.next;
  455.  
  456. if (Area(a, b, c) >= 0.0f)
  457. {
  458. // reflex, can't be an ear
  459. return false;
  460. }
  461.  
  462. // now make sure we don't have other points inside the potential ear
  463. var p = ear.next.next;
  464. while (p != ear.prev)
  465. {
  466. if (PointInTriangle(a, b, c, p) &&
  467. (Area(p.prev, p, p.next) >= 0.0f))
  468. {
  469. return false;
  470. }
  471.  
  472. p = p.next;
  473. }
  474.  
  475. return true;
  476. }
  477.  
  478. /// <summary>
  479. /// 判断是否是“耳朵”散列?
  480. /// </summary>
  481. private static bool IsEarHashed(Node ear, float minX, float minZ, float size)
  482. {
  483. var a = ear.prev;
  484. var b = ear;
  485. var c = ear.next;
  486.  
  487. if (Area(a, b, c) >= 0.0f)
  488. {
  489. // reflex, can't be an ear
  490. return false;
  491. }
  492.  
  493. // triangle bbox; min & max are calculated like this for speed
  494. var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x);
  495. var minTZ = a.z < b.z ? (a.z < c.z ? a.z : c.z) : (b.z < c.z ? b.z : c.z);
  496. var maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x);
  497. var maxTZ = a.z > b.z ? (a.z > c.z ? a.z : c.z) : (b.z > c.z ? b.z : c.z);
  498.  
  499. // z-order range for the current triangle bbox;
  500. int minZOrder = ZOrder(minTX, minTZ, minX, minZ, size);
  501. int maxZOrder = ZOrder(maxTX, maxTZ, minX, minZ, size);
  502.  
  503. // first look for points inside the triangle in increasing z-order
  504. var p = ear.nextZ;
  505.  
  506. while (p != null && p.zOrder <= maxZOrder)
  507. {
  508. if (p != ear.prev && p != ear.next &&
  509. PointInTriangle(a, b, c, p) &&
  510. Area(p.prev, p, p.next) >= 0.0f)
  511. {
  512. return false;
  513. }
  514.  
  515. p = p.nextZ;
  516. }
  517.  
  518. // then look for points in decreasing z-order
  519. p = ear.prevZ;
  520. while (p != null && p.zOrder >= minZOrder)
  521. {
  522. if (p != ear.prev && p != ear.next &&
  523. PointInTriangle(a, b, c, p) &&
  524. Area(p.prev, p, p.next) >= 0.0f)
  525. {
  526. return false;
  527. }
  528.  
  529. p = p.prevZ;
  530. }
  531.  
  532. return true;
  533. }
  534.  
  535. /// <summary>
  536. /// 通过对角线分割多边形
  537. /// </summary>
  538. /// <remarks>
  539. /// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
  540. /// if one belongs to the outer ring and another to a hole, it merges it into a single ring
  541. /// </remarks>
  542. private static Node SplitPolygon(Node a, Node b)
  543. {
  544. var a2 = new Node
  545. {
  546. i = a.i,
  547. x = a.x,
  548. z = a.z
  549. };
  550.  
  551. var b2 = new Node
  552. {
  553. i = b.i,
  554. x = b.x,
  555. z = b.z
  556. };
  557.  
  558. var an = a.next;
  559. var bp = b.prev;
  560.  
  561. a.next = b;
  562. b.prev = a;
  563.  
  564. a2.next = an;
  565. an.prev = a2;
  566.  
  567. b2.next = a2;
  568. a2.prev = b2;
  569.  
  570. bp.next = b2;
  571. b2.prev = bp;
  572.  
  573. return b2;
  574. }
  575.  
  576. /// <summary>
  577. /// 对结点进行排序
  578. /// </summary>
  579. /// <remarks>
  580. /// Simon Tatham's linked list merge sort algorithm
  581. /// </remarks>
  582. private static Node SortLinked(Node list)
  583. {
  584. int numMerges = 0;
  585. int pSize = 0;
  586. int qSize = 0;
  587. int inSize = 1;
  588.  
  589. Node p = null;
  590. Node q = null;
  591. Node e = null;
  592. Node tail = null;
  593.  
  594. do
  595. {
  596. p = list;
  597. list = null;
  598. tail = null;
  599. numMerges = 0;
  600.  
  601. while (p != null)
  602. {
  603. numMerges++;
  604.  
  605. q = p;
  606. pSize = 0;
  607.  
  608. for (int i = 0; i < inSize; i++)
  609. {
  610. pSize++;
  611. q = q.nextZ;
  612.  
  613. if (q == null)
  614. break;
  615. }
  616.  
  617. qSize = inSize;
  618.  
  619. while (pSize > 0 || (qSize > 0 && q != null))
  620. {
  621. if (pSize == 0)
  622. {
  623. e = q;
  624. q = q.nextZ;
  625. qSize--;
  626. }
  627. else if (qSize == 0 || q == null)
  628. {
  629. e = p;
  630. p = p.nextZ;
  631. pSize--;
  632. }
  633. else if (p.zOrder <= q.zOrder)
  634. {
  635. e = p;
  636. p = p.nextZ;
  637. pSize--;
  638. }
  639. else
  640. {
  641. e = q;
  642. q = q.nextZ;
  643. qSize--;
  644. }
  645.  
  646. if (tail != null)
  647. {
  648. tail.nextZ = e;
  649. }
  650. else
  651. {
  652. list = e;
  653. }
  654.  
  655. e.prevZ = tail;
  656. tail = e;
  657. }
  658.  
  659. p = q;
  660. }
  661.  
  662. tail.nextZ = null;
  663. inSize *= 2;
  664. } while (numMerges > 1);
  665.  
  666. return list;
  667. }
  668.  
  669. /// <summary>
  670. /// 相邻多边形节点次序(interlink polygon nodes in z-order)
  671. /// </summary>
  672. private static void IndexCurve(Node start, float minX, float minZ, float size)
  673. {
  674. var p = start;
  675.  
  676. do
  677. {
  678. if (p.zOrder == -1)
  679. {
  680. p.zOrder = ZOrder(p.x, p.z, minX, minZ, size);
  681. }
  682.  
  683. p.prevZ = p.prev;
  684. p.nextZ = p.next;
  685. p = p.next;
  686. } while (p != start);
  687.  
  688. p.prevZ.nextZ = null;
  689. p.prevZ = null;
  690.  
  691. SortLinked(p);
  692. }
  693.  
  694. /// <summary>
  695. /// 判断两条线段是否相交
  696. /// </summary>
  697. /// <remarks>
  698. /// </remarks>
  699. private static bool Intersects(Node p1, Node q1, Node p2, Node q2)
  700. {
  701. if ((Equals(p1, q1) && Equals(p2, q2)) ||
  702. (Equals(p1, q2) && Equals(p2, q1)))
  703. {
  704. return true;
  705. }
  706.  
  707. return (Area(p1, q1, p2) > 0.0 != Area(p1, q1, q2) > 0.0) &&
  708. (Area(p2, q2, p1) > 0.0 != Area(p2, q2, q1) > 0.0);
  709. }
  710.  
  711. /// <summary>
  712. /// 检测多边形的对角线是否与多边形的边相交
  713. /// </summary>
  714. private static bool IntersectsPolygon(Node a, Node b)
  715. {
  716. var p = a;
  717.  
  718. do
  719. {
  720. if (p.i == a.i && p.next.i != a.i &&
  721. p.i != b.i && p.next.i != b.i &&
  722. Intersects(p, p.next, a, b))
  723. {
  724. return true;
  725. }
  726.  
  727. p = p.next;
  728. } while (p != a);
  729.  
  730. return false;
  731. }
  732.  
  733. /// <summary>
  734. /// 查找多边形最坐标结点
  735. /// </summary>
  736. private static Node GetLeftmost(Node start)
  737. {
  738. var p = start;
  739. var leftmost = start;
  740.  
  741. do
  742. {
  743. if (p.x < leftmost.x)
  744. {
  745. leftmost = p;
  746. }
  747.  
  748. p = p.next;
  749. } while (p != start);
  750.  
  751. return leftmost;
  752. }
  753.  
  754. /// <summary>
  755. /// 查找多边形内部洞与外边的连接点
  756. /// </summary>
  757. /// <remarks>David Eberly's algorithm</remarks>
  758. private static Node FindHoleBridge(Node hole, Node outerNode)
  759. {
  760. var p = outerNode;
  761.  
  762. var hx = hole.x;
  763. var hz = hole.z;
  764. var qx = float.NegativeInfinity;
  765.  
  766. Node m = null;
  767.  
  768. // find a segment intersected by a ray from the hole's leftmost point to the left;
  769. // segment's endpoint with lesser x will be potential connection point
  770. do
  771. {
  772. if ((hz <= p.z && hz >= p.next.z) ||
  773. (hz <= p.next.z && hz >= p.z))
  774. {
  775. var x = p.x + (hz - p.z) * (p.next.x - p.x) / (p.next.z - p.z);
  776. if (x <= hx && x > qx)
  777. {
  778. qx = x;
  779.  
  780. if (x == hx)
  781. {
  782. if (hz == p.z)
  783. {
  784. return p;
  785. }
  786.  
  787. if (hz == p.next.z)
  788. {
  789. return p.next;
  790. }
  791. }
  792.  
  793. m = p.x < p.next.x ? p : p.next;
  794. }
  795. }
  796.  
  797. } while (p != outerNode);
  798.  
  799. if (m == null)
  800. {
  801. return null;
  802. }
  803.  
  804. // hole touches outer segment; pick lower endpoint
  805. if (hx == qx)
  806. {
  807. return m.prev;
  808. }
  809.  
  810. // look for points inside the triangle of hole point, segment intersection and endpoint;
  811. // if there are no points found, we have a valid connection;
  812. // otherwise choose the point of the minimum angle with the ray as connection point
  813.  
  814. var stop = m;
  815.  
  816. var mx = m.x;
  817. var mz = m.z;
  818. var tanMin = float.PositiveInfinity;
  819.  
  820. p = m.next;
  821.  
  822. while (p != stop)
  823. {
  824. if (hx >= p.x && p.x >= mx &&
  825. PointInTriangle(hz < mz ? hx : qx, hz, mx, mz, hz < mz ? qx : hx, hz, p.x, p.z))
  826. {
  827. var tan = Mathf.Abs(hz - p.z) / (hx - p.x); // tangential
  828.  
  829. if ((tan < tanMin || (tan == tanMin && p.x > m.x)) &&
  830. LocallyInside(p, hole))
  831. {
  832. m = p;
  833. tanMin = tan;
  834. }
  835. }
  836.  
  837. p = p.next;
  838. }
  839.  
  840. return m;
  841. }
  842.  
  843. /// <summary>
  844. /// 检测多边形的对角线是否在多边形内部
  845. /// </summary>
  846. private static bool LocallyInside(Node a, Node b)
  847. {
  848. return Area(a.prev, a, a.next) != 0.0f ?
  849. Area(a, b, a.next) >= 0.0f && Area(a, a.prev, b) >= 0.0f :
  850. Area(a, b, a.prev) >= 0.0f && Area(a, a.next, b) < 0.0f;
  851. }
  852.  
  853. /// <summary>
  854. /// 检测多边形对角线中心点是否在多边形内部
  855. /// </summary>
  856. private static bool MiddleInside(Node a, Node b)
  857. {
  858. var p = a;
  859. var inside = false;
  860. var px = (a.x + b.x) * 0.5f;
  861. var pz = (a.z + b.z) * 0.5f;
  862.  
  863. do
  864. {
  865. if (((p.z > pz) != (p.next.z > pz)) &&
  866. (px < ((p.next.x - px) * (pz - p.z) / (p.next.z - p.z) + p.x)))
  867. {
  868. inside = !inside;
  869. }
  870.  
  871. p = p.next;
  872. } while (p != a);
  873.  
  874. return inside;
  875. }
  876.  
  877. /// <summary>
  878. /// 判断多边形中的两点是否构成有效对角线
  879. /// </summary>
  880. private static bool IsValidDiagonal(Node a, Node b)
  881. {
  882. return a.next.i != b.i &&
  883. a.prev.i != b.i &&
  884. !IntersectsPolygon(a, b) &&
  885. LocallyInside(a, b) &&
  886. LocallyInside(b, a) &&
  887. MiddleInside(a, b);
  888. }
  889.  
  890. /// <summary>
  891. /// 过滤掉共线或重复的结点
  892. /// </summary>
  893. private static Node FilterPoints(Node start, Node end)
  894. {
  895. if (start == null) return start;
  896.  
  897. if (end == null) end = start;
  898.  
  899. var p = start;
  900. var again = false;
  901.  
  902. do
  903. {
  904. again = false;
  905.  
  906. if (!p.steiner && (Equals(p, p.next) || Area(p.prev, p, p.next) == 0.0f))
  907. {
  908. var prev = p.prev;
  909. RemoveNode(p);
  910. p = end = prev;
  911. if (p == p.next)
  912. {
  913. return null;
  914. }
  915.  
  916. again = true;
  917. }
  918. else
  919. {
  920. p = p.next;
  921. }
  922. } while (again || p != end);
  923.  
  924. return end;
  925. }
  926.  
  927. /// <summary>
  928. /// 计算给定坐标点和外包大小的结点z-order(z-order of a point given coords and size of the data bounding box)
  929. /// </summary>
  930. private static int ZOrder(float x, float z, float minX, float minZ, float size)
  931. {
  932. // coords are transformed into non-negative 15-bit integer range
  933. int _x = (int)(32767 * (x - minX) / size);
  934. int _z = (int)(32767 * (z - minZ) / size);
  935.  
  936. _x = (_x | (_x << 8)) & 0x00FF00FF;
  937. _x = (_x | (_x << 4)) & 0x0F0F0F0F;
  938. _x = (_x | (_x << 2)) & 0x33333333;
  939. _x = (_x | (_x << 1)) & 0x55555555;
  940.  
  941. _z = (_z | (_z << 8)) & 0x00FF00FF;
  942. _z = (_z | (_z << 4)) & 0x0F0F0F0F;
  943. _z = (_z | (_z << 2)) & 0x33333333;
  944. _z = (_z | (_z << 1)) & 0x55555555;
  945.  
  946. return _x | (_z << 1);
  947. }
  948.  
  949. /// <summary>
  950. /// 判断一个点是否在三角形内
  951. /// </summary>
  952. /// <returns>true在,false不在</returns>
  953. private static bool PointInTriangle(Node a, Node b, Node c, Node d)
  954. {
  955. var SABC = Mathf.Abs(Area(a, b, c)) * 0.5f;
  956. var SADB = Mathf.Abs(Area(a, d, b)) * 0.5f;
  957. var SBDC = Mathf.Abs(Area(b, d, c)) * 0.5f;
  958. var SADC = Mathf.Abs(Area(a, d, c)) * 0.5f;
  959.  
  960. var S = SABC - (SADB + SBDC + SADC);
  961. if (S > -EPSINON && S < EPSINON)
  962. {
  963. return true;
  964. }
  965.  
  966. return false;
  967. }
  968.  
  969. /// <summary>
  970. /// 判断一个点是否在三角形内
  971. /// </summary>
  972. /// <returns>true在,false不在</returns>
  973. private static bool PointInTriangle(float x0, float y0,
  974. float x1, float y1,
  975. float x2, float y2,
  976. float x3, float y3)
  977. {
  978. var SABC = Mathf.Abs(Area(x0, y0, x1, y1, x2, y2)) * 0.5f;
  979. var SADB = Mathf.Abs(Area(x0, y0, x3, y3, x1, y1)) * 0.5f;
  980. var SBDC = Mathf.Abs(Area(x1, y1, x3, y3, x2, y2)) * 0.5f;
  981. var SADC = Mathf.Abs(Area(x0, y0, x3, y3, x2, y2)) * 0.5f;
  982.  
  983. var S = SABC - (SADB + SBDC + SADC);
  984. if (S > -EPSINON && S < EPSINON)
  985. {
  986. return true;
  987. }
  988.  
  989. return false;
  990. }
  991.  
  992. /// <summary>
  993. /// 计算三角形有向面积(三角形面积的2倍)
  994. /// </summary>
  995. /// <remarks>
  996. /// 结果大于0.0,p、q、r按逆时针排列
  997. /// 结果等于0.0,p、q、r在一条直线上
  998. /// 结果小于0.0,p、q、r按顺时针排列
  999. /// </remarks>
  1000. /// <returns>三角形有向面积</returns>
  1001. private static float Area(Node p, Node q, Node r)
  1002. {
  1003. return Area(p.x, p.z, q.x, q.z, r.x, r.z);
  1004. }
  1005.  
  1006. /// <summary>
  1007. /// 计算三角形有向面积(三角形面积2倍)
  1008. /// </summary>
  1009. /// <returns>三角形有向面积</returns>
  1010. private static float Area(float x0, float y0,
  1011. float x1, float y1,
  1012. float x2, float y2)
  1013. {
  1014. return x0 * y1 + x2 * y0 + x1 * y2 - x2 * y1 - x0 * y2 - x1 * y0;
  1015. }
  1016.  
  1017. /// <summary>
  1018. /// 计算多边形有向面积(多边形面积的2倍)
  1019. /// </summary>
  1020. /// <param name="data">顶点数据</param>
  1021. /// <param name="start">起始顶点索引</param>
  1022. /// <param name="end">结束顶点索引</param>
  1023. /// <remarks>
  1024. /// 结果大于等于0.0,多边形顶点按顺时针排序
  1025. /// 结果小于0.0,多边形顶点按逆时针排序
  1026. /// </remarks>
  1027. /// <returns>多边形有向面积</returns>
  1028. private static float SignedArea(List<Vector3> data, int start, int end)
  1029. {
  1030. var sum = 0.0f;
  1031.  
  1032. for (int i = start; i < end; i++)
  1033. {
  1034. var next = (i + 1) == end ? start : i + 1;
  1035. var dx = data[next].x - data[i].x;
  1036. var dz = data[next].z + data[i].z;
  1037. sum += (dx * dz);
  1038. }
  1039.  
  1040. return sum;
  1041. }
  1042.  
  1043. #endregion
  1044. }
  1045. }

封装三角形算法集合:

  1. /*
  2. *******************************************************
  3. *
  4. * 文件名称:TriangulateUtil
  5. * 文件描述:三角化相关算法集合
  6. * *****************************************************
  7. */
  8.  
  9. using System;
  10. using System.Collections;
  11. using System.Collections.Generic;
  12. using UnityEngine;
  13.  
  14. namespace Tx3d.Framework
  15. {
  16. /// <summary>
  17. /// 三角化相关算法集合
  18. /// </summary>
  19. public class TriangulateUtil
  20. {
  21. #region Public Methods
  22.  
  23. /// <summary>
  24. /// 多边形三角化
  25. /// </summary>
  26. /// <param name="polygon"></param>
  27. /// <returns>返回顺时针方向的三角形数据</returns>
  28. public static PolygonData GeneratePolygon(List<Vector3> polygon)
  29. {
  30. PolygonData polygonData = new PolygonData();
  31.  
  32. //保证是顺时针队列
  33. if (!IsClockwise(polygon))
  34. {
  35. //排序
  36. polygonData.Vertices.AddRange(Reverse(polygon));
  37. }
  38. else
  39. polygonData.Vertices.AddRange(polygon);
  40.  
  41. //不带洞的多边形
  42. polygonData.Indices = EarCut.CutEar(polygonData.Vertices, new List<int>());
  43. return polygonData;
  44. }
  45.  
  46. /// <summary>
  47. /// 带洞多边形三角化
  48. /// </summary>
  49. /// <param name="polygon">多边形</param>
  50. /// <param name="indices">孔洞数组</param>
  51. /// <returns>返回顺时针方向的三角形数据</returns>
  52. public static PolygonData GeneratePolygon(List<Vector3> polygon, List<Vector3>[] holes)
  53. {
  54. PolygonData polygonData = new PolygonData();
  55.  
  56. //保证是顺时针队列
  57. if (!IsClockwise(polygon))
  58. {
  59. //排序
  60. polygonData.Vertices.AddRange(Reverse(polygon));
  61. }
  62. else
  63. polygonData.Vertices.AddRange(polygon);
  64.  
  65. var holeIndices = new List<int>();
  66. int offset = polygon.Count;
  67.  
  68. //孔洞需要逆时针
  69. foreach (var hole in holes)
  70. {
  71. if (IsClockwise(hole))
  72. {
  73. //排序
  74. polygonData.Vertices.AddRange(Reverse(hole));
  75. }
  76. else
  77. polygonData.Vertices.AddRange(hole);
  78.  
  79. holeIndices.Add(offset);
  80. offset += hole.Count;
  81. }
  82.  
  83. //带洞的多边形
  84. polygonData.Indices = EarCut.CutEar(polygonData.Vertices, holeIndices);
  85. return polygonData;
  86. }
  87. /// <summary>
  88. /// 判断坐标序列是否是顺时针排列
  89. /// </summary>
  90. /// <param name="ring">坐标序列</param>
  91. /// <returns>true顺时针,false逆时针</returns>
  92. public static bool IsClockwise(List<Vector3> ring)
  93. {
  94. var val = 0.0;
  95.  
  96. for (int i = 0, il = ring.Count; i < il; i++)
  97. {
  98. var next = (i + 1) % il;
  99. var point = ring[i];
  100. var nextPoint = ring[next];
  101.  
  102. val += (nextPoint.x - point.x) * (nextPoint.z + point.z);
  103. }
  104.  
  105. return val > 0.0;
  106. }
  107.  
  108. /// <summary>
  109. /// 计算三角面法向量
  110. /// </summary>
  111. /// <param name="a">顶点a</param>
  112. /// <param name="b">顶点b</param>
  113. /// <param name="c">顶点c</param>
  114. /// <returns>单位化的法向量</returns>
  115. public static Vector3 CalculateFaceNormal(Vector3 a, Vector3 b, Vector3 c)
  116. {
  117. var side1 = b - a;
  118. var side2 = c - a;
  119. var n = Vector3.Cross(side1, side2);
  120.  
  121. return n.normalized;
  122. }
  123. /// <summary>
  124. /// 射线和三角面相交检测
  125. /// </summary>
  126. /// <param name="ray"></param>
  127. /// <param name="a"></param>
  128. /// <param name="b"></param>
  129. /// <param name="c"></param>
  130. /// <param name="distance"></param>
  131. /// <param name="positiveSide">是否检测正面</param>
  132. /// <param name="negativeSide">是否检测反面</param>
  133. /// <returns></returns>
  134. public static bool Raycast(Ray ray,Vector3 a, Vector3 b, Vector3 c,out float distance, bool positiveSide = true, bool negativeSide = true)
  135. {
  136. distance = 0;
  137.  
  138. Vector3 normal = CalculateFaceNormal(a, b, c);
  139. float t;
  140. {
  141. float denom = Vector3.Dot(normal, ray.direction);
  142.  
  143. // Check intersect side
  144. if (denom > +float.Epsilon)
  145. {
  146. if (!negativeSide)
  147. return false;
  148. }
  149. else if (denom < -float.Epsilon)
  150. {
  151. if (!positiveSide)
  152. return false;
  153. }
  154. else
  155. {
  156. // Parallel or triangle area is close to zero when
  157. // the plane normal not normalised.
  158. return false;
  159. }
  160.  
  161. t = Vector3.Dot(normal, a - ray.origin) / denom;
  162.  
  163. if (t < 0)
  164. {
  165. // Intersection is behind origin
  166. return false;
  167. }
  168. }
  169.  
  170. //
  171. // Calculate the largest area projection plane in X, Y or Z.
  172. //
  173. int i0, i1;
  174. {
  175. float n0 = Mathf.Abs(normal[0]);
  176. float n1 = Mathf.Abs(normal[1]);
  177. float n2 = Mathf.Abs(normal[2]);
  178.  
  179. i0 = 1; i1 = 2;
  180. if (n1 > n2)
  181. {
  182. if (n1 > n0) i0 = 0;
  183. }
  184. else
  185. {
  186. if (n2 > n0) i1 = 0;
  187. }
  188. }
  189.  
  190. //
  191. // Check the intersection point is inside the triangle.
  192. //
  193. {
  194. float u1 = b[i0] - a[i0];
  195. float v1 = b[i1] - a[i1];
  196. float u2 = c[i0] - a[i0];
  197. float v2 = c[i1] - a[i1];
  198. float u0 = t * ray.direction[i0] + ray.origin[i0] - a[i0];
  199. float v0 = t * ray.direction[i1] + ray.origin[i1] - a[i1];
  200.  
  201. float alpha = u0 * v2 - u2 * v0;
  202. float beta = u1 * v0 - u0 * v1;
  203. float area = u1 * v2 - u2 * v1;
  204.  
  205. // epsilon to avoid float precision error
  206. const float EPSILON = 1e-6f;
  207.  
  208. float tolerance = -EPSILON * area;
  209.  
  210. if (area > 0)
  211. {
  212. if (alpha < tolerance || beta < tolerance || alpha + beta > area - tolerance)
  213. return false;
  214. }
  215. else
  216. {
  217. if (alpha > tolerance || beta > tolerance || alpha + beta < area - tolerance)
  218. return false;
  219. }
  220. }
  221. distance = t;
  222.  
  223. return true;
  224. }
  225. /// <summary>
  226. /// 反转坐标序列
  227. /// </summary>
  228. /// <param name="coords"></param>
  229. /// <returns></returns>
  230. private static List<Vector3> Reverse(List<Vector3> coords)
  231. {
  232. List<Vector3> result = new List<Vector3>();
  233. for (int i = coords.Count - 1; i >= 0; i--)
  234. {
  235. result.Add(coords[i]);
  236. }
  237. return result;
  238. }
  239.  
  240. #endregion
  241. }
  242.  
  243. /// <summary>
  244. /// 多边形数据
  245. /// </summary>
  246. public class PolygonData
  247. {
  248. /// <summary>
  249. /// 顶点数据
  250. /// </summary>
  251. public List<Vector3> Vertices = new List<Vector3>();
  252. /// <summary>
  253. /// 索引数据
  254. /// </summary>
  255. public List<int> Indices = new List<int>();
  256. }
  257. }

多边形实体类

  1. 1 /// <summary>
  2. 2 /// 矢量面实体
  3. 3 /// </summary>
  4. 4 public class PloygonEntity
  5. 5 {
  6. 6 #region 字段
  7. 7
  8. 8 /// <summary>
  9. 9 /// 点集
  10. 10 /// </summary>
  11. 11 private List<Vector3> points = new List<Vector3>();
  12. 12
  13. 13 /// <summary>
  14. 14 /// 顶点集合
  15. 15 /// </summary>
  16. 16 private List<Vector3> vertexs = new List<Vector3>();
  17. 17
  18. 18 /// <summary>
  19. 19 /// 索引集合
  20. 20 /// </summary>
  21. 21 private List<int> triangles = new List<int>();
  22. 22
  23. 23 /// <summary>
  24. 24 /// 实体
  25. 25 /// </summary>
  26. 26 private GameObject obj;
  27. 27
  28. 28 private MeshFilter meshFilter;
  29. 29
  30. 30 private MeshRenderer meshRenderer;
  31. 31
  32. 32 private Material material;
  33. 33
  34. 34 #endregion
  35. 35
  36. 36
  37. 37 #region Methods
  38. 38
  39. 39 #region Public
  40. 40
  41. 41 /// <summary>
  42. 42 /// 创建矢量面实体
  43. 43 /// </summary>
  44. 44 /// <param name="points"></param>
  45. 45 public PloygonEntity(List<Vector3> points)
  46. 46 {
  47. 47 this.points = points;
  48. 48 SetVertexTrianglesData();
  49. 49 RenderPloygon();
  50. 50 }
  51. 51
  52. 52 public void AddPoint(Vector3 point)
  53. 53 {
  54. 54 point.y = 0;
  55. 55 points.Add(point);
  56. 56 SetVertexTrianglesData();
  57. 57 RenderPloygon();
  58. 58 }
  59. 59
  60. 60 public void ChangePoint(Vector3 vector3)
  61. 61 {
  62. 62 if (points.Count>0)
  63. 63 {
  64. 64 vector3.y = 0;
  65. 65 points[points.Count - 1] = vector3;
  66. 66 SetVertexTrianglesData();
  67. 67 RenderPloygon();
  68. 68 }
  69. 69 }
  70. 70
  71. 71 #endregion
  72. 72
  73. 73
  74. 74 #region Private
  75. 75
  76. 76 /// <summary>
  77. 77 /// 设置顶点三角形数据
  78. 78 /// </summary>
  79. 79 private void SetVertexTrianglesData()
  80. 80 {
  81. 81 for (int i = 0; i < points.Count; i++)
  82. 82 {
  83. 83 Debug.LogError(points[i]);
  84. 84 }
  85. 85 PolygonData data = GetPolygonData(points);
  86. 86 vertexs = data.Vertices;
  87. 87 triangles = data.Indices;
  88. 88 }
  89. 89
  90. 90 /// <summary>
  91. 91 /// 渲染面
  92. 92 /// </summary>
  93. 93 private void RenderPloygon()
  94. 94 {
  95. 95 if (obj == null)
  96. 96 obj = new GameObject();
  97. 97
  98. 98 if (meshFilter == null)
  99. 99 meshFilter = obj.AddComponent<MeshFilter>();
  100. 100
  101. 101 meshFilter.mesh = new Mesh
  102. 102 {
  103. 103 vertices = vertexs.ToArray(),
  104. 104 triangles = triangles.ToArray(),
  105. 105 };
  106. 106
  107. 107 meshFilter.mesh.RecalculateNormals();
  108. 108
  109. 109 if (meshRenderer == null)
  110. 110 meshRenderer = obj.AddComponent<MeshRenderer>();
  111. 111
  112. 112 SetMatraial();
  113. 113 meshRenderer.material = material;
  114. 114
  115. 115 if (points.Count > 2)
  116. 116 {
  117. 117 UpdateLineGameObject();
  118. 118 }
  119. 119 }
  120. 120
  121. 121 /// <summary>
  122. 122 /// 设置材质
  123. 123 /// </summary>
  124. 124 private void SetMatraial()
  125. 125 {
  126. 126 if (material==null)
  127. 127 {
  128. 128 material = new Material(Resources.Load<Shader>("shader/Ploygon"));
  129. 129 }
  130. 130 material.SetColor("_PloygonColor", Color.yellow);
  131. 131 material.SetFloat("_Scale", 1.2f);
  132. 132 material.SetVector("_CenterPointPosition", GetCenterPointPos());
  133. 133
  134. 134 }
  135. 135
  136. 136 // <summary>
  137. 137 /// 根据顶点集合获取矢量面数据
  138. 138 /// </summary>
  139. 139 private PolygonData GetPolygonData(List<Vector3> points)
  140. 140 {
  141. 141 return TriangulateUtil.GeneratePolygon(points);
  142. 142 }
  143. 143
  144. 144 private Vector3 GetCenterPointPos()
  145. 145 {
  146. 146 Vector3 point = Vector3.zero;
  147. 147 for (int i = 0; i < points.Count; i++)
  148. 148 {
  149. 149 point += Camera.main.WorldToScreenPoint(points[i]);
  150. 150 }
  151. 151 point /= points.Count;
  152. 152 return point;
  153. 153 }
  154. 154
  155. 155
  156. 156 /// <summary>
  157. 157 /// 创建边框线Mesh
  158. 158 /// </summary>
  159. 159 /// <param name="start">线起点</param>
  160. 160 /// <param name="end">线终点</param>
  161. 161 /// <returns>Mesh对象</returns>
  162. 162 private Mesh CreateLineMesh()
  163. 163 {
  164. 164 List<Vector3> vertex =new List<Vector3> ();
  165. 165 var indices = new List<int>();
  166. 166 for (int i = 0; i < points.Count; i++)
  167. 167 {
  168. 168 vertex .Add(points[i]);
  169. 169 indices.Add(i);
  170. 170 if (i > 0 && i < points.Count - 1)
  171. 171 {
  172. 172 indices.Add(i);
  173. 173 }
  174. 174 }
  175. 175 for (int i = points.Count - 1; i >= 0; i--)
  176. 176 {
  177. 177 indices.Add(i);
  178. 178 if (i > 0 && i < points.Count - 1)
  179. 179 {
  180. 180 indices.Add(i);
  181. 181 }
  182. 182 }
  183. 183
  184. 184 Mesh mesh = new Mesh();
  185. 185 mesh.SetVertices(points);
  186. 186 mesh.SetIndices(indices.ToArray(), MeshTopology.Lines, 0);
  187. 187
  188. 188 return mesh;
  189. 189 }
  190. 190
  191. 191
  192. 192 MeshFilter goMeshFilter;
  193. 193 MeshRenderer goMeshRenderer;
  194. 194 GameObject go;
  195. 195
  196. 196 private void UpdateLineGameObject()
  197. 197 {
  198. 198 if (go==null)
  199. 199 {
  200. 200 go = new GameObject("line");
  201. 201 }
  202. 202
  203. 203 if (goMeshFilter==null)
  204. 204 {
  205. 205 goMeshFilter = go.AddComponent<MeshFilter>();
  206. 206 }
  207. 207 goMeshFilter.mesh = CreateLineMesh();
  208. 208
  209. 209 if (goMeshRenderer==null)
  210. 210 {
  211. 211 goMeshRenderer = go.AddComponent<MeshRenderer>();
  212. 212 }
  213. 213 goMeshRenderer.material.color = Color.red;
  214. 214 }
  215. 215
  216. 216 #endregion
  217. 217
  218. 218 #endregion
  219. 219
  220. 220 }

核心代码就这些,创建测试自己调调试试吧。

Unity中创建多边形并计算面积的更多相关文章

  1. 在Unity中创建攻击Slot系统

    http://www.manew.com/thread-109310-1-1.html 马上注册,结交更多好友,享用更多功能,让你轻松玩转社区. 您需要 登录 才可以下载或查看,没有帐号?注册帐号  ...

  2. 在Unity中创建可远程加载的.unity3d包

    在一个Unity项目中,发布包本身不一定要包括所有的Asset(译为资产或组件),其它的部分可以单独发布为.unity3d,再由程序从本地/远程加载执行,这部分不在本文讨论范围.虽然Unity并没有直 ...

  3. Unity中创建二维码

    在网络上发现了一个可以把字符串转换成二维码的dll,但是我们要怎么使用他呢.不废话,直接进入主题. 用到的引用 using UnityEngine;using ZXing;using ZXing.Qr ...

  4. 在Unity中创建VR游戏

    添加VR插件为了为您选择的平台创建VR游戏,我们需要下载几个插件.出于本教程的目的,我将向您展示如何上传到Android平台.要上传到iOS,您需要下载 Xcode. 现在让我们下载Unity的Goo ...

  5. unity 中UGUI制作滚动条视图效果(按钮)

    1.在unity中创建一个Image作为滚动条视图的背景: 2.在Image下创建一个空物体,在空物体下创建unity自带的Scroll View组件: 3.对滑动条视图的子物体进行调整: 4.添加滚 ...

  6. Unity中通过深度优先算法和广度优先算法打印游戏物体名

    前言:又是一个月没写博客了,每次下班都懒得写,觉得浪费时间.... 深度优先搜索和广度优先搜索的定义,网络上已经说的很清楚了,我也是看了网上的才懂的,所以就不在这里赘述了.今天讲解的实例,主要是通过自 ...

  7. Unity中的3D数学

    3D数学(2022.11.25) 三角函数 Unity中会运用到角度制(Deg)和弧度制(Rad)的转换,弧度制是用圆的弧长来衡量角度的大小,π对应180度.这种转换在Unity中对应有两个方法: 角 ...

  8. Unity中动态创建Mesh

    什么是Mesh? Mesh是指的模型的网格,3D模型是由多边形拼接而成,而多边形实际上又是由多个三角形拼接而成的.即一个3D模型的表面其实是由多个彼此相连的三角面构成.三维空间中,构成这些三角形的点和 ...

  9. SAP HANA中创建计算视图(Calculation View)

    [Step By Step]SAP HANA中创建计算视图(Calculation View) Demo Instruction: 该视图将两个表AUDIOBOOKS和BOOKS中的数据进行连接,并作 ...

  10. 【Unity Shaders】Reflecting Your World —— 在Unity3D中创建Cubemaps

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

随机推荐

  1. 密钥存储在过时的 trusted.gpg 密钥环中(/etc/apt/trusted.gpg)

    密钥存储在过时的 trusted.gpg 密钥环中(/etc/apt/trusted.gpg) 问题: 解决: cd /etc/opt sudo cp trusted.gpg trusted.gpg. ...

  2. Kafka源码分析(四) - Server端-请求处理框架

    系列文章目录 https://zhuanlan.zhihu.com/p/367683572 一. 总体结构 先给一张概览图: 服务端请求处理过程涉及到两个模块:kafka.network和kafka. ...

  3. docker / compose 的安装 和 体验

    文档 官网文档 视频 视频 简介 课程内容 1.Docker Compose 容器编排 2.Docker Swarm #集群 热扩容 需要在阿里上买服务器,至少冲100+以上的人民币 文档: 集群方式 ...

  4. JavaScript 实现前端文件下载

    A.download HTML5的A标签有一个download属性,可以告诉浏览器下载而非预览文件,很实用,参考链接:http://www.zhangxinxu.com/wordpress/2016/ ...

  5. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明

    1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明 @ 目录 1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明 每博一文案 2. 环境准备 3 ...

  6. FFmpeg开发笔记(二十二)FFmpeg中SAR与DAR的显示宽高比

    ​<FFmpeg开发实战:从零基础到短视频上线>一书提到:通常情况下,在视频流解析之后,从AVCodecContext结构得到的宽高就是视频画面的宽高.然而有的视频文件并非如此,如果按照A ...

  7. 详解RocketMQ消息存储原理

    本文基于RocketMQ 4.6.0进行源码分析 一. 存储概要设计 RocketMQ存储的文件主要包括CommitLog文件.ConsumeQueue文件.Index文件.RocketMQ将所有to ...

  8. Flutter(七):Flutter混合开发--接入现有原生工程(iOS+Android)

    在上一篇文章Flutter(六):Flutter_Boost接入现有原生工程(iOS+Android)中介绍了Flutter_Boost的接入方法,这一篇将介绍Flutter自带的接入方法. 新建工程 ...

  9. 你唯一需要的是“Wide Events”,而非“Metrics、Logs、Traces”

    Charity Majors 的这句话可能是对科技行业当前可观察性状态的最好总结--完全的.大规模的混乱.大家都很困惑.什么是 trace?什么是 span?一行日志就是一个 span 吗?如果我有日 ...

  10. CICD介绍

    1.学习背景 当公司的服务器架构越来越复杂,需要频繁的发布新配置文件,以及新代码: 但是如果机器部署数量较多,发布的效率必然很低: 并且如果代码没有经过测试环境,预生产环境层层测试,最终才到生产环境, ...