Water.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. import { _decorator, Color, Graphics, Node, NodeEventType, v2, v3, Vec2, Vec3 } from 'cc';
  2. import { WaterData } from './WaterData';
  3. import { GameConfing } from '../../scene/GameConfing';
  4. //水面y轴半径
  5. const waterY = 15
  6. const { ccclass, property } = _decorator;
  7. @ccclass('Water')
  8. export class Water {
  9. // @property(Graphics)
  10. private drawGraphics: Graphics
  11. bottleWidth = 64
  12. bottleHeight = 207
  13. // 出水口位子
  14. kouPos = new Vec2(20, this.bottleHeight)// 出水口位置
  15. height: number
  16. with: number
  17. color: WaterData// 液体颜色数据
  18. isDrawButtom: boolean
  19. shape: Vec2[]
  20. /**
  21. * 初始化参数
  22. * @param shape bottleShape
  23. * @param bottleWidth 瓶子宽度
  24. * @param bottleHeight 瓶子高度
  25. * @param kouPos 瓶子口位置
  26. */
  27. initShape(shape: Vec2[], bottleWidth: number, bottleHeight: number, kouPos: number) {
  28. this.shape = shape
  29. this.bottleWidth = bottleWidth
  30. this.bottleHeight = bottleHeight
  31. this.kouPos.x = kouPos
  32. }
  33. initDrawGraphics(drawGraphics: Graphics) {
  34. this.drawGraphics = drawGraphics
  35. }
  36. initWater(height: number, color: WaterData) {
  37. this.height = height
  38. this.color = color
  39. }
  40. drawWater(lastColor: WaterData, angle: number = 0, Maxheight: number = this.height) {
  41. angle = angle % 360
  42. let height = this.height > Maxheight ? Maxheight : this.height
  43. return new Promise<void>((resolve) => {
  44. if (!this.drawGraphics) return resolve()
  45. // 修改判断逻辑:不仅要比较颜色,还要比较是否需要遮罩
  46. if (lastColor?.color == this.color.color && lastColor?.isHide == this.color?.isHide) {
  47. return resolve()
  48. }
  49. // 里面有画圆和不画圆的
  50. if (height <= 0) {
  51. return resolve()
  52. }
  53. this.drawOneWater(height, this.color, angle, lastColor);
  54. resolve()
  55. })
  56. }
  57. clear() {
  58. this.drawGraphics = null
  59. this.color = null
  60. }
  61. getTempWAngle(): number {
  62. // 当前总面积
  63. let sum = this.height * this.bottleWidth
  64. let dir = null
  65. if (sum < this.kouPos.x * this.bottleHeight * 0.5) {
  66. let h = 2 * sum / this.kouPos.x
  67. dir = v2(-this.kouPos.x, h)
  68. } else {
  69. let w1 = this.bottleWidth - this.kouPos.x
  70. // 当前空面基
  71. let s = (this.bottleHeight - this.height) * this.bottleWidth
  72. // 不变形可空最大面积
  73. let sk = w1 * this.bottleHeight * 0.5
  74. if (s > sk) {
  75. let wh = 2 * s / this.bottleHeight
  76. dir = v2(w1 - (wh - w1), this.bottleHeight)
  77. } else {
  78. let h = 2 * s / w1
  79. dir = v2(w1, h)
  80. }
  81. }
  82. let angle = Math.acos(dir.normalize().dot(v2(1, 0)))
  83. return Math.abs(angle)
  84. }
  85. getCenter(height = this.height) {
  86. return v2(this.bottleWidth / 2, height)
  87. }
  88. private drawOneWater(height: number, color: WaterData, angle = 0, lastColor: WaterData) {
  89. // 计算水面倾斜
  90. let tAngle = angle / 180 * Math.PI;
  91. let dir = v2(Math.cos(tAngle), -Math.sin(tAngle)).normalize()
  92. let center = this.getCenter(height)
  93. //计算临界角度
  94. let s = height * this.bottleWidth
  95. if (angle == 90) {
  96. angle += 2
  97. }
  98. let t = Math.abs(Math.tan(tAngle))
  99. let h = t * center.x;
  100. if (height < this.bottleHeight * 0.5) {
  101. if (h > height) {
  102. let w = Math.sqrt(2 * s / t)
  103. if (this.bottleHeight * this.bottleHeight >= 2 * s * t) {
  104. center = v2(w, 0)
  105. if (angle > 90) {
  106. center = v2(w, this.bottleHeight)
  107. center.x = Math.min(center.x, this.kouPos.x)
  108. }
  109. } else {
  110. let w = 2 * s / this.bottleHeight
  111. let w1 = this.bottleHeight / t
  112. if (angle <= 90) {
  113. center = v2((w - w1) / 2, this.bottleHeight)
  114. } else {
  115. center = v2((w + w1) / 2, this.bottleHeight)
  116. }
  117. center.x = Math.min(center.x, this.kouPos.x)
  118. }
  119. } else {
  120. if (angle > 90) {
  121. let w = Math.sqrt(2 * s / t)
  122. center = v2(w, this.bottleHeight)
  123. center.x = Math.min(center.x, this.kouPos.x)
  124. }
  125. }
  126. } else {
  127. if (h + height > this.bottleHeight) {
  128. let s1 = this.bottleHeight * this.bottleWidth - s
  129. let w = Math.sqrt(2 * s1 / t)
  130. if (this.bottleHeight * this.bottleHeight >= 2 * s1 * t) {
  131. center = v2(this.bottleWidth - w, this.bottleHeight)
  132. center.x = Math.min(center.x, this.kouPos.x)
  133. } else {
  134. let w1 = this.bottleHeight / t
  135. if (angle <= 90) {
  136. center = v2(this.bottleWidth - ((w + w1) / 2), this.bottleHeight)
  137. } else {
  138. center = v2(this.bottleWidth - (w - w1) / 2, this.bottleHeight)
  139. }
  140. center.x = Math.min(center.x, this.kouPos.x)
  141. }
  142. } else {
  143. if (angle > 90) {
  144. center = this.kouPos.clone()
  145. }
  146. }
  147. }
  148. // 计算需要画的梯形
  149. let shape = this.clipPolygonBelowFixed(this.shape, center, dir)
  150. // 这里要画圆
  151. let lineShape = shape.line
  152. let start = lineShape[0]
  153. let end = lineShape[1]
  154. if (lineShape.length >= 2) {
  155. if (lineShape.length > 2) {
  156. }
  157. lineShape.forEach((it) => {
  158. if (it.y > start.y) {
  159. start = it
  160. }
  161. if (it.y < end.y) {
  162. end = it
  163. }
  164. })
  165. let lenx = end.clone().subtract(start)
  166. let cen = end.clone().add(start).multiplyScalar(0.5)
  167. let k = 1 //+ Math.abs(Math.sin(tAngle) * 0.2)
  168. let rx = lenx.length() * k / 2
  169. let ry = Math.min(waterY, (rx * 0.8))
  170. let resShpe: Vec3[] = []
  171. let topColor = color.topColor
  172. if (!lastColor) {
  173. // resShpe = this.ellipse(cen.x, cen.y, rx, ry, tAngle)
  174. this.drawShape(shape.shape, color.buttomColor)
  175. // this.drawShape(resShpe, topColor)
  176. this.drawHightShape(topColor, cen.x, cen.y, rx, ry, -tAngle)
  177. } else {
  178. resShpe = this.ellipse(cen.x, cen.y, rx, ry, tAngle, 180, 180)
  179. topColor = lastColor.buttomColor
  180. // 这里需要给shape.shape排序
  181. let startIndex = 0
  182. shape.shape.forEach((it, index) => {
  183. if (it == lineShape[0]) {
  184. startIndex = index
  185. }
  186. })
  187. let res: any[] = []
  188. if (Math.abs(angle) > 5) {
  189. if (angle > 90) {
  190. if (resShpe[resShpe.length - 1].y > resShpe[0].y) {
  191. resShpe.reverse()
  192. }
  193. }
  194. res.push(...resShpe)
  195. if (resShpe[resShpe.length - 1].y > 0) {
  196. res.push(v2(resShpe[resShpe.length - 1].x, 0))
  197. }
  198. res.push(v2(0, 0))
  199. if (resShpe[0].x > 0) {
  200. res.push(v2(0, resShpe[0].y))
  201. }
  202. } else {
  203. resShpe.reverse()
  204. for (let i = 0; i < shape.shape.length; i++) {
  205. let t = (i + startIndex) % shape.shape.length
  206. res.push(shape.shape[t])
  207. }
  208. res.push(...resShpe)
  209. }
  210. this.drawShape(res, color.buttomColor)
  211. // this.drawShape(resShpe, topColor)
  212. }
  213. }
  214. }
  215. drawShape(shape: Vec3[] | Vec2[], color: Color) {
  216. let k = 100
  217. let colorc = new Color(k, k, k, 80)
  218. this.drawGraphics.fillColor = color
  219. // this.drawGraphics.strokeColor = color.clone().multiply(colorc)
  220. shape.forEach((it, ind) => {
  221. if (ind == 0) {
  222. this.drawGraphics.moveTo(it.x, it.y)
  223. }
  224. this.drawGraphics.lineTo(it.x, it.y)
  225. })
  226. this.drawGraphics.fill()
  227. // this.drawGraphics.stroke()
  228. }
  229. /**
  230. *
  231. * @param cx
  232. * @param cy
  233. * @param rx
  234. * @param ry
  235. * @param angle 360 的
  236. * @param startAng
  237. * @param angSize
  238. * @returns
  239. */
  240. // 画出带旋转的椭圆,画出60个点,可以画一个弧度,角坐标系计算
  241. ellipse(cx: number, cy: number, rx: number, ry: number, angle: number, startAng: number = 0, angSize = 360): Vec3[] {
  242. let pointLen = 60
  243. let an = angSize / pointLen
  244. let points: Vec3[] = []
  245. for (let i = 0; i <= pointLen; i++) {
  246. let nangle = (an * i + startAng) / 180 * Math.PI - angle
  247. let p = 1 / (Math.pow(Math.cos(nangle + angle) / rx, 2) + Math.pow(Math.sin(nangle + angle) / ry, 2))
  248. p = Math.sqrt(p)
  249. let x = p * Math.cos(nangle) + cx
  250. let y = p * Math.sin(nangle) + cy
  251. points.push(v3(x, y, 0))
  252. }
  253. return points
  254. }
  255. clipPolygonBelowFixed(polygon: Vec2[], v2: Vec2, dir: Vec2): { shape: Vec2[], line: Vec2[] } {
  256. const result: Vec2[] = [];
  257. const line: Vec2[] = [];
  258. // 法线方向(用于判断点在直线的哪一边)
  259. const nx = -dir.y;
  260. const ny = dir.x;
  261. const px = v2.x;
  262. const py = v2.y;
  263. const n = polygon.length;
  264. for (let i = 0; i < n; i++) {
  265. const curr = polygon[i];
  266. const next = polygon[(i + 1) % n];
  267. const dx1 = curr.x - px;
  268. const dy1 = curr.y - py;
  269. const d1 = nx * dx1 + ny * dy1;
  270. const dx2 = next.x - px;
  271. const dy2 = next.y - py;
  272. const d2 = nx * dx2 + ny * dy2;
  273. const currInside = d1 <= 0;
  274. const nextInside = d2 <= 0;
  275. if (currInside && nextInside) {
  276. result.push(next);
  277. } else if (currInside && !nextInside) {
  278. // 出界,插入交点
  279. const t = d1 / (d1 - d2);
  280. const ix = curr.x + t * (next.x - curr.x);
  281. const iy = curr.y + t * (next.y - curr.y);
  282. const inter = new Vec2(ix, iy);
  283. result.push(inter);
  284. line.push(inter);
  285. } else if (!currInside && nextInside) {
  286. // 入界,插入交点 + next 点
  287. const t = d1 / (d1 - d2);
  288. const ix = curr.x + t * (next.x - curr.x);
  289. const iy = curr.y + t * (next.y - curr.y);
  290. const inter = new Vec2(ix, iy);
  291. result.push(inter);
  292. line.push(inter);
  293. result.push(next);
  294. }
  295. }
  296. return { shape: result, line: line };
  297. }
  298. /**
  299. * 绘制带边缘高光的椭圆(高光在对角方向)
  300. * @param color 基础颜色
  301. * @param cx 中心X
  302. * @param cy 中心Y
  303. * @param rx X半径
  304. * @param ry Y半径
  305. * @param angle 旋转角度(弧度)
  306. * @param lightDir 光源方向(默认右上方,但高光会出现在对角方向)
  307. */
  308. drawHightShape(
  309. color: Color,
  310. cx: number, cy: number,
  311. rx: number, ry: number,
  312. angle: number,
  313. lightDir: Vec2 = v2(1, 1).normalize()
  314. ) {
  315. const pointLen = 60;
  316. const points: Vec2[] = [];
  317. const highlightPoints: Vec2[] = [];
  318. // 预计算旋转矩阵
  319. const cosA = Math.cos(angle);
  320. const sinA = Math.sin(angle);
  321. // 反转光源方向(使高光出现在对角方向)
  322. const oppositeLightDir = v2(-lightDir.x, -lightDir.y);
  323. // 1. 生成椭圆点集
  324. for (let i = 0; i <= pointLen; i++) {
  325. const theta = (i / pointLen) * Math.PI * 2;
  326. const localX = rx * Math.cos(theta);
  327. const localY = ry * Math.sin(theta);
  328. // 旋转到世界坐标系
  329. const x = localX * cosA - localY * sinA + cx;
  330. const y = localX * sinA + localY * cosA + cy;
  331. points.push(v2(x, y));
  332. // 计算是否在对角方向的边缘(使用反转的光源方向)
  333. const normal = v2(
  334. (localX / (rx * rx)) * cosA - (localY / (ry * ry)) * sinA,
  335. (localX / (rx * rx)) * sinA + (localY / (ry * ry)) * cosA
  336. ).normalize();
  337. if (normal.dot(oppositeLightDir) > 0.7) { // 高光阈值
  338. highlightPoints.push(v2(x, y));
  339. }
  340. }
  341. this.drawShape(points, color)
  342. // 3. 绘制对角方向的边缘高光
  343. if (highlightPoints.length > 1) {
  344. this.drawGraphics.strokeColor = new Color(255, 255, 255, 80);
  345. this.drawGraphics.lineWidth = 2.5;
  346. // 按顺序连接高光点
  347. for (let i = 1; i < highlightPoints.length; i++) {
  348. this.drawGraphics.moveTo(highlightPoints[i - 1].x, highlightPoints[i - 1].y);
  349. this.drawGraphics.lineTo(highlightPoints[i].x, highlightPoints[i].y);
  350. }
  351. // 平滑连接首尾点(如果形成连续区域)
  352. if (highlightPoints.length > 2 &&
  353. Math.abs(highlightPoints[0].x - highlightPoints[highlightPoints.length - 1].x) < 20 &&
  354. Math.abs(highlightPoints[0].y - highlightPoints[highlightPoints.length - 1].y) < 20) {
  355. this.drawGraphics.moveTo(highlightPoints[highlightPoints.length - 1].x, highlightPoints[highlightPoints.length - 1].y);
  356. this.drawGraphics.lineTo(highlightPoints[0].x, highlightPoints[0].y);
  357. }
  358. this.drawGraphics.stroke();
  359. }
  360. }
  361. }