| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- import { _decorator, Color, Graphics, Node, NodeEventType, v2, v3, Vec2, Vec3 } from 'cc';
- import { WaterData } from './WaterData';
- import { GameConfing } from '../../scene/GameConfing';
- //水面y轴半径
- const waterY = 15
- const { ccclass, property } = _decorator;
- @ccclass('Water')
- export class Water {
- // @property(Graphics)
- private drawGraphics: Graphics
- bottleWidth = 64
- bottleHeight = 207
- // 出水口位子
- kouPos = new Vec2(20, this.bottleHeight)// 出水口位置
- height: number
- with: number
- color: WaterData// 液体颜色数据
- isDrawButtom: boolean
- shape: Vec2[]
- /**
- * 初始化参数
- * @param shape bottleShape
- * @param bottleWidth 瓶子宽度
- * @param bottleHeight 瓶子高度
- * @param kouPos 瓶子口位置
- */
- initShape(shape: Vec2[], bottleWidth: number, bottleHeight: number, kouPos: number) {
- this.shape = shape
- this.bottleWidth = bottleWidth
- this.bottleHeight = bottleHeight
- this.kouPos.x = kouPos
- }
- initDrawGraphics(drawGraphics: Graphics) {
- this.drawGraphics = drawGraphics
- }
- initWater(height: number, color: WaterData) {
- this.height = height
- this.color = color
- }
- drawWater(lastColor: WaterData, angle: number = 0, Maxheight: number = this.height) {
- angle = angle % 360
-
- let height = this.height > Maxheight ? Maxheight : this.height
- return new Promise<void>((resolve) => {
- if (!this.drawGraphics) return resolve()
- // 修改判断逻辑:不仅要比较颜色,还要比较是否需要遮罩
- if (lastColor?.color == this.color.color && lastColor?.isHide == this.color?.isHide) {
- return resolve()
- }
-
- // 里面有画圆和不画圆的
-
- if (height <= 0) {
- return resolve()
- }
- this.drawOneWater(height, this.color, angle, lastColor);
- resolve()
- })
- }
- clear() {
- this.drawGraphics = null
- this.color = null
- }
- getTempWAngle(): number {
- // 当前总面积
- let sum = this.height * this.bottleWidth
- let dir = null
- if (sum < this.kouPos.x * this.bottleHeight * 0.5) {
- let h = 2 * sum / this.kouPos.x
- dir = v2(-this.kouPos.x, h)
- } else {
- let w1 = this.bottleWidth - this.kouPos.x
- // 当前空面基
- let s = (this.bottleHeight - this.height) * this.bottleWidth
- // 不变形可空最大面积
- let sk = w1 * this.bottleHeight * 0.5
- if (s > sk) {
- let wh = 2 * s / this.bottleHeight
- dir = v2(w1 - (wh - w1), this.bottleHeight)
- } else {
- let h = 2 * s / w1
- dir = v2(w1, h)
- }
- }
- let angle = Math.acos(dir.normalize().dot(v2(1, 0)))
- return Math.abs(angle)
- }
- getCenter(height = this.height) {
- return v2(this.bottleWidth / 2, height)
- }
- private drawOneWater(height: number, color: WaterData, angle = 0, lastColor: WaterData) {
- // 计算水面倾斜
- let tAngle = angle / 180 * Math.PI;
- let dir = v2(Math.cos(tAngle), -Math.sin(tAngle)).normalize()
- let center = this.getCenter(height)
- //计算临界角度
- let s = height * this.bottleWidth
- if (angle == 90) {
- angle += 2
- }
- let t = Math.abs(Math.tan(tAngle))
- let h = t * center.x;
- if (height < this.bottleHeight * 0.5) {
- if (h > height) {
- let w = Math.sqrt(2 * s / t)
- if (this.bottleHeight * this.bottleHeight >= 2 * s * t) {
- center = v2(w, 0)
- if (angle > 90) {
- center = v2(w, this.bottleHeight)
- center.x = Math.min(center.x, this.kouPos.x)
- }
- } else {
- let w = 2 * s / this.bottleHeight
- let w1 = this.bottleHeight / t
- if (angle <= 90) {
- center = v2((w - w1) / 2, this.bottleHeight)
- } else {
- center = v2((w + w1) / 2, this.bottleHeight)
- }
- center.x = Math.min(center.x, this.kouPos.x)
- }
- } else {
- if (angle > 90) {
- let w = Math.sqrt(2 * s / t)
- center = v2(w, this.bottleHeight)
- center.x = Math.min(center.x, this.kouPos.x)
- }
- }
- } else {
- if (h + height > this.bottleHeight) {
- let s1 = this.bottleHeight * this.bottleWidth - s
- let w = Math.sqrt(2 * s1 / t)
- if (this.bottleHeight * this.bottleHeight >= 2 * s1 * t) {
- center = v2(this.bottleWidth - w, this.bottleHeight)
- center.x = Math.min(center.x, this.kouPos.x)
- } else {
- let w1 = this.bottleHeight / t
- if (angle <= 90) {
- center = v2(this.bottleWidth - ((w + w1) / 2), this.bottleHeight)
- } else {
- center = v2(this.bottleWidth - (w - w1) / 2, this.bottleHeight)
- }
- center.x = Math.min(center.x, this.kouPos.x)
- }
- } else {
- if (angle > 90) {
- center = this.kouPos.clone()
- }
- }
- }
- // 计算需要画的梯形
- let shape = this.clipPolygonBelowFixed(this.shape, center, dir)
- // 这里要画圆
- let lineShape = shape.line
- let start = lineShape[0]
- let end = lineShape[1]
- if (lineShape.length >= 2) {
- if (lineShape.length > 2) {
- }
- lineShape.forEach((it) => {
- if (it.y > start.y) {
- start = it
- }
- if (it.y < end.y) {
- end = it
- }
- })
- let lenx = end.clone().subtract(start)
- let cen = end.clone().add(start).multiplyScalar(0.5)
- let k = 1 //+ Math.abs(Math.sin(tAngle) * 0.2)
- let rx = lenx.length() * k / 2
- let ry = Math.min(waterY, (rx * 0.8))
- let resShpe: Vec3[] = []
- let topColor = color.topColor
- if (!lastColor) {
- // resShpe = this.ellipse(cen.x, cen.y, rx, ry, tAngle)
- this.drawShape(shape.shape, color.buttomColor)
- // this.drawShape(resShpe, topColor)
- this.drawHightShape(topColor, cen.x, cen.y, rx, ry, -tAngle)
- } else {
- resShpe = this.ellipse(cen.x, cen.y, rx, ry, tAngle, 180, 180)
- topColor = lastColor.buttomColor
- // 这里需要给shape.shape排序
- let startIndex = 0
- shape.shape.forEach((it, index) => {
- if (it == lineShape[0]) {
- startIndex = index
- }
- })
- let res: any[] = []
- if (Math.abs(angle) > 5) {
- if (angle > 90) {
- if (resShpe[resShpe.length - 1].y > resShpe[0].y) {
- resShpe.reverse()
- }
- }
- res.push(...resShpe)
- if (resShpe[resShpe.length - 1].y > 0) {
- res.push(v2(resShpe[resShpe.length - 1].x, 0))
- }
- res.push(v2(0, 0))
- if (resShpe[0].x > 0) {
- res.push(v2(0, resShpe[0].y))
- }
- } else {
- resShpe.reverse()
- for (let i = 0; i < shape.shape.length; i++) {
- let t = (i + startIndex) % shape.shape.length
- res.push(shape.shape[t])
- }
- res.push(...resShpe)
- }
- this.drawShape(res, color.buttomColor)
- // this.drawShape(resShpe, topColor)
- }
- }
- }
- drawShape(shape: Vec3[] | Vec2[], color: Color) {
- let k = 100
- let colorc = new Color(k, k, k, 80)
- this.drawGraphics.fillColor = color
- // this.drawGraphics.strokeColor = color.clone().multiply(colorc)
- shape.forEach((it, ind) => {
- if (ind == 0) {
- this.drawGraphics.moveTo(it.x, it.y)
- }
- this.drawGraphics.lineTo(it.x, it.y)
- })
- this.drawGraphics.fill()
- // this.drawGraphics.stroke()
- }
- /**
- *
- * @param cx
- * @param cy
- * @param rx
- * @param ry
- * @param angle 360 的
- * @param startAng
- * @param angSize
- * @returns
- */
- // 画出带旋转的椭圆,画出60个点,可以画一个弧度,角坐标系计算
- ellipse(cx: number, cy: number, rx: number, ry: number, angle: number, startAng: number = 0, angSize = 360): Vec3[] {
- let pointLen = 60
- let an = angSize / pointLen
- let points: Vec3[] = []
- for (let i = 0; i <= pointLen; i++) {
- let nangle = (an * i + startAng) / 180 * Math.PI - angle
- let p = 1 / (Math.pow(Math.cos(nangle + angle) / rx, 2) + Math.pow(Math.sin(nangle + angle) / ry, 2))
- p = Math.sqrt(p)
- let x = p * Math.cos(nangle) + cx
- let y = p * Math.sin(nangle) + cy
- points.push(v3(x, y, 0))
- }
- return points
- }
- clipPolygonBelowFixed(polygon: Vec2[], v2: Vec2, dir: Vec2): { shape: Vec2[], line: Vec2[] } {
- const result: Vec2[] = [];
- const line: Vec2[] = [];
- // 法线方向(用于判断点在直线的哪一边)
- const nx = -dir.y;
- const ny = dir.x;
- const px = v2.x;
- const py = v2.y;
- const n = polygon.length;
- for (let i = 0; i < n; i++) {
- const curr = polygon[i];
- const next = polygon[(i + 1) % n];
- const dx1 = curr.x - px;
- const dy1 = curr.y - py;
- const d1 = nx * dx1 + ny * dy1;
- const dx2 = next.x - px;
- const dy2 = next.y - py;
- const d2 = nx * dx2 + ny * dy2;
- const currInside = d1 <= 0;
- const nextInside = d2 <= 0;
- if (currInside && nextInside) {
- result.push(next);
- } else if (currInside && !nextInside) {
- // 出界,插入交点
- const t = d1 / (d1 - d2);
- const ix = curr.x + t * (next.x - curr.x);
- const iy = curr.y + t * (next.y - curr.y);
- const inter = new Vec2(ix, iy);
- result.push(inter);
- line.push(inter);
- } else if (!currInside && nextInside) {
- // 入界,插入交点 + next 点
- const t = d1 / (d1 - d2);
- const ix = curr.x + t * (next.x - curr.x);
- const iy = curr.y + t * (next.y - curr.y);
- const inter = new Vec2(ix, iy);
- result.push(inter);
- line.push(inter);
- result.push(next);
- }
- }
- return { shape: result, line: line };
- }
- /**
- * 绘制带边缘高光的椭圆(高光在对角方向)
- * @param color 基础颜色
- * @param cx 中心X
- * @param cy 中心Y
- * @param rx X半径
- * @param ry Y半径
- * @param angle 旋转角度(弧度)
- * @param lightDir 光源方向(默认右上方,但高光会出现在对角方向)
- */
- drawHightShape(
- color: Color,
- cx: number, cy: number,
- rx: number, ry: number,
- angle: number,
- lightDir: Vec2 = v2(1, 1).normalize()
- ) {
- const pointLen = 60;
- const points: Vec2[] = [];
- const highlightPoints: Vec2[] = [];
- // 预计算旋转矩阵
- const cosA = Math.cos(angle);
- const sinA = Math.sin(angle);
- // 反转光源方向(使高光出现在对角方向)
- const oppositeLightDir = v2(-lightDir.x, -lightDir.y);
- // 1. 生成椭圆点集
- for (let i = 0; i <= pointLen; i++) {
- const theta = (i / pointLen) * Math.PI * 2;
- const localX = rx * Math.cos(theta);
- const localY = ry * Math.sin(theta);
- // 旋转到世界坐标系
- const x = localX * cosA - localY * sinA + cx;
- const y = localX * sinA + localY * cosA + cy;
- points.push(v2(x, y));
- // 计算是否在对角方向的边缘(使用反转的光源方向)
- const normal = v2(
- (localX / (rx * rx)) * cosA - (localY / (ry * ry)) * sinA,
- (localX / (rx * rx)) * sinA + (localY / (ry * ry)) * cosA
- ).normalize();
- if (normal.dot(oppositeLightDir) > 0.7) { // 高光阈值
- highlightPoints.push(v2(x, y));
- }
- }
- this.drawShape(points, color)
- // 3. 绘制对角方向的边缘高光
- if (highlightPoints.length > 1) {
- this.drawGraphics.strokeColor = new Color(255, 255, 255, 80);
- this.drawGraphics.lineWidth = 2.5;
- // 按顺序连接高光点
- for (let i = 1; i < highlightPoints.length; i++) {
- this.drawGraphics.moveTo(highlightPoints[i - 1].x, highlightPoints[i - 1].y);
- this.drawGraphics.lineTo(highlightPoints[i].x, highlightPoints[i].y);
- }
- // 平滑连接首尾点(如果形成连续区域)
- if (highlightPoints.length > 2 &&
- Math.abs(highlightPoints[0].x - highlightPoints[highlightPoints.length - 1].x) < 20 &&
- Math.abs(highlightPoints[0].y - highlightPoints[highlightPoints.length - 1].y) < 20) {
- this.drawGraphics.moveTo(highlightPoints[highlightPoints.length - 1].x, highlightPoints[highlightPoints.length - 1].y);
- this.drawGraphics.lineTo(highlightPoints[0].x, highlightPoints[0].y);
- }
- this.drawGraphics.stroke();
- }
- }
- }
|