xl-public-utils 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,130 @@
1
+ import { Curve } from '../Curve/Curve.js';
2
+ import { Vector2 } from '../Math/Vector2.js';
3
+
4
+ class EllipseCurve extends Curve {
5
+ constructor(aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0) {
6
+ super();
7
+
8
+ this.isEllipseCurve = true;
9
+
10
+ this.type = 'EllipseCurve';
11
+
12
+ this.aX = aX;
13
+ this.aY = aY;
14
+
15
+ this.xRadius = xRadius;
16
+ this.yRadius = yRadius;
17
+
18
+ this.aStartAngle = aStartAngle;
19
+ this.aEndAngle = aEndAngle;
20
+
21
+ this.aClockwise = aClockwise;
22
+
23
+ this.aRotation = aRotation;
24
+ }
25
+
26
+ getPoint(t, optionalTarget) {
27
+ const point = optionalTarget || new Vector2();
28
+
29
+ const twoPi = Math.PI * 2;
30
+ let deltaAngle = this.aEndAngle - this.aStartAngle;
31
+ const samePoints = Math.abs(deltaAngle) < Number.EPSILON;
32
+
33
+ // ensures that deltaAngle is 0 .. 2 PI
34
+ while (deltaAngle < 0) deltaAngle += twoPi;
35
+ while (deltaAngle > twoPi) deltaAngle -= twoPi;
36
+
37
+ if (deltaAngle < Number.EPSILON) {
38
+ if (samePoints) {
39
+ deltaAngle = 0;
40
+ } else {
41
+ deltaAngle = twoPi;
42
+ }
43
+ }
44
+
45
+ if (this.aClockwise === true && !samePoints) {
46
+ if (deltaAngle === twoPi) {
47
+ deltaAngle = -twoPi;
48
+ } else {
49
+ deltaAngle = deltaAngle - twoPi;
50
+ }
51
+ }
52
+
53
+ const angle = this.aStartAngle + t * deltaAngle;
54
+ let x = this.aX + this.xRadius * Math.cos(angle);
55
+ let y = this.aY + this.yRadius * Math.sin(angle);
56
+
57
+ if (this.aRotation !== 0) {
58
+ const cos = Math.cos(this.aRotation);
59
+ const sin = Math.sin(this.aRotation);
60
+
61
+ const tx = x - this.aX;
62
+ const ty = y - this.aY;
63
+
64
+ // Rotate the point about the center of the ellipse.
65
+ x = tx * cos - ty * sin + this.aX;
66
+ y = tx * sin + ty * cos + this.aY;
67
+ }
68
+
69
+ return point.set(x, y);
70
+ }
71
+
72
+ copy(source) {
73
+ super.copy(source);
74
+
75
+ this.aX = source.aX;
76
+ this.aY = source.aY;
77
+
78
+ this.xRadius = source.xRadius;
79
+ this.yRadius = source.yRadius;
80
+
81
+ this.aStartAngle = source.aStartAngle;
82
+ this.aEndAngle = source.aEndAngle;
83
+
84
+ this.aClockwise = source.aClockwise;
85
+
86
+ this.aRotation = source.aRotation;
87
+
88
+ return this;
89
+ }
90
+
91
+ toJSON() {
92
+ const data = super.toJSON();
93
+
94
+ data.aX = this.aX;
95
+ data.aY = this.aY;
96
+
97
+ data.xRadius = this.xRadius;
98
+ data.yRadius = this.yRadius;
99
+
100
+ data.aStartAngle = this.aStartAngle;
101
+ data.aEndAngle = this.aEndAngle;
102
+
103
+ data.aClockwise = this.aClockwise;
104
+
105
+ data.aRotation = this.aRotation;
106
+
107
+ return data;
108
+ }
109
+
110
+ fromJSON(json) {
111
+ super.fromJSON(json);
112
+
113
+ this.aX = json.aX;
114
+ this.aY = json.aY;
115
+
116
+ this.xRadius = json.xRadius;
117
+ this.yRadius = json.yRadius;
118
+
119
+ this.aStartAngle = json.aStartAngle;
120
+ this.aEndAngle = json.aEndAngle;
121
+
122
+ this.aClockwise = json.aClockwise;
123
+
124
+ this.aRotation = json.aRotation;
125
+
126
+ return this;
127
+ }
128
+ }
129
+
130
+ export { EllipseCurve };
@@ -0,0 +1,70 @@
1
+ import { Curve } from '../Curve/Curve.js';
2
+ import { Vector2 } from '../Math/Vector2.js';
3
+
4
+ class LineCurve extends Curve {
5
+ constructor(v1 = new Vector2(), v2 = new Vector2()) {
6
+ super();
7
+
8
+ this.isLineCurve = true;
9
+
10
+ this.type = 'LineCurve';
11
+
12
+ this.v1 = v1;
13
+ this.v2 = v2;
14
+ }
15
+
16
+ getPoint(t, optionalTarget = new Vector2()) {
17
+ const point = optionalTarget;
18
+
19
+ if (t === 1) {
20
+ point.copy(this.v2);
21
+ } else {
22
+ point.copy(this.v2).sub(this.v1);
23
+ point.multiplyScalar(t).add(this.v1);
24
+ }
25
+
26
+ return point;
27
+ }
28
+
29
+ // Line curve is linear, so we can overwrite default getPointAt
30
+ getPointAt(u, optionalTarget) {
31
+ return this.getPoint(u, optionalTarget);
32
+ }
33
+
34
+ getTangent(t, optionalTarget) {
35
+ const tangent = optionalTarget || new Vector2();
36
+
37
+ tangent.copy(this.v2).sub(this.v1).normalize();
38
+
39
+ return tangent;
40
+ }
41
+
42
+ copy(source) {
43
+ super.copy(source);
44
+
45
+ this.v1.copy(source.v1);
46
+ this.v2.copy(source.v2);
47
+
48
+ return this;
49
+ }
50
+
51
+ toJSON() {
52
+ const data = super.toJSON();
53
+
54
+ data.v1 = this.v1.toArray();
55
+ data.v2 = this.v2.toArray();
56
+
57
+ return data;
58
+ }
59
+
60
+ fromJSON(json) {
61
+ super.fromJSON(json);
62
+
63
+ this.v1.fromArray(json.v1);
64
+ this.v2.fromArray(json.v2);
65
+
66
+ return this;
67
+ }
68
+ }
69
+
70
+ export { LineCurve };
@@ -0,0 +1,61 @@
1
+ import { Curve } from '../Curve/Curve.js';
2
+ import { Vector2 } from '../Math/Vector2.js';
3
+ import { QuadraticBezier } from '../Curve/Interpolations.js';
4
+
5
+ class QuadraticBezierCurve extends Curve {
6
+ constructor(v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2()) {
7
+ super();
8
+
9
+ this.isQuadraticBezierCurve = true;
10
+
11
+ this.type = 'QuadraticBezierCurve';
12
+
13
+ this.v0 = v0;
14
+ this.v1 = v1;
15
+ this.v2 = v2;
16
+ }
17
+
18
+ getPoint(t, optionalTarget = new Vector2()) {
19
+ const point = optionalTarget;
20
+
21
+ const v0 = this.v0,
22
+ v1 = this.v1,
23
+ v2 = this.v2;
24
+
25
+ point.set(QuadraticBezier(t, v0.x, v1.x, v2.x), QuadraticBezier(t, v0.y, v1.y, v2.y));
26
+
27
+ return point;
28
+ }
29
+
30
+ copy(source) {
31
+ super.copy(source);
32
+
33
+ this.v0.copy(source.v0);
34
+ this.v1.copy(source.v1);
35
+ this.v2.copy(source.v2);
36
+
37
+ return this;
38
+ }
39
+
40
+ toJSON() {
41
+ const data = super.toJSON();
42
+
43
+ data.v0 = this.v0.toArray();
44
+ data.v1 = this.v1.toArray();
45
+ data.v2 = this.v2.toArray();
46
+
47
+ return data;
48
+ }
49
+
50
+ fromJSON(json) {
51
+ super.fromJSON(json);
52
+
53
+ this.v0.fromArray(json.v0);
54
+ this.v1.fromArray(json.v1);
55
+ this.v2.fromArray(json.v2);
56
+
57
+ return this;
58
+ }
59
+ }
60
+
61
+ export { QuadraticBezierCurve };
@@ -0,0 +1,76 @@
1
+ import { Curve } from '../Curve/Curve.js';
2
+ import { Vector2 } from '../Math/Vector2.js';
3
+ import { CatmullRom } from '../Curve/Interpolations.js';
4
+
5
+ class SplineCurve extends Curve {
6
+ constructor(points = []) {
7
+ super();
8
+
9
+ this.isSplineCurve = true;
10
+
11
+ this.type = 'SplineCurve';
12
+
13
+ this.points = points;
14
+ }
15
+
16
+ getPoint(t, optionalTarget = new Vector2()) {
17
+ const point = optionalTarget;
18
+
19
+ const points = this.points;
20
+ const p = (points.length - 1) * t;
21
+
22
+ const intPoint = Math.floor(p);
23
+ const weight = p - intPoint;
24
+
25
+ const p0 = points[intPoint === 0 ? intPoint : intPoint - 1];
26
+ const p1 = points[intPoint];
27
+ const p2 = points[intPoint > points.length - 2 ? points.length - 1 : intPoint + 1];
28
+ const p3 = points[intPoint > points.length - 3 ? points.length - 1 : intPoint + 2];
29
+
30
+ point.set(CatmullRom(weight, p0.x, p1.x, p2.x, p3.x), CatmullRom(weight, p0.y, p1.y, p2.y, p3.y));
31
+
32
+ return point;
33
+ }
34
+
35
+ copy(source) {
36
+ super.copy(source);
37
+
38
+ this.points = [];
39
+
40
+ for (let i = 0, l = source.points.length; i < l; i++) {
41
+ const point = source.points[i];
42
+
43
+ this.points.push(point.clone());
44
+ }
45
+
46
+ return this;
47
+ }
48
+
49
+ toJSON() {
50
+ const data = super.toJSON();
51
+
52
+ data.points = [];
53
+
54
+ for (let i = 0, l = this.points.length; i < l; i++) {
55
+ const point = this.points[i];
56
+ data.points.push(point.toArray());
57
+ }
58
+
59
+ return data;
60
+ }
61
+
62
+ fromJSON(json) {
63
+ super.fromJSON(json);
64
+
65
+ this.points = [];
66
+
67
+ for (let i = 0, l = json.points.length; i < l; i++) {
68
+ const point = json.points[i];
69
+ this.points.push(new Vector2().fromArray(point));
70
+ }
71
+
72
+ return this;
73
+ }
74
+ }
75
+
76
+ export { SplineCurve };
@@ -0,0 +1,166 @@
1
+ import { Font } from './TextGeometry/FontLoader.js';
2
+ import { ExtrudeGeometry } from './TextGeometry/ExtrudeGeometry.js';
3
+ import { mat4, vec3 } from 'gl-matrix';
4
+
5
+ import vtkPolyData from '@kitware/vtk.js/Common/DataModel/PolyData.js';
6
+ import vtkPoints from '@kitware/vtk.js/Common/Core/Points.js';
7
+ import vtkCellArray from '@kitware/vtk.js/Common/Core/CellArray.js';
8
+
9
+ import {
10
+ clonePolyData,
11
+ deduplicatePolyData,
12
+ pointsApplyMat4
13
+ } from '../vtkUtils.js';
14
+
15
+ /**
16
+ * 文字网格管理
17
+ */
18
+ class FontManager {
19
+ static instance;
20
+
21
+ constructor() {
22
+ if (FontManager.instance) return FontManager.instance;
23
+
24
+ /** @type {Object.<string, vtkPolyData>} */
25
+ this.cacheFontMap = {};
26
+ /** @type {Object.<string, { zeroIndexs: number[], oneIndexs: number[] }>} */
27
+ this.cacheFontPointMap = {};
28
+
29
+ this.fontJsonMap = {};
30
+
31
+ /** @type {{ font: Object, size: number, height: number, curveSegments: number, bevelThickness: number, bevelSize: number, bevelEnabled: boolean, font: Record<string, Font> }} */
32
+ this.parameters = {
33
+ font: null,
34
+ size: 100,
35
+ height: 20,
36
+ curveSegments: 4,
37
+ bevelThickness: 2,
38
+ bevelSize: 3,
39
+ bevelEnabled: true,
40
+ font: {}
41
+ };
42
+
43
+ FontManager.instance = this;
44
+ }
45
+
46
+ static Mgr() {
47
+ return new FontManager();
48
+ }
49
+
50
+ /**
51
+ * 加载字体文件
52
+ * @param {string} url 文件json地址
53
+ * @param {string} fontName 字体名称
54
+ * @param {boolean} bevelEnabled
55
+ */
56
+ async loadFontFile(url, fontName = 'defaultFont', bevelEnabled = false) {
57
+ if(this.fontJsonMap[fontName]) {
58
+ return;
59
+ }
60
+ const fontMap = await (await fetch(url)).json();
61
+ this.fontJsonMap[fontName] = fontMap;
62
+ this.parameters.font[fontName] = new Font(this.fontJsonMap[fontName]);
63
+ this.parameters.bevelEnabled = bevelEnabled;
64
+ }
65
+
66
+ /**
67
+ * 生成文字的 PolyData
68
+ * @param {string} text
69
+ * @param {string} fontName
70
+ * @param {vec3} scale
71
+ * @returns {{ polyData: import('@kitware/vtk.js/Common/DataModel/PolyData').default, indexData: { zeroIndexs: number[], oneIndexs: number[] } }}
72
+ */
73
+ generateShapes(text, fontName = 'defaultFont', scale = [0.018, 0.018, 0.018]) {
74
+ const scaleM4 = mat4.scale(mat4.create(), mat4.create(), scale);
75
+
76
+ if (this.cacheFontMap[text]) {
77
+ const newPolyData = clonePolyData(this.cacheFontMap[text]);
78
+ pointsApplyMat4(scaleM4, newPolyData);
79
+ return {
80
+ polyData: newPolyData,
81
+ indexData: this.cacheFontPointMap[text]
82
+ };
83
+ }
84
+
85
+ const shapes = this.parameters.font[fontName].generateShapes(text, this.parameters.size);
86
+ const extrude = new ExtrudeGeometry(shapes, this.parameters);
87
+
88
+ let polyData = this.createPolyData(extrude.vertices, extrude.faces);
89
+ polyData = deduplicatePolyData(polyData);
90
+ const indexData = this.getPointsMapData(polyData);
91
+
92
+ const _polyData = clonePolyData(polyData);
93
+ pointsApplyMat4(scaleM4, _polyData);
94
+
95
+ this.cacheFontMap[text] = polyData;
96
+ this.cacheFontPointMap[text] = indexData;
97
+
98
+ return {
99
+ polyData: _polyData,
100
+ indexData
101
+ };
102
+ }
103
+
104
+ /**
105
+ * 获取前后层顶点索引映射数据
106
+ * @param {import('@kitware/vtk.js/Common/DataModel/PolyData').default} polydata
107
+ * @returns {{ zeroIndexs: number[], oneIndexs: number[] }}
108
+ */
109
+ getPointsMapData(polydata) {
110
+ const points = polydata.getPoints().getData();
111
+ const zeroIndexs = [];
112
+ const oneIndexs = [];
113
+ const pointIndexMap = {};
114
+
115
+ for (let i = 0; i < points.length / 3; i++) {
116
+ const point = [points[i * 3], points[i * 3 + 1], points[i * 3 + 2]];
117
+ if (point[2] === 0) {
118
+ zeroIndexs.push(i);
119
+ }
120
+ const key = point.join('_');
121
+ pointIndexMap[key] = i;
122
+ }
123
+
124
+ for (let i = 0; i < zeroIndexs.length; i++) {
125
+ const index = zeroIndexs[i];
126
+ const point = [points[index * 3], points[index * 3 + 1], 1];
127
+ const key = point.join('_');
128
+ const oneIndex = pointIndexMap[key];
129
+ oneIndexs.push(oneIndex);
130
+ }
131
+
132
+ return {
133
+ zeroIndexs,
134
+ oneIndexs
135
+ };
136
+ }
137
+
138
+ /**
139
+ * 创建 vtkPolyData 对象
140
+ * @param {any} verts
141
+ * @param {any} faces
142
+ * @returns {vtkPolyData}
143
+ */
144
+ createPolyData(verts, faces) {
145
+ const values_v = Object.values(verts);
146
+ const values_f = Object.values(faces);
147
+
148
+ const _verts = values_v.map(v => parseFloat(v));
149
+ const _faces = values_f.map(f => parseInt(f));
150
+
151
+ const vpoints = vtkPoints.newInstance();
152
+ const cellArray = vtkCellArray.newInstance();
153
+
154
+ vpoints.setData(_verts, 3);
155
+ cellArray.setNumberOfComponents(4);
156
+ cellArray.setData(_faces);
157
+
158
+ const polyData = vtkPolyData.newInstance();
159
+ polyData.setPoints(vpoints);
160
+ polyData.setPolys(cellArray);
161
+
162
+ return polyData;
163
+ }
164
+ }
165
+
166
+ export default FontManager;
package/src/vtkUtils.js CHANGED
@@ -256,29 +256,22 @@ export function projectVecToPlane(vec, norm) {
256
256
  * @returns {vec3} 投影后的点
257
257
  */
258
258
  export function projectPointOnPoints(point, begin, end) {
259
- const dx = begin[0] - end[0];
260
- const dy = begin[1] - end[1];
261
- const dz = begin[2] - end[2];
262
- const EPS = 0.00000001;
259
+ const EPS = 1e-8;
263
260
 
264
- // 确保两个点不是同一个点
265
- if (Math.abs(dx) < EPS && Math.abs(dy) < EPS && Math.abs(dz) < EPS) {
266
- return begin;
267
- }
261
+ let dir = vec3.sub(vec3.create(), end, begin);
262
+ let lenSq = vec3.squaredLength(dir);
268
263
 
269
- //计算斜率
270
- let u =
271
- (point[0] - begin[0]) * (begin[0] - end[0]) +
272
- (point[1] - begin[1]) * (begin[1] - end[1]) +
273
- (point[2] - begin[2]) * (begin[2] - end[2]);
274
- u = u / (Math.pow(dx, 2) + Math.pow(dy, 2) + Math.pow(dz, 2));
264
+ if (lenSq < EPS) {
265
+ // begin 和 end 太近了,视为同一点
266
+ return vec3.clone(begin);
267
+ }
268
+ vec3.normalize(dir, dir);
269
+ lenSq = vec3.squaredLength(dir);
270
+ const v = vec3.sub(vec3.create(), point, begin);
271
+ const u = vec3.dot(v, dir) / lenSq;
275
272
 
276
- return vec3.set(
277
- vec3.create(),
278
- begin[0] + u * dx,
279
- begin[1] + u * dy,
280
- begin[2] + u * dz
281
- );
273
+ const projected = vec3.scaleAndAdd(vec3.create(), begin, dir, u);
274
+ return projected;
282
275
  }
283
276
 
284
277
  /**
@@ -330,14 +323,23 @@ export function getPointToPointsMinIndex(point, points) {
330
323
  * @returns {number} 返回点到曲线的最近距离
331
324
  */
332
325
  export function getPointToPointsMinDist(point, points) {
333
- let dist = vec3.dist(point, points[0]);
334
- for (let i = 1; i < points.length; i++) {
335
- const iDist = vec3.dist(point, points[i]);
336
- if (iDist < dist) {
337
- dist = iDist;
326
+ if (!Array.isArray(points) || points.length === 0) {
327
+ return Infinity; // 或者可以抛错 throw new Error('Points array is empty');
328
+ }
329
+
330
+ let minDistSq = Infinity;
331
+ for (let i = 0; i < points.length; i++) {
332
+ const dx = point[0] - points[i][0];
333
+ const dy = point[1] - points[i][1];
334
+ const dz = point[2] - points[i][2];
335
+ const distSq = dx * dx + dy * dy + dz * dz;
336
+
337
+ if (distSq < minDistSq) {
338
+ minDistSq = distSq;
338
339
  }
339
340
  }
340
- return dist;
341
+
342
+ return Math.sqrt(minDistSq);
341
343
  }
342
344
 
343
345
 
@@ -374,8 +376,13 @@ export function getPointToLineDist(point, begin, end) {
374
376
  * @param {vec3} normal 平面法向量,要求单位化
375
377
  * @returns {number}
376
378
  */
377
- export function getPointToPlaneDist(point, origin, normal) {
378
- return vec3.dot(vec3.sub(vec3.create(), point, origin), normal);
379
+ export function getPointToPlaneDist(point, origin, normal) { // 如果法向量没有单位化,先进行归一化
380
+ if(vec3.squaredLength(normal) < 1e-8) {
381
+ throw('Normal vector is too small');
382
+ }
383
+ const normalizedNormal = vec3.create();
384
+ vec3.normalize(normalizedNormal, normal);
385
+ return vec3.dot(vec3.sub(vec3.create(), point, origin), normalizedNormal);
379
386
  }
380
387
 
381
388
 
@@ -545,6 +552,9 @@ export function deduplicatePolyData(data) {
545
552
  */
546
553
 
547
554
  export function getPointsCenter(points) {
555
+ if(!points.length) {
556
+ throw('no points input')
557
+ }
548
558
  let sumPoints = vec3.create();
549
559
  for (let i = 0; i < points.length; i++) {
550
560
  vec3.add(sumPoints, sumPoints, points[i]);
@@ -1016,4 +1026,21 @@ export function getCurvePointIndexByLength(points, dist, absDist = true) {
1016
1026
  const minSub = Math.min(...targetDistanceSubs);
1017
1027
  const targetIndex = targetDistanceSubs.findIndex(i => i === minSub)
1018
1028
  return targetIndex;
1029
+ }
1030
+
1031
+ /**
1032
+ * 复制一个polydata,只复制点和面的数据
1033
+ * @param {vtkPolyData} originalPolyData 原始的polydata
1034
+ * @returns {vtkPolyData} 新的polydata
1035
+ */
1036
+ export function clonePolyData(originalPolyData) {
1037
+ // 创建深拷贝的 PolyData 对象
1038
+ const clonedPolyData = vtkPolyData.newInstance();
1039
+ // 复制顶点数据
1040
+ const originalPoints = originalPolyData.getPoints().getData();
1041
+ clonedPolyData.getPoints().setData([...originalPoints]);
1042
+ // 复制单元数据
1043
+ const originalCells = originalPolyData.getPolys().getData();
1044
+ clonedPolyData.getPolys().setData([...originalCells]);
1045
+ return clonedPolyData;
1019
1046
  }
package/tsconfig.json CHANGED
@@ -5,5 +5,5 @@
5
5
  "emitDeclarationOnly": true,
6
6
  "outDir": "types" // 可选,定义.d.ts文件输出目录
7
7
  },
8
- "include": ["src/*.js", "src/vtkUtils.js",] // 指定包含的 JavaScript 文件路径
8
+ "include": ["src/*.js", "src/vtkUtils.js", "src/threeFont/font.js",] // 指定包含的 JavaScript 文件路径
9
9
  }