Files
Cocos3.8.5/assets/script/SplitRender/SplitRenderHelper.ts
ZhouXiao 487c68994d feat: add builder configuration file
feat: add cocos-service configuration file
feat: add device configuration file
feat: add engine configuration file
feat: add information configuration file
feat: add program configuration file
feat: add project configuration file
feat: add TypeScript configuration file
2025-12-22 11:42:51 +08:00

376 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { math, v2, Vec2, Vec3 } from 'cc';
export class SplitRenderHelper {
//ab与ac的叉积
static ab_cross_ac(a, b, c) {
return SplitRenderHelper.cross(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y);
}
static dot(x1, y1, x2, y2) {
return x1 * x2 + y1 * y2;
}
static cross(x1, y1, x2, y2) {
return x1 * y2 - x2 * y1;
}
static dblcmp(a: number, b: number) {
if (Math.abs(a - b) <= 0.000001) return 0;
if (a > b) return 1;
else return -1;
}
//求a点是不是在线段上>0不在=0与端点重合<0在。
static point_on_line(a, p1, p2) {
return SplitRenderHelper.dblcmp(SplitRenderHelper.dot(p1.x - a.x, p1.y - a.y, p2.x - a.x, p2.y - a.y), 0);
}
// 判断一个点是否在三角形内
static isInTriangle(point: Vec2, triA: Vec2, triB: Vec2, triC: Vec2) {
let AB: Vec2 = new Vec2();
Vec2.subtract(AB, triB, triA);
let AC: Vec2 = new Vec2();
Vec2.subtract(AC, triC, triA);
let BC: Vec2 = new Vec2();
Vec2.subtract(BC, triC, triB);
let AD: Vec2 = new Vec2();
Vec2.subtract(AD, point, triA);
let BD: Vec2 = new Vec2();
Vec2.subtract(BD, point, triB);
//@ts-ignore
return (AB.cross(AC) >= 0 ^ AB.cross(AD) < 0) && (AB.cross(AC) >= 0 ^ AC.cross(AD) >= 0) && (BC.cross(AB) > 0 ^ BC.cross(BD) >= 0);
}
static isInPolygon(checkPoint: Vec2, polygonPoints: Vec2[]) {
var counter = 0;
var i: number;
var xinters;
var p1: Vec2, p2: Vec2;
var pointCount = polygonPoints.length;
p1 = polygonPoints[0];
for (i = 1; i <= pointCount; i++) {
p2 = polygonPoints[i % pointCount];
if (
checkPoint.x > Math.min(p1.x, p2.x) &&
checkPoint.x <= Math.max(p1.x, p2.x)
) {
if (checkPoint.y <= Math.max(p1.y, p2.y)) {
if (p1.x != p2.x) {
xinters = (checkPoint.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x) + p1.y;
if (p1.y == p2.y || checkPoint.y <= xinters) {
counter++;
}
}
}
}
p1 = p2;
}
if (counter % 2 == 0) {
return false;
}
return true;
}
static computeUv(points: Vec2[], width: number, height: number) {
let uvs: Vec2[] = [];
for (const p of points) {
// uv原点是左上角
let x = math.clamp(0, 1, (p.x + width / 2) / width);
let y = math.clamp(0, 1, 1. - (p.y + height / 2) / height);
uvs.push(v2(x, y));
}
return uvs;
}
static splitPolygon(points: Vec2[]): number[] {
if (points.length <= 3) return [0, 1, 2];
let pointMap: { [key: string]: number } = {}; // point与idx的映射
for (let i = 0; i < points.length; i++) {
let p = points[i];
pointMap[`${p.x}-${p.y}`] = i;
}
const getIdx = (p: Vec2) => {
return pointMap[`${p.x}-${p.y}`]
}
points = points.concat([]);
let idxs: number[] = [];
let index = 0;
while (points.length > 3) {
let p1 = points[(index) % points.length]
, p2 = points[(index + 1) % points.length]
, p3 = points[(index + 2) % points.length];
let splitPoint = (index + 1) % points.length;
let v1: Vec2 = new Vec2();
Vec2.subtract(v1, p2, p1);
let v2: Vec2 = new Vec2();
Vec2.subtract(v2, p3, p2);
if (v1.cross(v2) < 0) { // 是一个凹角, 寻找下一个
index = (index + 1) % points.length;
continue;
}
let hasPoint = false;
for (const p of points) {
if (p != p1 && p != p2 && p != p3 && this.isInTriangle(p, p1, p2, p3)) {
hasPoint = true;
break;
}
}
if (hasPoint) { // 当前三角形包含其他点, 寻找下一个
index = (index + 1) % points.length;
continue;
}
// 找到了耳朵, 切掉
idxs.push(getIdx(p1), getIdx(p2), getIdx(p3));
points.splice(splitPoint, 1);
}
for (const p of points) {
idxs.push(getIdx(p));
}
return idxs;
}
//点发出的右射线和线段的关系
// 返回值: -1:不相交, 0:相交, 1:点在线段上
static rayPointToLine(point: Vec2, linePA: Vec2, linePB: Vec2) {
// 定义最小和最大的X Y轴值
let minX = Math.min(linePA.x, linePB.x);
let maxX = Math.max(linePA.x, linePB.x);
let minY = Math.min(linePA.y, linePB.y);
let maxY = Math.max(linePA.y, linePB.y);
// 射线与边无交点的其他情况
if (point.y < minY || point.y > maxY || point.x > maxX) {
return -1;
}
// 剩下的情况, 计算射线与边所在的直线的交点的横坐标
let x0 = linePA.x + ((linePB.x - linePA.x) / (linePB.y - linePA.y)) * (point.y - linePA.y);
if (x0 > point.x) {
return 0;
}
if (x0 == point.x) {
return 1;
}
return -1;
}
//点和多边形的关系
//返回值: -1:在多边形外部, 0:在多边形内部, 1:在多边形边线内, 2:跟多边形某个顶点重合
static relationPointToPolygon(point: Vec2, polygon: Vec2[]) {
let count = 0;
for (let i = 0; i < polygon.length; ++i) {
if (polygon[i].equals(point)) {
return 2;
}
let pa = polygon[i];
let pb = polygon[0];
if (i < polygon.length - 1) {
pb = polygon[i + 1];
}
let re = SplitRenderHelper.rayPointToLine(point, pa, pb);
if (re == 1) {
return 1;
}
if (re == 0) {
count++;
}
}
if (count % 2 == 0) {
return -1;
}
return 0;
}
//求两条线段的交点
//返回值:[n,p] n:0相交1在共有点-1不相交 p:交点
static lineCrossPoint(p1: Vec2, p2: Vec2, q1: Vec2, q2: Vec2): [number, Vec2] {
let a = p1, b = p2, c = q1, d = q2;
let s1, s2, s3, s4;
let d1, d2, d3, d4;
let p: Vec2 = new Vec2(0, 0);
d1 = SplitRenderHelper.dblcmp(s1 = SplitRenderHelper.ab_cross_ac(a, b, c), 0);
d2 = SplitRenderHelper.dblcmp(s2 = SplitRenderHelper.ab_cross_ac(a, b, d), 0);
d3 = SplitRenderHelper.dblcmp(s3 = SplitRenderHelper.ab_cross_ac(c, d, a), 0);
d4 = SplitRenderHelper.dblcmp(s4 = SplitRenderHelper.ab_cross_ac(c, d, b), 0);
if ((d1 ^ d2) == -2 && (d3 ^ d4) == -2) {
p.x = (c.x * s2 - d.x * s1) / (s2 - s1);
p.y = (c.y * s2 - d.y * s1) / (s2 - s1);
return [0, p];
}
if (d1 == 0 && SplitRenderHelper.point_on_line(c, a, b) <= 0) {
p = c;
return [1, p];
}
if (d2 == 0 && SplitRenderHelper.point_on_line(d, a, b) <= 0) {
p = d;
return [1, p];
}
if (d3 == 0 && SplitRenderHelper.point_on_line(a, c, d) <= 0) {
p = a;
return [1, p];
}
if (d4 == 0 && SplitRenderHelper.point_on_line(b, c, d) <= 0) {
p = b;
return [1, p];
}
return [-1, null];
}
//线段对多边形进行切割
//返回多边形数组
//如果没有被切割,返回空数组
static lineCutPolygon(pa: Vec2, pb: Vec2, polygon: Vec2[]) {
// 检查切割线的端点是否在多边形内部
const extendPoint = (point: Vec2, direction: Vec2) => {
const extendedPoint = new Vec2(point.x + direction.x * 1000, point.y + direction.y * 1000);
return extendedPoint;
};
if (SplitRenderHelper.isInPolygon(pa, polygon)) {
const direction = Vec2.subtract(new Vec2(), pa, pb).normalize();
pa = extendPoint(pa, direction);
}
if (SplitRenderHelper.isInPolygon(pb, polygon)) {
const direction = Vec2.subtract(new Vec2(), pb, pa).normalize();
pb = extendPoint(pb, direction);
}
let ret: Array<Vec2[]> = [];
let points: Vec2[] = [];
let pointIndex: number[] = [];
for (let i = 0; i < polygon.length; ++i) {
points.push(polygon[i]);
let a = polygon[i];
let b = polygon[0];
if (i < polygon.length - 1) b = polygon[i + 1];
let c = SplitRenderHelper.lineCrossPoint(pa, pb, a, b);
if (c[0] == 0) {
pointIndex.push(points.length);
points.push(c[1] as Vec2);
}
else if (c[0] > 0) {
if ((c[1] as Vec2).equals(a)) {
pointIndex.push(points.length - 1);
}
else {
pointIndex.push(points.length);
}
}
}
if (pointIndex.length > 1) {
let cp0 = points[pointIndex[0]];
let cp1 = points[pointIndex[1]];
let r = SplitRenderHelper.relationPointToPolygon(new Vec2((cp0.x + cp1.x) / 2, (cp0.y + cp1.y) / 2), polygon);
let inPolygon: boolean = r >= 0;
let cp0_cp1: Vec2 = new Vec2();
let len_0_1 = Vec2.subtract(cp0_cp1, cp0, cp1).length()
let cp0_cp: Vec2 = new Vec2();
let len_0_ = Vec2.subtract(cp0_cp, cp0, points[pointIndex[pointIndex.length - 1]]).length()
if (pointIndex.length > 2 && len_0_1 > len_0_) {
cp1 = points[pointIndex[pointIndex.length - 1]];
r = SplitRenderHelper.relationPointToPolygon(new Vec2((cp0.x + cp1.x) / 2, (cp0.y + cp1.y) / 2), polygon);
inPolygon = r < 0;
}
let firstInPolygon = inPolygon;
let index = 0;
let startIndex = pointIndex[index];
let oldPoints = [];
let newPoints = [];
let count = 0;
oldPoints.push(points[startIndex]);
if (inPolygon) {
newPoints.push(points[startIndex]);
}
index++;
count++;
startIndex++;
while (count < points.length) {
if (startIndex == points.length) startIndex = 0;
let p = points[startIndex];
if (index >= 0 && startIndex == pointIndex[index]) {
index++;
if (index >= pointIndex.length) index = 0;
if (inPolygon) {
newPoints.push(p);
ret.push(newPoints);
newPoints = [];
}
else {
newPoints = [];
newPoints.push(p);
}
oldPoints.push(p);
inPolygon = !inPolygon;
}
else {
if (inPolygon) {
newPoints.push(p);
}
else {
oldPoints.push(p);
}
}
startIndex++;
count++;
}
if (inPolygon) {
if (!firstInPolygon && newPoints.length > 1) {
newPoints.push(points[pointIndex[0]]);
ret.push(newPoints);
}
else {
for (let i = 0; i < newPoints.length; ++i) {
oldPoints.push(newPoints[i]);
}
}
}
ret.push(oldPoints);
}
return ret;
}
// 实用函数
static isPointInsidePolygon(p: Vec2, polygon: Vec2[]) {
let windingNumber = 0;
for (let i = 0; i < polygon.length; i++) {
const a = polygon[i];
const b = polygon[(i + 1) % polygon.length];
if (a.y <= p.y) {
if (b.y > p.y && (b.x - a.x) * (p.y - a.y) > (p.x - a.x) * (b.y - a.y))
windingNumber++;
} else {
if (b.y <= p.y && (b.x - a.x) * (p.y - a.y) < (p.x - a.x) * (b.y - a.y))
windingNumber--;
}
}
return windingNumber !== 0;
}
static calculatePolygonArea(polygon: Vec2[]) {
let area = 0;
for (let i = 0; i < polygon.length; i++) {
const j = (i + 1) % polygon.length;
area += polygon[i].x * polygon[j].y;
area -= polygon[j].x * polygon[i].y;
}
return Math.abs(area) / 2;
}
}