@kitware/vtk.js 34.0.0 → 34.2.0

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,520 @@
1
+ import { D as areEquals } from '../../../Common/Core/Math/index.js';
2
+
3
+ /**
4
+ * Computes UV coordinates for top/bottom faces
5
+ * @param {Array} vertices - The vertices array
6
+ * @param {Number} iA - First index
7
+ * @param {Number} iB - Second index
8
+ * @param {Number} iC - Third index
9
+ * @returns {Array} Array of UV coordinates
10
+ */
11
+ function computeFacesUV(vertices, iA, iB, iC) {
12
+ const ax = vertices[iA * 3];
13
+ const ay = vertices[iA * 3 + 1];
14
+ const bx = vertices[iB * 3];
15
+ const by = vertices[iB * 3 + 1];
16
+ const cx = vertices[iC * 3];
17
+ const cy = vertices[iC * 3 + 1];
18
+ return [[ax, ay], [bx, by], [cx, cy]];
19
+ }
20
+
21
+ /**
22
+ * Computes UV coordinates for side walls
23
+ * @param {Array} vertices - The vertices array
24
+ * @param {Number} iA - First index
25
+ * @param {Number} iB - Second index
26
+ * @param {Number} iC - Third index
27
+ * @param {Number} iD - Fourth index
28
+ * @returns {Array} Array of UV coordinates
29
+ */
30
+ function computeSidesUV(vertices, iA, iB, iC, iD) {
31
+ const ax = vertices[iA * 3];
32
+ const ay = vertices[iA * 3 + 1];
33
+ const az = vertices[iA * 3 + 2];
34
+ const bx = vertices[iB * 3];
35
+ const by = vertices[iB * 3 + 1];
36
+ const bz = vertices[iB * 3 + 2];
37
+ const cx = vertices[iC * 3];
38
+ const cy = vertices[iC * 3 + 1];
39
+ const cz = vertices[iC * 3 + 2];
40
+ const dx = vertices[iD * 3];
41
+ const dy = vertices[iD * 3 + 1];
42
+ const dz = vertices[iD * 3 + 2];
43
+
44
+ // Determine the best UV mapping direction based on geometry
45
+ if (Math.abs(ay - by) < Math.abs(ax - bx)) {
46
+ return [[ax, 1 - az], [bx, 1 - bz], [cx, 1 - cz], [dx, 1 - dz]];
47
+ }
48
+ return [[ay, 1 - az], [by, 1 - bz], [cy, 1 - cz], [dy, 1 - dz]];
49
+ }
50
+
51
+ /**
52
+ * Creates a shape path object with methods for path operations
53
+ * @returns {Object} A shape path object with methods for manipulating paths
54
+ */
55
+ function createShapePath() {
56
+ const curves = [];
57
+ const currentPoint = [0, 0];
58
+ const holes = [];
59
+ return {
60
+ curves,
61
+ currentPoint,
62
+ holes,
63
+ moveTo(x, y) {
64
+ currentPoint[0] = x;
65
+ currentPoint[1] = y;
66
+ },
67
+ lineTo(x, y) {
68
+ const start = [...currentPoint];
69
+ const end = [x, y];
70
+ curves.push({
71
+ curveType: 'LineCurve',
72
+ start,
73
+ end,
74
+ getPointAt(t) {
75
+ return [start[0] + t * (end[0] - start[0]), start[1] + t * (end[1] - start[1])];
76
+ },
77
+ getPoints(resolution) {
78
+ const points = [];
79
+ for (let i = 0; i <= resolution; i++) {
80
+ points.push(this.getPointAt(i / resolution));
81
+ }
82
+ return points;
83
+ }
84
+ });
85
+ currentPoint[0] = x;
86
+ currentPoint[1] = y;
87
+ },
88
+ quadraticCurveTo(cpX, cpY, x, y) {
89
+ const start = [...currentPoint];
90
+ const end = [x, y];
91
+ const cp = [cpX, cpY];
92
+ curves.push({
93
+ curveType: 'QuadraticBezierCurve',
94
+ cp,
95
+ start,
96
+ end,
97
+ getPointAt(t) {
98
+ const oneMinusT = 1 - t;
99
+ return [oneMinusT * oneMinusT * start[0] + 2 * oneMinusT * t * cp[0] + t * t * end[0], oneMinusT * oneMinusT * start[1] + 2 * oneMinusT * t * cp[1] + t * t * end[1]];
100
+ },
101
+ getPoints(resolution) {
102
+ const points = [];
103
+ for (let i = 0; i <= resolution; i++) {
104
+ points.push(this.getPointAt(i / resolution));
105
+ }
106
+ return points;
107
+ }
108
+ });
109
+ currentPoint[0] = x;
110
+ currentPoint[1] = y;
111
+ },
112
+ bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, x, y) {
113
+ const start = [...currentPoint];
114
+ const end = [x, y];
115
+ const cp1 = [cp1X, cp1Y];
116
+ const cp2 = [cp2X, cp2Y];
117
+ curves.push({
118
+ curveType: 'BezierCurve',
119
+ cp1,
120
+ cp2,
121
+ start,
122
+ end,
123
+ getPointAt(t) {
124
+ const oneMinusT = 1 - t;
125
+ return [oneMinusT * oneMinusT * oneMinusT * start[0] + 3 * oneMinusT * oneMinusT * t * cp1[0] + 3 * oneMinusT * t * t * cp2[0] + t * t * t * end[0], oneMinusT * oneMinusT * oneMinusT * start[1] + 3 * oneMinusT * oneMinusT * t * cp1[1] + 3 * oneMinusT * t * t * cp2[1] + t * t * t * end[1]];
126
+ },
127
+ getPoints(resolution) {
128
+ const points = [];
129
+ for (let i = 0; i <= resolution; i++) {
130
+ points.push(this.getPointAt(i / resolution));
131
+ }
132
+ return points;
133
+ }
134
+ });
135
+ currentPoint[0] = x;
136
+ currentPoint[1] = y;
137
+ },
138
+ /**
139
+ * Get points from the shape
140
+ * @param {*} divisions
141
+ * @returns
142
+ */
143
+ getPoints(divisions) {
144
+ let last;
145
+ const points = [];
146
+ for (let i = 0; i < curves.length; i++) {
147
+ const curve = curves[i];
148
+ let resolution = divisions;
149
+ if (curve.curveType === 'EllipseCurve') {
150
+ resolution = divisions * 2;
151
+ } else if (curve.curveType === 'LineCurve') {
152
+ resolution = 1;
153
+ }
154
+ const pts = curve.getPoints(resolution);
155
+ for (let j = 0; j < pts.length; j++) {
156
+ const point = pts[j];
157
+ // eslint-disable-next-line no-continue
158
+ if (last && areEquals(last, point)) continue;
159
+ points.push(point);
160
+ last = point;
161
+ }
162
+ }
163
+ return points;
164
+ },
165
+ /**
166
+ * Extract points from the shape
167
+ * @param {*} divisions
168
+ * @returns
169
+ */
170
+ extractPoints(divisions) {
171
+ const points = this.getPoints(divisions);
172
+ const holesPoints = this.holes.map(hole => hole.getPoints(divisions));
173
+ return {
174
+ shape: points,
175
+ holes: holesPoints
176
+ };
177
+ },
178
+ /**
179
+ * Defines if a given point is inside the polygon defines by the path
180
+ * @param {*} point
181
+ * @param {*} polygon
182
+ * @returns {boolean}
183
+ */
184
+ isPointInside(point, polygon) {
185
+ const x = point[0];
186
+ const y = point[1];
187
+ let isInside = false;
188
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
189
+ const xi = polygon[i][0];
190
+ const yi = polygon[i][1];
191
+ const xj = polygon[j][0];
192
+ const yj = polygon[j][1];
193
+ const intersect = yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi;
194
+ if (intersect) isInside = !isInside;
195
+ }
196
+ return isInside;
197
+ },
198
+ isIntersect(path) {
199
+ const pathA = this.getPoints(1, curves, false);
200
+ const pathB = path.getPoints(1);
201
+ return this.isPointInside(pathB[0], pathA);
202
+ }
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Calculates the bounding box size for a set of shapes
208
+ * @param {Array} shapes - Array of shape objects
209
+ * @param {Number} depth - Depth of the 3D text
210
+ * @param {Number} curveSegments - Number of segments for curved paths
211
+ * @returns {Object} Object with min and max point coordinates
212
+ */
213
+ function getBoundingSize(shapes, depth, curveSegments) {
214
+ const minPoint = [Infinity, Infinity, depth > 0 ? 0 : depth];
215
+ const maxPoint = [-Infinity, -Infinity, depth < 0 ? 0 : depth];
216
+ for (let i = 0; i < shapes.length; i++) {
217
+ const shape = shapes[i];
218
+ const shapePoints = shape.extractPoints(curveSegments);
219
+ for (let j = 0; j < shapePoints.shape.length; j++) {
220
+ const p = shapePoints.shape[j];
221
+ if (p[0] < minPoint[0]) minPoint[0] = p[0];
222
+ if (p[1] < minPoint[1]) minPoint[1] = p[1];
223
+ if (p[0] > maxPoint[0]) maxPoint[0] = p[0];
224
+ if (p[1] > maxPoint[1]) maxPoint[1] = p[1];
225
+ }
226
+ }
227
+ return {
228
+ min: minPoint,
229
+ max: maxPoint
230
+ };
231
+ }
232
+
233
+ /**
234
+ * Removes duplicate end points in a points array
235
+ * @param {Array} points - Array of points
236
+ */
237
+ function removeDupEndPoints(points) {
238
+ const l = points.length;
239
+ const isEqual = areEquals(points[l - 1], points[0]);
240
+ if (l > 2 && isEqual) {
241
+ points.pop();
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Checks if the points are in a clockwise order
247
+ * @param {Array} points - Array of points [x, y]
248
+ * @returns {Boolean} True if points are in clockwise order
249
+ */
250
+ function isClockWise(points) {
251
+ let sum = 0.0;
252
+ const n = points.length;
253
+ for (let p = n - 1, q = 0; q < n; p = q++) {
254
+ sum += points[p][0] * points[q][1] - points[q][0] * points[p][1];
255
+ }
256
+ // Positive signed area means counter-clockwise, so return true if area is negative
257
+ return sum * 0.5 < 0;
258
+ }
259
+
260
+ /**
261
+ * Computes the bevel vector for a point in a shape.
262
+ * @param {Array} pt - Current point [x, y]
263
+ * @param {Array} prev - Previous point [x, y]
264
+ * @param {Array} next - Next point [x, y]
265
+ * @returns {Array} Normalized bevel vector [x, y]
266
+ */
267
+ function computeBevelVector(pt, prev, next) {
268
+ const vPrevX = pt[0] - prev[0];
269
+ const vPrevY = pt[1] - prev[1];
270
+ const vNextX = next[0] - pt[0];
271
+ const vNextY = next[1] - pt[1];
272
+
273
+ // Check collinearity
274
+ const cross = vPrevX * vNextY - vPrevY * vNextX;
275
+ let tx;
276
+ let ty;
277
+ let shrinkBy;
278
+ if (Math.abs(cross) > Number.EPSILON) {
279
+ // non‐collinear
280
+ const lenPrev = Math.hypot(vPrevX, vPrevY);
281
+ const lenNext = Math.hypot(vNextX, vNextY);
282
+
283
+ // shift prev and next perpendicular to themselves
284
+ const prevShiftX = prev[0] - vPrevY / lenPrev;
285
+ const prevShiftY = prev[1] + vPrevX / lenPrev;
286
+ const nextShiftX = next[0] - vNextY / lenNext;
287
+ const nextShiftY = next[1] + vNextX / lenNext;
288
+
289
+ // intersection factor
290
+ const sf = ((nextShiftX - prevShiftX) * vNextY - (nextShiftY - prevShiftY) * vNextX) / (vPrevX * vNextY - vPrevY * vNextX);
291
+ tx = prevShiftX + vPrevX * sf - pt[0];
292
+ ty = prevShiftY + vPrevY * sf - pt[1];
293
+ const lensq = tx * tx + ty * ty;
294
+ if (lensq <= 2) {
295
+ return [tx, ty];
296
+ }
297
+ shrinkBy = Math.sqrt(lensq / 2);
298
+ } else {
299
+ // collinear or opposing
300
+ const sameDir = vPrevX > 0 && vNextX > 0 || vPrevX < 0 && vNextX < 0 || Math.sign(vPrevY) === Math.sign(vNextY);
301
+ if (sameDir) {
302
+ // perpendicular to prev
303
+ tx = -vPrevY;
304
+ ty = vPrevX;
305
+ shrinkBy = Math.hypot(vPrevX, vPrevY);
306
+ } else {
307
+ // just offset along prev
308
+ tx = vPrevX;
309
+ ty = vPrevY;
310
+ shrinkBy = Math.sqrt((vPrevX * vPrevX + vPrevY * vPrevY) / 2);
311
+ }
312
+ }
313
+ return [tx / shrinkBy, ty / shrinkBy];
314
+ }
315
+
316
+ /**
317
+ * Triangulates a shape with holes
318
+ * @param {Array} contour - Array of contour points
319
+ * @param {Array} holes - Array of hole paths
320
+ * @returns {Array} Array of triangle faces as arrays of indices
321
+ */
322
+ function triangulateShape(earcut, contour, holes) {
323
+ const faces = [];
324
+ const vertices = [];
325
+ const holeIndices = [];
326
+ removeDupEndPoints(contour);
327
+ for (let i = 0; i < contour.length; i++) {
328
+ vertices.push(contour[i][0], contour[i][1]);
329
+ }
330
+ let holeIndex = contour.length;
331
+ holes.forEach(removeDupEndPoints);
332
+ for (let i = 0; i < holes.length; i++) {
333
+ holeIndices.push(holeIndex);
334
+ const hole = holes[i];
335
+ holeIndex += hole.length;
336
+ for (let j = 0; j < hole.length; j++) {
337
+ vertices.push(hole[j][0], hole[j][1]);
338
+ }
339
+ }
340
+ const triangles = earcut(vertices, holeIndices);
341
+ for (let i = 0; i < triangles.length; i += 3) {
342
+ faces.push(triangles.slice(i, i + 3));
343
+ }
344
+ return faces;
345
+ }
346
+
347
+ /**
348
+ * Scales a point along a vector
349
+ * @param {Array} pt - Point to scale [x, y]
350
+ * @param {Array} vec - Direction vector [x, y]
351
+ * @param {Number} size - Scale amount
352
+ * @returns {Array} Scaled point [x, y]
353
+ */
354
+ function scalePoint(pt, vec, size) {
355
+ const rt = [pt[0], pt[1]];
356
+ rt[0] += vec[0] * size;
357
+ rt[1] += vec[1] * size;
358
+ return rt;
359
+ }
360
+
361
+ /**
362
+ * Creates triangle faces with specified indices
363
+ * @param {Array} layers - The layers array with vertex positions
364
+ * @param {Number} a - First index
365
+ * @param {Number} b - Second index
366
+ * @param {Number} c - Third index
367
+ * @param {Array} verticesArray - The output vertices array
368
+ * @param {Array} uvArray - The output UV array
369
+ * @param {Array} colorArray - The output color array
370
+ * @param {Array} color - The color [r, g, b]
371
+ * @param {Boolean} perFaceUV - Flag for per-face UV mapping
372
+ * @param {Number} faceIndex - Index of the face for UV mapping
373
+ */
374
+ function addTriangle(layers, a, b, c, verticesArray, uvArray, colorArray, color) {
375
+ const tri = [a, c, b];
376
+ tri.forEach(i => {
377
+ verticesArray.push(layers[i * 3], layers[i * 3 + 1], layers[i * 3 + 2]);
378
+ });
379
+ const nextIndex = verticesArray.length / 3;
380
+ const uvs = computeFacesUV(verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1);
381
+
382
+ // Add each UV coordinate pair to the array
383
+ uvs.forEach(uv => {
384
+ uvArray.push(uv[0], uv[1]);
385
+ });
386
+ if (colorArray && color) {
387
+ for (let i = 0; i < 3; ++i) colorArray.push(color[0], color[1], color[2]);
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Creates quad faces with specified indices
393
+ * @param {Array} layers - The layers array with vertex positions
394
+ * @param {Number} a - First index
395
+ * @param {Number} b - Second index
396
+ * @param {Number} c - Third index
397
+ * @param {Number} d - Fourth index
398
+ * @param {Array} verticesArray - The output vertices array
399
+ * @param {Array} uvArray - The output UV array
400
+ * @param {Array} colorArray - The output color array
401
+ * @param {Array} color - The color [r, g, b]
402
+ */
403
+ function addQuad(layers, a, b, c, d, verticesArray, uvArray, colorArray, color) {
404
+ const quad = [a, d, b, b, d, c];
405
+ quad.forEach(i => verticesArray.push(layers[i * 3], layers[i * 3 + 1], layers[i * 3 + 2]));
406
+ const nextIndex = verticesArray.length / 3;
407
+ const uvs = computeSidesUV(verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1);
408
+
409
+ // UV coordinates for both triangles of the quad
410
+ // First triangle
411
+ uvArray.push(uvs[0][0], uvs[0][1]);
412
+ uvArray.push(uvs[1][0], uvs[1][1]);
413
+ uvArray.push(uvs[3][0], uvs[3][1]);
414
+
415
+ // Second triangle
416
+ uvArray.push(uvs[1][0], uvs[1][1]);
417
+ uvArray.push(uvs[2][0], uvs[2][1]);
418
+ uvArray.push(uvs[3][0], uvs[3][1]);
419
+ if (colorArray && color) {
420
+ for (let i = 0; i < 6; ++i) colorArray.push(color[0], color[1], color[2]);
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Creates the faces for the top and bottom of the 3D text
426
+ * @param {Array} layers - The layers array with vertex positions
427
+ * @param {Array} faces - The triangulated faces
428
+ * @param {Number} vlen - The number of vertices
429
+ * @param {Number} steps - The number of steps
430
+ * @param {Boolean} bevelEnabled - Whether bevel is enabled
431
+ * @param {Number} bevelSegments - Number of bevel segments
432
+ * @param {Array} verticesArray - The output vertices array
433
+ * @param {Array} uvArray - The output UV array
434
+ */
435
+ function buildLidFaces(layers, faces, vlen, steps, bevelEnabled, bevelSegments, verticesArray, uvArray, colorArray, color) {
436
+ if (bevelEnabled) {
437
+ let layer = 0;
438
+ let offset = vlen * layer; // Bottom faces
439
+ faces.forEach(_ref => {
440
+ let [a, b, c] = _ref;
441
+ addTriangle(layers, c + offset, b + offset, a + offset, verticesArray, uvArray, colorArray, color);
442
+ });
443
+ layer = steps + bevelSegments * 2;
444
+ offset = vlen * layer;
445
+
446
+ // Top faces
447
+ faces.forEach(_ref2 => {
448
+ let [a, b, c] = _ref2;
449
+ addTriangle(layers, a + offset, b + offset, c + offset, verticesArray, uvArray, colorArray, color);
450
+ });
451
+ } else {
452
+ // Bottom faces
453
+ faces.forEach(_ref3 => {
454
+ let [a, b, c] = _ref3;
455
+ addTriangle(layers, c, b, a, verticesArray, uvArray, colorArray, color);
456
+ });
457
+
458
+ // Top faces
459
+ const offset = vlen * steps;
460
+ faces.forEach(_ref4 => {
461
+ let [a, b, c] = _ref4;
462
+ addTriangle(layers, a + offset, b + offset, c + offset, verticesArray, uvArray, colorArray, color);
463
+ });
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Creates side walls for contour or hole
469
+ * @param {Array} layers - The layers array
470
+ * @param {Array} contour - The contour points
471
+ * @param {Number} layerOffset - Offset for the layer
472
+ * @param {Number} vlen - The number of vertices
473
+ * @param {Number} steps - The number of steps
474
+ * @param {Number} bevelSegments - The number of bevel segments
475
+ * @param {Array} verticesArray - The output vertices array
476
+ * @param {Array} uvArray - The output UV array
477
+ */
478
+ function buildWalls(layers, contour, layerOffset, vlen, steps, bevelSegments, verticesArray, uvArray, colorArray, color) {
479
+ const totalLayers = steps + bevelSegments * 2;
480
+ for (let i = 0; i < contour.length; i++) {
481
+ const j = i;
482
+ const k = i === 0 ? contour.length - 1 : i - 1;
483
+ for (let s = 0; s < totalLayers; s++) {
484
+ const slen1 = vlen * s;
485
+ const slen2 = vlen * (s + 1);
486
+ const a = layerOffset + j + slen1;
487
+ const b = layerOffset + k + slen1;
488
+ const c = layerOffset + k + slen2;
489
+ const d = layerOffset + j + slen2;
490
+ addQuad(layers, a, b, c, d, verticesArray, uvArray, colorArray, color);
491
+ }
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Builds the side faces of the 3D text
497
+ * @param {Array} layers - The layers array
498
+ * @param {Array} contour - The contour points
499
+ * @param {Array} holes - The holes
500
+ * @param {Number} vlen - The number of vertices
501
+ * @param {Number} steps - The number of steps
502
+ * @param {Number} bevelSegments - The number of bevel segments
503
+ * @param {Array} verticesArray - The output vertices array
504
+ * @param {Array} uvArray - The output UV array
505
+ */
506
+ function buildSideFaces(layers, contour, holes, vlen, steps, bevelSegments, verticesArray, uvArray, colorArray, color) {
507
+ let layerOffset = 0;
508
+ // Create contour walls
509
+ buildWalls(layers, contour, layerOffset, vlen, steps, bevelSegments, verticesArray, uvArray, colorArray, color);
510
+ layerOffset += contour.length;
511
+
512
+ // Create hole walls
513
+ for (let i = 0; i < holes.length; i++) {
514
+ const ahole = holes[i];
515
+ buildWalls(layers, ahole, layerOffset, vlen, steps, bevelSegments, verticesArray, uvArray, colorArray, color);
516
+ layerOffset += ahole.length;
517
+ }
518
+ }
519
+
520
+ export { addQuad, addTriangle, buildLidFaces, buildSideFaces, buildWalls, computeBevelVector, computeFacesUV, computeSidesUV, createShapePath, getBoundingSize, isClockWise, scalePoint, triangulateShape };
@@ -0,0 +1,183 @@
1
+ import { vtkAlgorithm, vtkObject } from './../../interfaces';
2
+ import { Nullable, RGBColor } from './../../types';
3
+
4
+ export interface IVectorTextInitialValues {
5
+ fontSize?: number;
6
+ text?: string;
7
+ depth?: number;
8
+ steps?: number;
9
+ bevelEnabled?: boolean;
10
+ curveSegments?: number;
11
+ bevelThickness?: number;
12
+ bevelSize?: number;
13
+ bevelOffset?: number;
14
+ bevelSegments?: number;
15
+ font?: any;
16
+ earcut?: any; // Earcut module for triangulation
17
+ perLetterFaceColors?: (letterIndex: number) => [number, number, number];
18
+ }
19
+
20
+ type vtkVectorTextBase = vtkObject & vtkAlgorithm;
21
+
22
+ export interface vtkVectorText extends vtkVectorTextBase {
23
+ /**
24
+ * Returns whether beveling is enabled.
25
+ */
26
+ getBevelEnabled(): boolean;
27
+
28
+ /**
29
+ * Returns the number of segments used for the bevel geometry.
30
+ */
31
+ getBevelSegments(): number;
32
+
33
+ /**
34
+ * Returns the size of the bevel.
35
+ */
36
+ getBevelSize(): number;
37
+
38
+ /**
39
+ * Returns the thickness of the bevel.
40
+ */
41
+ getBevelThickness(): number;
42
+
43
+ /**
44
+ * Returns the offset of the bevel.
45
+ */
46
+ getBevelOffset(): number;
47
+
48
+ /**
49
+ * Returns the number of curve segments used for the text geometry.
50
+ */
51
+ getCurveSegments(): number;
52
+
53
+ /**
54
+ * Returns the extrusion depth of the text.
55
+ */
56
+ getDepth(): number;
57
+
58
+ /**
59
+ * Returns the current font size.
60
+ */
61
+ getFontSize(): number;
62
+
63
+ /**
64
+ * Returns the number of steps used for the text geometry.
65
+ */
66
+ getSteps(): number;
67
+
68
+ /**
69
+ * Returns the current text string.
70
+ */
71
+ getText(): string;
72
+
73
+ /**
74
+ * Gets or sets the per-letter face color function.
75
+ * @param fn - Function mapping letter index to [r,g,b] color.
76
+ */
77
+ getPerLetterFaceColors(): Nullable<(letterIndex: number) => RGBColor>;
78
+
79
+ /**
80
+ * Enables or disables beveling.
81
+ * @param bevelEnabled - True to enable beveling, false to disable.
82
+ */
83
+ setBevelEnabled(bevelEnabled: boolean): boolean;
84
+
85
+ /**
86
+ * Sets the number of segments used for the bevel geometry.
87
+ * @param bevelSegments - The number of bevel segments.
88
+ */
89
+ setBevelSegments(bevelSegments: number): boolean;
90
+
91
+ /**
92
+ * Sets the size of the bevel.
93
+ * @param bevelSize - The bevel size.
94
+ */
95
+ setBevelSize(bevelSize: number): boolean;
96
+
97
+ /**
98
+ * Sets the thickness of the bevel.
99
+ * @param bevelThickness - The bevel thickness.
100
+ */
101
+ setBevelThickness(bevelThickness: number): boolean;
102
+
103
+ /**
104
+ * Sets the offset of the bevel.
105
+ * @param bevelOffset - The bevel offset.
106
+ */
107
+ setBevelOffset(bevelOffset: number): boolean;
108
+
109
+ /**
110
+ * Sets the number of curve segments used for the text geometry.
111
+ * @param curveSegments - The number of curve segments.
112
+ */
113
+ setCurveSegments(curveSegments: number): boolean;
114
+
115
+ /**
116
+ * Sets the extrusion depth of the text.
117
+ * @param depth - The new depth value.
118
+ */
119
+ setDepth(depth: number): boolean;
120
+
121
+ /**
122
+ * Sets the font object used for rendering the text.
123
+ * This should be a parsed font object from opentype.js.
124
+ * @param font - The font object.
125
+ */
126
+ setFont(font: any): boolean;
127
+
128
+ /**
129
+ * Sets the font size.
130
+ * @param fontSize - The new font size.
131
+ */
132
+ setFontSize(fontSize: number): boolean;
133
+
134
+ /**
135
+ * Sets the number of steps used for the text geometry.
136
+ * @param steps - The number of steps.
137
+ */
138
+ setSteps(steps: number): boolean;
139
+
140
+ /**
141
+ * Sets the text string.
142
+ * @param text - The new text to display.
143
+ */
144
+ setText(text: string): boolean;
145
+
146
+ /**
147
+ * Sets the per-letter face color function.
148
+ * @param fn - Function mapping letter index to [r,g,b] color.
149
+ */
150
+ setPerLetterFaceColors(fn: (letterIndex: number) => RGBColor): boolean;
151
+ }
152
+
153
+ /**
154
+ * Method use to decorate a given object (publicAPI+model) with vtkVectorText characteristics.
155
+ *
156
+ * @param publicAPI object on which methods will be bounds (public)
157
+ * @param model object on which data structure will be bounds (protected)
158
+ * @param {IVectorTextInitialValues} [initialValues] (default: {})
159
+ */
160
+ export function extend(
161
+ publicAPI: object,
162
+ model: object,
163
+ initialValues?: IVectorTextInitialValues
164
+ ): void;
165
+
166
+ /**
167
+ * Method use to create a new instance of vtkVectorText
168
+ * @param {IVectorTextInitialValues} [initialValues] for pre-setting some of its content
169
+ */
170
+ export function newInstance(
171
+ initialValues?: IVectorTextInitialValues
172
+ ): vtkVectorText;
173
+
174
+ /**
175
+ * vtkVectorText generates vtkPolyData from an input string.
176
+ * The TTF file needs to be parsed using opentype.js and then passed to
177
+ * vtkVectorText via the setFont method.
178
+ */
179
+ export declare const vtkVectorText: {
180
+ newInstance: typeof newInstance;
181
+ extend: typeof extend;
182
+ };
183
+ export default vtkVectorText;
@@ -0,0 +1,397 @@
1
+ import { m as macro } from '../../macros2.js';
2
+ import { s as subtract } from '../../Common/Core/Math/index.js';
3
+ import vtkPolyData from '../../Common/DataModel/PolyData.js';
4
+ import vtkDataArray from '../../Common/Core/DataArray.js';
5
+ import vtkCellArray from '../../Common/Core/CellArray.js';
6
+ import { isClockWise, getBoundingSize, triangulateShape, computeBevelVector, scalePoint, buildLidFaces, buildSideFaces, createShapePath } from './VectorText/Utils.js';
7
+
8
+ const {
9
+ vtkErrorMacro,
10
+ vtkWarningMacro
11
+ } = macro;
12
+
13
+ // ----------------------------------------------------------------------------
14
+ // vtkVectorText methods
15
+ // ----------------------------------------------------------------------------
16
+
17
+ function vtkVectorText(publicAPI, model) {
18
+ // Set our className
19
+ model.classHierarchy.push('vtkVectorText');
20
+
21
+ // -------------------------------------------------------------------------
22
+ // Private methods
23
+ // -------------------------------------------------------------------------
24
+
25
+ /**
26
+ * Process a shape into 3D geometry
27
+ * @param {Object} shape - The shape to process
28
+ * @param {Array} offsetSize - The offset size for positioning the shape
29
+ * @param {Array} letterColor - The color for the shape
30
+ */
31
+ function addShape(shape, offsetSize, letterColor) {
32
+ // extract contour + holes, offset them
33
+ const curveSegments = model.curveSegments;
34
+ const steps = model.steps;
35
+ const depth = model.depth;
36
+
37
+ // Calculate bevel parameters
38
+ const bevelEnabled = model.bevelEnabled;
39
+ let bevelThickness = model.bevelThickness;
40
+ let bevelSize = bevelThickness - 0.1;
41
+ let bevelOffset = model.bevelOffset;
42
+ let bevelSegments = model.bevelSegments;
43
+ if (!bevelEnabled) {
44
+ bevelSegments = 0;
45
+ bevelThickness = 0;
46
+ bevelSize = 0;
47
+ bevelOffset = 0;
48
+ }
49
+
50
+ // Extract points from shape
51
+ const shapePoints = shape.extractPoints(curveSegments);
52
+ let vertices = shapePoints.shape;
53
+ const holes = shapePoints.holes;
54
+
55
+ // Offset points to the correct position
56
+ vertices.forEach(p => {
57
+ p[0] += offsetSize[0];
58
+ p[1] += offsetSize[1];
59
+ });
60
+ holes.forEach(hole => {
61
+ hole.forEach(p => {
62
+ p[0] += offsetSize[0];
63
+ p[1] += offsetSize[1];
64
+ });
65
+ });
66
+
67
+ // Check if we have enough points to create a shape
68
+ if (vertices.length < 3) {
69
+ vtkWarningMacro('Not enough points to create a shape');
70
+ return;
71
+ }
72
+
73
+ // Triangulate the shape
74
+ const faces = triangulateShape(model.earcut, vertices, holes);
75
+ const contour = vertices;
76
+
77
+ // Combine all vertices (contour and holes)
78
+ vertices = [...vertices, ...holes.flat()];
79
+ const vlen = vertices.length;
80
+
81
+ // Calculate bevel vectors for the contour
82
+ const contourMovements = [];
83
+ for (let i = 0, j = contour.length - 1, k = i + 1; i < contour.length; i++, j++, k++) {
84
+ if (j === contour.length) j = 0;
85
+ if (k === contour.length) k = 0;
86
+ contourMovements[i] = computeBevelVector(contour[i], contour[j], contour[k]);
87
+ }
88
+
89
+ // Calculate bevel vectors for the holes
90
+ const holesMovements = [];
91
+ let oneHoleMovements;
92
+ let verticesMovements = [...contourMovements];
93
+ for (let h = 0, hl = holes.length; h < hl; h++) {
94
+ const ahole = holes[h];
95
+ oneHoleMovements = [];
96
+ for (let i = 0, j = ahole.length - 1, k = i + 1; i < ahole.length; i++, j++, k++) {
97
+ if (j === ahole.length) j = 0;
98
+ if (k === ahole.length) k = 0;
99
+ oneHoleMovements[i] = computeBevelVector(ahole[i], ahole[j], ahole[k]);
100
+ }
101
+ holesMovements.push(oneHoleMovements);
102
+ verticesMovements = [...verticesMovements, ...oneHoleMovements];
103
+ }
104
+
105
+ // Generate all the layers of points
106
+ const layers = [];
107
+
108
+ // Bottom bevel layers
109
+ for (let b = 0; b < bevelSegments; b++) {
110
+ const t = b / bevelSegments;
111
+ const z = bevelThickness * Math.cos(t * Math.PI / 2);
112
+ const bs = bevelSize * Math.sin(t * Math.PI / 2) + bevelOffset;
113
+
114
+ // Add points for contour and holes
115
+ for (let i = 0; i < contour.length; i++) {
116
+ const vert = scalePoint(contour[i], contourMovements[i], bs);
117
+ layers.push(vert[0], vert[1], -z + offsetSize[2]);
118
+ }
119
+ for (let h = 0, hl = holes.length; h < hl; h++) {
120
+ const ahole = holes[h];
121
+ oneHoleMovements = holesMovements[h];
122
+ for (let i = 0; i < ahole.length; i++) {
123
+ const vert = scalePoint(ahole[i], oneHoleMovements[i], bs);
124
+ layers.push(vert[0], vert[1], -z + offsetSize[2]);
125
+ }
126
+ }
127
+ }
128
+
129
+ // Base layer (z=0)
130
+ const bs = bevelSize + bevelOffset;
131
+ for (let i = 0; i < vlen; i++) {
132
+ const vert = bevelEnabled ? scalePoint(vertices[i], verticesMovements[i], bs) : vertices[i];
133
+ layers.push(vert[0], vert[1], 0 + offsetSize[2]);
134
+ }
135
+
136
+ // Middle layers
137
+ for (let s = 1; s <= steps; s++) {
138
+ for (let i = 0; i < vlen; i++) {
139
+ const vert = bevelEnabled ? scalePoint(vertices[i], verticesMovements[i], bs) : vertices[i];
140
+ layers.push(vert[0], vert[1], depth / steps * s + offsetSize[2]);
141
+ }
142
+ }
143
+
144
+ // Top bevel layers
145
+ for (let b = bevelSegments - 1; b >= 0; b--) {
146
+ const t = b / bevelSegments;
147
+ const z = bevelThickness * Math.cos(t * Math.PI / 2);
148
+ const topBevelSize = bevelSize * Math.sin(t * Math.PI / 2) + bevelOffset;
149
+ for (let i = 0, il = contour.length; i < il; i++) {
150
+ const vert = scalePoint(contour[i], contourMovements[i], topBevelSize);
151
+ layers.push(vert[0], vert[1], depth + z + offsetSize[2]);
152
+ }
153
+ for (let h = 0, hl = holes.length; h < hl; h++) {
154
+ const ahole = holes[h];
155
+ oneHoleMovements = holesMovements[h];
156
+ for (let i = 0, il = ahole.length; i < il; i++) {
157
+ const vert = scalePoint(ahole[i], oneHoleMovements[i], topBevelSize);
158
+ layers.push(vert[0], vert[1], depth + z + offsetSize[2]);
159
+ }
160
+ }
161
+ }
162
+
163
+ // Build all the faces
164
+ buildLidFaces(layers, faces, vlen, steps, bevelEnabled, bevelSegments, model.verticesArray, model.uvArray, model.colorArray, letterColor);
165
+ buildSideFaces(layers, contour, holes, vlen, steps, bevelSegments, model.verticesArray, model.uvArray, model.colorArray, letterColor);
166
+ }
167
+
168
+ /**
169
+ * Creates shape paths from the font and text
170
+ */
171
+ function buildShape() {
172
+ model.shapes = [];
173
+ if (!model.font || !model.text) {
174
+ return;
175
+ }
176
+ const path = model.font.getPath(model.text, 0, 0, model.fontSize);
177
+ if (!path || !path.commands || !path.commands.length) {
178
+ return;
179
+ }
180
+ let first;
181
+ let shapePath = createShapePath();
182
+ const commands = path.commands;
183
+ for (let i = 0; i < commands.length; i++) {
184
+ const command = commands[i];
185
+
186
+ // start a fresh shape if the previous one was closed
187
+ shapePath = shapePath || createShapePath();
188
+ switch (command.type) {
189
+ case 'M':
190
+ // Move to
191
+ shapePath.moveTo(command.x, -command.y);
192
+ first = command;
193
+ break;
194
+ case 'L':
195
+ // Line to
196
+ shapePath.lineTo(command.x, -command.y);
197
+ break;
198
+ case 'C':
199
+ // Cubic bezier curve
200
+ shapePath.bezierCurveTo(command.x1, -command.y1, command.x2, -command.y2, command.x, -command.y);
201
+ break;
202
+ case 'Q':
203
+ // Quadratic bezier curve
204
+ shapePath.quadraticCurveTo(command.x1, -command.y1, command.x, -command.y);
205
+ break;
206
+ case 'Z':
207
+ // Close path
208
+ // Close the contour
209
+ shapePath.lineTo(first.x, -first.y);
210
+
211
+ // Determine if this path is a clockwise contour (shape) or a counter-clockwise hole
212
+ if (isClockWise(shapePath.getPoints(1))) {
213
+ model.shapes.push(shapePath);
214
+ } else {
215
+ // Find which shape this hole belongs to
216
+ for (let j = 0; j < model.shapes.length; j++) {
217
+ const shape = model.shapes[j];
218
+ if (shape.isIntersect(shapePath)) {
219
+ shape.holes.push(shapePath);
220
+ break;
221
+ }
222
+ }
223
+ }
224
+
225
+ // Mark for restart on next iteration
226
+ shapePath = null;
227
+ break;
228
+ default:
229
+ console.warn(`Unknown path command: ${command.type}`);
230
+ break;
231
+ }
232
+ }
233
+
234
+ // If there's an unclosed shape, add it
235
+ if (shapePath) {
236
+ model.shapes.push(shapePath);
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Creates a vtkPolyData from the processed shapes
242
+ * @returns {Object} vtkPolyData instance
243
+ */
244
+ function buildPolyData() {
245
+ model.verticesArray = [];
246
+ model.uvArray = [];
247
+ model.colorArray = [];
248
+ const polyData = vtkPolyData.newInstance();
249
+ const cells = vtkCellArray.newInstance();
250
+ const pointData = polyData.getPointData();
251
+
252
+ // Calculate the bounding box to center the text
253
+ const boundingSize = getBoundingSize(model.shapes, model.depth, model.curveSegments);
254
+ const offsetSize = [0, 0, 0];
255
+ subtract(boundingSize.min, boundingSize.max, offsetSize);
256
+
257
+ // Process each shape
258
+ let letterIndex = 0;
259
+ model.shapes.forEach(shape => {
260
+ let color = null;
261
+ if (typeof model.perLetterFaceColors === 'function') {
262
+ color = model.perLetterFaceColors(letterIndex) || [1, 1, 1];
263
+ }
264
+ addShape(shape, offsetSize, color);
265
+ letterIndex++;
266
+ });
267
+
268
+ // Create triangle indices
269
+ const vertexCount = model.verticesArray.length / 3;
270
+ const indices = [];
271
+
272
+ // Generate indices for triangles
273
+ for (let i = 0; i < vertexCount; i += 3) {
274
+ indices.push(i, i + 2, i + 1);
275
+ }
276
+
277
+ // Create cells for polydata
278
+ const cellSize = indices.length;
279
+ cells.resize(cellSize + cellSize / 3); // Allocate space for cells (+1 for size per cell)
280
+
281
+ // Add triangles to cells
282
+ for (let i = 0; i < indices.length; i += 3) {
283
+ cells.insertNextCell([indices[i], indices[i + 1], indices[i + 2]]);
284
+ }
285
+ polyData.setPolys(cells);
286
+
287
+ // Set points (vertices)
288
+ polyData.getPoints().setData(new Float32Array(model.verticesArray), 3);
289
+
290
+ // Set texture coordinates
291
+ const da = vtkDataArray.newInstance({
292
+ numberOfComponents: 2,
293
+ values: new Float32Array(model.uvArray),
294
+ name: 'TEXCOORD_0'
295
+ });
296
+ pointData.addArray(da);
297
+ pointData.setActiveTCoords(da.getName());
298
+
299
+ // Set color array if present
300
+ if (model.colorArray && model.colorArray.length) {
301
+ const ca = vtkDataArray.newInstance({
302
+ numberOfComponents: 3,
303
+ values: new Float32Array(model.colorArray),
304
+ name: 'Colors'
305
+ });
306
+ pointData.addArray(ca);
307
+ pointData.setActiveScalars(ca.getName());
308
+ }
309
+ return polyData;
310
+ }
311
+
312
+ // -------------------------------------------------------------------------
313
+ // Public methods
314
+ // -------------------------------------------------------------------------
315
+
316
+ /**
317
+ * Handles the request to generate vector text data
318
+ * @param {Object} inData - Input data (not used)
319
+ * @param {Object} outData - Output data target
320
+ */
321
+ publicAPI.requestData = (inData, outData) => {
322
+ if (!model.font) {
323
+ vtkErrorMacro('Font object not set, make sure the TTF file is parsed using opentype.js.');
324
+ return;
325
+ }
326
+ if (!model.text) {
327
+ vtkErrorMacro('Text not set. Cannot generate vector text.');
328
+ return;
329
+ }
330
+ buildShape();
331
+ outData[0] = buildPolyData();
332
+ };
333
+ }
334
+
335
+ // ----------------------------------------------------------------------------
336
+ // Object factory
337
+ // ----------------------------------------------------------------------------
338
+
339
+ /**
340
+ * Default values for the VectorText model
341
+ * shapes: Array to store shape paths
342
+ * verticesArray: Array of vertex coordinates
343
+ * uvArray: Array of texture coordinates
344
+ * font: Font object (from opentype.js)
345
+ * earcut: Earcut module for triangulation
346
+ * fontSize: Font size in points
347
+ * depth: Depth of the extruded text
348
+ * steps: Number of steps in extrusion (for curved surfaces)
349
+ * bevelEnabled: Whether to add beveled edges
350
+ * curveSegments: Number of segments for curved paths
351
+ * bevelThickness: Thickness of the bevel
352
+ * bevelSize: Size of the bevel
353
+ * bevelOffset: Offset of the bevel
354
+ * bevelSegments: Number of segments in the bevel
355
+ * text: The text to render
356
+ * perLetterFaceColors: Function to get per-letter face colors
357
+ */
358
+ const DEFAULT_VALUES = {
359
+ shapes: [],
360
+ verticesArray: [],
361
+ uvArray: [],
362
+ font: null,
363
+ earcut: null,
364
+ // Earcut module for triangulation
365
+ fontSize: 10,
366
+ depth: 1,
367
+ steps: 1,
368
+ bevelEnabled: false,
369
+ curveSegments: 12,
370
+ bevelThickness: 0.2,
371
+ bevelSize: 0.1,
372
+ bevelOffset: 0,
373
+ bevelSegments: 1,
374
+ text: null,
375
+ perLetterFaceColors: null // (letterIndex: number) => [r,g,b]
376
+ };
377
+
378
+ function extend(publicAPI, model) {
379
+ let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
380
+ Object.assign(model, DEFAULT_VALUES, initialValues);
381
+
382
+ // Object methods
383
+ macro.obj(publicAPI, model);
384
+ macro.algo(publicAPI, model, 0, 1);
385
+
386
+ // Build VTK API with automatic getters/setters
387
+ macro.setGet(publicAPI, model, ['fontSize', 'text', 'depth', 'steps', 'bevelEnabled', 'curveSegments', 'bevelThickness', 'bevelSize', 'bevelOffset', 'bevelSegments', 'perLetterFaceColors']);
388
+ macro.set(publicAPI, model, ['font']);
389
+ vtkVectorText(publicAPI, model);
390
+ }
391
+ const newInstance = macro.newInstance(extend, 'vtkVectorText');
392
+ var vtkVector = {
393
+ newInstance,
394
+ extend
395
+ };
396
+
397
+ export { vtkVector as default, extend, newInstance };
package/Rendering/Core.js CHANGED
@@ -38,6 +38,7 @@ import vtkSkybox from './Core/Skybox.js';
38
38
  import vtkSphereMapper from './Core/SphereMapper.js';
39
39
  import vtkStickMapper from './Core/StickMapper.js';
40
40
  import vtkTexture from './Core/Texture.js';
41
+ import vtkVector from './Core/VectorText.js';
41
42
  import vtkViewport from './Core/Viewport.js';
42
43
  import vtkVolume from './Core/Volume.js';
43
44
  import vtkVolumeMapper from './Core/VolumeMapper.js';
@@ -87,6 +88,7 @@ var Core = {
87
88
  vtkSphereMapper,
88
89
  vtkStickMapper,
89
90
  vtkTexture,
91
+ vtkVector,
90
92
  vtkViewport,
91
93
  vtkVolume,
92
94
  vtkVolumeMapper,
@@ -246,7 +246,14 @@ export interface vtkOpenGLTexture extends vtkViewNode {
246
246
  * @param image The image to use for the texture.
247
247
  * @returns {boolean} True if the texture was successfully created, false otherwise.
248
248
  */
249
- create2DFromImage(image: any): boolean;
249
+ create2DFromImage(image: HTMLImageElement): boolean;
250
+
251
+ /**
252
+ * Creates a 2D texture from an ImageBitmap.
253
+ * @param imageBitmap The ImageBitmap to use for the texture.
254
+ * @returns {boolean} True if the texture was successfully created, false otherwise.
255
+ */
256
+ create2DFromImageBitmap(imageBitmap: ImageBitmap): boolean;
250
257
 
251
258
  /**
252
259
  * Creates a 2D filterable texture from raw data, with a preference for size over accuracy if necessary.
@@ -75,6 +75,19 @@ function vtkOpenGLTexture(publicAPI, model) {
75
75
  }
76
76
  // create the texture if it is not done already
77
77
  if (!model.handle || model.renderable.getMTime() > model.textureBuildTime.getMTime()) {
78
+ if (model.renderable.getImageBitmap() !== null) {
79
+ if (model.renderable.getInterpolate()) {
80
+ model.generateMipmap = true;
81
+ publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR);
82
+ }
83
+ // Have an Image which may not be complete
84
+ if (model.renderable.getImageBitmap() && model.renderable.getImageLoaded()) {
85
+ publicAPI.create2DFromImageBitmap(model.renderable.getImageBitmap());
86
+ publicAPI.activate();
87
+ publicAPI.sendParameters();
88
+ model.textureBuildTime.modified();
89
+ }
90
+ }
78
91
  // if we have an Image
79
92
  if (model.renderable.getImage() !== null) {
80
93
  if (model.renderable.getInterpolate()) {
@@ -1013,7 +1026,7 @@ function vtkOpenGLTexture(publicAPI, model) {
1013
1026
 
1014
1027
  //----------------------------------------------------------------------------
1015
1028
  publicAPI.create2DFromImage = image => {
1016
- // Now determine the texture parameters using the arguments.
1029
+ // Determine the texture parameters using the arguments.
1017
1030
  publicAPI.getOpenGLDataType(VtkDataTypes.UNSIGNED_CHAR);
1018
1031
  publicAPI.getInternalFormat(VtkDataTypes.UNSIGNED_CHAR, 4);
1019
1032
  publicAPI.getFormat(VtkDataTypes.UNSIGNED_CHAR, 4);
@@ -1028,30 +1041,79 @@ function vtkOpenGLTexture(publicAPI, model) {
1028
1041
  model._openGLRenderWindow.activateTexture(publicAPI);
1029
1042
  publicAPI.createTexture();
1030
1043
  publicAPI.bind();
1044
+ const needNearestPowerOfTwo = !model._openGLRenderWindow.getWebgl2() && (!isPowerOfTwo(image.width) || !isPowerOfTwo(image.height));
1045
+ let textureSource = image;
1046
+ let targetWidth = image.width;
1047
+ let targetHeight = image.height;
1048
+ let flipY = true;
1049
+
1050
+ // For WebGL1, we need to scale the image to the nearest power of two
1051
+ // dimensions if the image is not already a power of two. For WebGL2, we can
1052
+ // use the image as is. Note: Chrome has a perf issue where the path
1053
+ // HTMLImageElement -> Canvas -> texSubImage2D is faster than
1054
+ // HTMLImageElement -> texSubImage2D directly. See
1055
+ // https://issues.chromium.org/issues/41311312#comment7
1056
+ // Tested on Chrome 137.0.7151.104 Windows 11
1057
+ const isChrome = window.chrome;
1058
+ if (needNearestPowerOfTwo || isChrome) {
1059
+ const canvas = new OffscreenCanvas(nearestPowerOfTwo(image.width), nearestPowerOfTwo(image.height));
1060
+ targetWidth = canvas.width;
1061
+ targetHeight = canvas.height;
1062
+ const ctx = canvas.getContext('2d');
1063
+ ctx.translate(0, canvas.height);
1064
+ ctx.scale(1, -1);
1065
+ ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
1066
+ textureSource = canvas;
1067
+ flipY = false; // we are flipping the image manually using translate/scale
1068
+ }
1069
+
1070
+ model.width = targetWidth;
1071
+ model.height = targetHeight;
1031
1072
 
1032
1073
  // Source texture data from the PBO.
1033
- // model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
1074
+ model.context.pixelStorei(model.context.UNPACK_FLIP_Y_WEBGL, flipY);
1034
1075
  model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
1076
+ if (useTexStorage(VtkDataTypes.UNSIGNED_CHAR)) {
1077
+ model.context.texStorage2D(model.target, 1, model.internalFormat, model.width, model.height);
1078
+ model.context.texSubImage2D(model.target, 0, 0, 0, model.width, model.height, model.format, model.openGLDataType, textureSource);
1079
+ } else {
1080
+ model.context.texImage2D(model.target, 0, model.internalFormat, model.width, model.height, 0, model.format, model.openGLDataType, textureSource);
1081
+ }
1082
+ if (model.generateMipmap) {
1083
+ model.context.generateMipmap(model.target);
1084
+ }
1085
+ model.allocatedGPUMemoryInBytes = model.width * model.height * model.depth * model.components * model._openGLRenderWindow.getDefaultTextureByteSize(VtkDataTypes.UNSIGNED_CHAR, getNorm16Ext(), publicAPI.useHalfFloat());
1086
+ publicAPI.deactivate();
1087
+ return true;
1088
+ };
1035
1089
 
1036
- // Scale up the texture to the next highest power of two dimensions (if needed) and flip y.
1037
- const needNearestPowerOfTwo = !model._openGLRenderWindow.getWebgl2() && (!isPowerOfTwo(image.width) || !isPowerOfTwo(image.height));
1038
- const canvas = document.createElement('canvas');
1039
- canvas.width = needNearestPowerOfTwo ? nearestPowerOfTwo(image.width) : image.width;
1040
- canvas.height = needNearestPowerOfTwo ? nearestPowerOfTwo(image.height) : image.height;
1041
- model.width = canvas.width;
1042
- model.height = canvas.height;
1043
- const ctx = canvas.getContext('2d');
1044
- ctx.translate(0, canvas.height);
1045
- ctx.scale(1, -1);
1046
- ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
1047
- const safeImage = canvas;
1090
+ //----------------------------------------------------------------------------
1091
+ publicAPI.create2DFromImageBitmap = imageBitmap => {
1092
+ // Determine the texture parameters.
1093
+ publicAPI.getOpenGLDataType(VtkDataTypes.UNSIGNED_CHAR);
1094
+ publicAPI.getInternalFormat(VtkDataTypes.UNSIGNED_CHAR, 4);
1095
+ publicAPI.getFormat(VtkDataTypes.UNSIGNED_CHAR, 4);
1096
+ if (!model.internalFormat || !model.format || !model.openGLDataType) {
1097
+ vtkErrorMacro('Failed to determine texture parameters.');
1098
+ return false;
1099
+ }
1100
+ model.target = model.context.TEXTURE_2D;
1101
+ model.components = 4;
1102
+ model.depth = 1;
1103
+ model.numberOfDimensions = 2;
1104
+ model._openGLRenderWindow.activateTexture(publicAPI);
1105
+ publicAPI.createTexture();
1106
+ publicAPI.bind();
1107
+
1108
+ // Prepare texture unpack alignment
1109
+ model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
1110
+ model.width = imageBitmap.width;
1111
+ model.height = imageBitmap.height;
1048
1112
  if (useTexStorage(VtkDataTypes.UNSIGNED_CHAR)) {
1049
1113
  model.context.texStorage2D(model.target, 1, model.internalFormat, model.width, model.height);
1050
- if (safeImage != null) {
1051
- model.context.texSubImage2D(model.target, 0, 0, 0, model.width, model.height, model.format, model.openGLDataType, safeImage);
1052
- }
1114
+ model.context.texSubImage2D(model.target, 0, 0, 0, model.width, model.height, model.format, model.openGLDataType, imageBitmap);
1053
1115
  } else {
1054
- model.context.texImage2D(model.target, 0, model.internalFormat, model.width, model.height, 0, model.format, model.openGLDataType, safeImage);
1116
+ model.context.texImage2D(model.target, 0, model.internalFormat, model.width, model.height, 0, model.format, model.openGLDataType, imageBitmap);
1055
1117
  }
1056
1118
  if (model.generateMipmap) {
1057
1119
  model.context.generateMipmap(model.target);
package/index.d.ts CHANGED
@@ -206,6 +206,7 @@
206
206
  /// <reference path="./Rendering/Core/SphereMapper.d.ts" />
207
207
  /// <reference path="./Rendering/Core/StickMapper.d.ts" />
208
208
  /// <reference path="./Rendering/Core/Texture.d.ts" />
209
+ /// <reference path="./Rendering/Core/VectorText.d.ts" />
209
210
  /// <reference path="./Rendering/Core/Viewport.d.ts" />
210
211
  /// <reference path="./Rendering/Core/Volume.d.ts" />
211
212
  /// <reference path="./Rendering/Core/VolumeMapper/Constants.d.ts" />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitware/vtk.js",
3
- "version": "34.0.0",
3
+ "version": "34.2.0",
4
4
  "description": "Visualization Toolkit for the Web",
5
5
  "keywords": [
6
6
  "3d",