@babylonjs/core 9.5.1 → 9.5.2

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.
Files changed (34) hide show
  1. package/Engines/abstractEngine.js +2 -2
  2. package/Engines/abstractEngine.js.map +1 -1
  3. package/Lights/Clustered/clusteredLightContainer.js +1 -1
  4. package/Lights/Clustered/clusteredLightContainer.js.map +1 -1
  5. package/Materials/Background/backgroundMaterial.js +1 -0
  6. package/Materials/Background/backgroundMaterial.js.map +1 -1
  7. package/Materials/Node/Blocks/Dual/imageSourceBlock.d.ts +2 -0
  8. package/Materials/Node/Blocks/Dual/imageSourceBlock.js +14 -0
  9. package/Materials/Node/Blocks/Dual/imageSourceBlock.js.map +1 -1
  10. package/Materials/Node/Blocks/Dual/lightBlock.js +1 -1
  11. package/Materials/Node/Blocks/Dual/lightBlock.js.map +1 -1
  12. package/Materials/Node/Blocks/Dual/textureBlock.d.ts +2 -0
  13. package/Materials/Node/Blocks/Dual/textureBlock.js +19 -4
  14. package/Materials/Node/Blocks/Dual/textureBlock.js.map +1 -1
  15. package/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.js +11 -6
  16. package/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.js.map +1 -1
  17. package/Materials/PBR/openpbrMaterial.js +1 -0
  18. package/Materials/PBR/openpbrMaterial.js.map +1 -1
  19. package/Materials/PBR/pbrBaseMaterial.js +1 -0
  20. package/Materials/PBR/pbrBaseMaterial.js.map +1 -1
  21. package/Materials/materialHelper.functions.d.ts +2 -1
  22. package/Materials/materialHelper.functions.js +8 -3
  23. package/Materials/materialHelper.functions.js.map +1 -1
  24. package/Materials/standardMaterial.js +1 -0
  25. package/Materials/standardMaterial.js.map +1 -1
  26. package/Meshes/Node/Blocks/bevelBlock.d.ts +52 -0
  27. package/Meshes/Node/Blocks/bevelBlock.js +1327 -0
  28. package/Meshes/Node/Blocks/bevelBlock.js.map +1 -0
  29. package/Meshes/Node/index.d.ts +1 -0
  30. package/Meshes/Node/index.js +1 -0
  31. package/Meshes/Node/index.js.map +1 -1
  32. package/Misc/tools.js +1 -1
  33. package/Misc/tools.js.map +1 -1
  34. package/package.json +1 -1
@@ -0,0 +1,1327 @@
1
+ import { __decorate } from "../../../tslib.es6.js";
2
+ import { editableInPropertyPage } from "../../../Decorators/nodeDecorator.js";
3
+ import { Vector3 } from "../../../Maths/math.vector.js";
4
+ import { RegisterClass } from "../../../Misc/typeStore.js";
5
+ import { VertexData, VertexDataMaterialInfo } from "../../mesh.vertexData.js";
6
+ import { NodeGeometryBlockConnectionPointTypes } from "../Enums/nodeGeometryConnectionPointTypes.js";
7
+ import { NodeGeometryBlock } from "../nodeGeometryBlock.js";
8
+ const PositionEpsilon = 1e-5;
9
+ const OutputPositionEpsilon = 1e-4;
10
+ const NormalEpsilon = 1e-8;
11
+ const AngleEpsilon = 1e-7;
12
+ const TriangleAreaEpsilon = PositionEpsilon * PositionEpsilon * PositionEpsilon * PositionEpsilon;
13
+ function _Quantize(value) {
14
+ const quantized = Math.round(value / PositionEpsilon);
15
+ return quantized === 0 ? 0 : quantized;
16
+ }
17
+ function _PositionKey(x, y, z) {
18
+ return `${_Quantize(x)}:${_Quantize(y)}:${_Quantize(z)}`;
19
+ }
20
+ function _VectorKey(position) {
21
+ return _PositionKey(position.x, position.y, position.z);
22
+ }
23
+ function _OutputQuantize(value) {
24
+ const quantized = Math.round(value / OutputPositionEpsilon);
25
+ return quantized === 0 ? 0 : quantized;
26
+ }
27
+ function _OutputPositionKey(x, y, z) {
28
+ return `${x}:${y}:${z}`;
29
+ }
30
+ function _EdgeKey(v0, v1) {
31
+ return v0 < v1 ? `${v0}:${v1}` : `${v1}:${v0}`;
32
+ }
33
+ function _CloneVertexData(vertexData) {
34
+ const clone = vertexData.clone();
35
+ if (!clone.normals && clone.positions && clone.indices) {
36
+ const normals = [];
37
+ VertexData.ComputeNormals(clone.positions, clone.indices, normals);
38
+ clone.normals = normals;
39
+ }
40
+ return clone;
41
+ }
42
+ function _NormalizeNormalOrFallback(normal, fallback) {
43
+ return normal.lengthSquared() > NormalEpsilon ? normal.normalizeToNew() : fallback.normalizeToNew();
44
+ }
45
+ function _BuildAttributeDescriptors(vertexData, vertexCount) {
46
+ const descriptors = [];
47
+ const addDescriptor = (name, stride) => {
48
+ const source = vertexData[name];
49
+ if (!source || source.length < vertexCount * stride) {
50
+ return;
51
+ }
52
+ descriptors.push({
53
+ name,
54
+ source,
55
+ stride,
56
+ offset: descriptors.reduce((sum, descriptor) => sum + descriptor.stride, 0),
57
+ output: [],
58
+ });
59
+ };
60
+ addDescriptor("tangents", 4);
61
+ addDescriptor("uvs", 2);
62
+ addDescriptor("uvs2", 2);
63
+ addDescriptor("uvs3", 2);
64
+ addDescriptor("uvs4", 2);
65
+ addDescriptor("uvs5", 2);
66
+ addDescriptor("uvs6", 2);
67
+ if (vertexData.colors) {
68
+ addDescriptor("colors", vertexData.colors.length === vertexData.positions.length ? 3 : 4);
69
+ }
70
+ addDescriptor("matricesIndices", 4);
71
+ addDescriptor("matricesWeights", 4);
72
+ addDescriptor("matricesIndicesExtra", 4);
73
+ addDescriptor("matricesWeightsExtra", 4);
74
+ return descriptors;
75
+ }
76
+ function _GetAttributeLength(descriptors) {
77
+ return descriptors.reduce((sum, descriptor) => sum + descriptor.stride, 0);
78
+ }
79
+ function _GetVertexAttributes(descriptors, vertexIndex) {
80
+ const attributes = [];
81
+ for (const descriptor of descriptors) {
82
+ const sourceOffset = vertexIndex * descriptor.stride;
83
+ for (let index = 0; index < descriptor.stride; index++) {
84
+ attributes.push(descriptor.source[sourceOffset + index]);
85
+ }
86
+ }
87
+ return attributes;
88
+ }
89
+ function _InterpolateAttributes(start, end, amount) {
90
+ if (!start.length) {
91
+ return start;
92
+ }
93
+ return start.map((value, index) => value + (end[index] - value) * amount);
94
+ }
95
+ function _AverageAttributes(attributes, length) {
96
+ if (!length || !attributes.length) {
97
+ return [];
98
+ }
99
+ const result = new Array(length).fill(0);
100
+ for (const attribute of attributes) {
101
+ for (let index = 0; index < length; index++) {
102
+ result[index] += attribute[index] ?? 0;
103
+ }
104
+ }
105
+ for (let index = 0; index < length; index++) {
106
+ result[index] /= attributes.length;
107
+ }
108
+ return result;
109
+ }
110
+ function _AttributesMatch(left, right) {
111
+ if (left.length !== right.length) {
112
+ return false;
113
+ }
114
+ for (let index = 0; index < left.length; index++) {
115
+ if (Math.abs(left[index] - right[index]) > OutputPositionEpsilon) {
116
+ return false;
117
+ }
118
+ }
119
+ return true;
120
+ }
121
+ function _AssignAttributeOutputs(result, descriptors, vertexAttributes) {
122
+ for (const descriptor of descriptors) {
123
+ descriptor.output.length = 0;
124
+ for (const attributes of vertexAttributes) {
125
+ for (let index = 0; index < descriptor.stride; index++) {
126
+ descriptor.output.push(attributes[descriptor.offset + index] ?? 0);
127
+ }
128
+ }
129
+ switch (descriptor.name) {
130
+ case "tangents":
131
+ result.tangents = descriptor.output;
132
+ break;
133
+ case "uvs":
134
+ result.uvs = descriptor.output;
135
+ break;
136
+ case "uvs2":
137
+ result.uvs2 = descriptor.output;
138
+ break;
139
+ case "uvs3":
140
+ result.uvs3 = descriptor.output;
141
+ break;
142
+ case "uvs4":
143
+ result.uvs4 = descriptor.output;
144
+ break;
145
+ case "uvs5":
146
+ result.uvs5 = descriptor.output;
147
+ break;
148
+ case "uvs6":
149
+ result.uvs6 = descriptor.output;
150
+ break;
151
+ case "colors":
152
+ result.colors = descriptor.output;
153
+ break;
154
+ case "matricesIndices":
155
+ result.matricesIndices = descriptor.output;
156
+ break;
157
+ case "matricesWeights":
158
+ result.matricesWeights = descriptor.output;
159
+ break;
160
+ case "matricesIndicesExtra":
161
+ result.matricesIndicesExtra = descriptor.output;
162
+ break;
163
+ case "matricesWeightsExtra":
164
+ result.matricesWeightsExtra = descriptor.output;
165
+ break;
166
+ }
167
+ }
168
+ }
169
+ function _GetMaterialIndex(vertexData, indexStart) {
170
+ if (!vertexData.materialInfos) {
171
+ return 0;
172
+ }
173
+ for (const materialInfo of vertexData.materialInfos) {
174
+ if (indexStart >= materialInfo.indexStart && indexStart < materialInfo.indexStart + materialInfo.indexCount) {
175
+ return materialInfo.materialIndex;
176
+ }
177
+ }
178
+ return 0;
179
+ }
180
+ function _BuildMaterialInfoResult(vertexData, indices, materialIndices, vertexCount) {
181
+ if (!vertexData.materialInfos?.length) {
182
+ return { indices, materialInfos: null };
183
+ }
184
+ const materialOrder = vertexData.materialInfos.map((materialInfo) => materialInfo.materialIndex);
185
+ const groups = new Map();
186
+ for (let triangleIndex = 0; triangleIndex < materialIndices.length; triangleIndex++) {
187
+ const materialIndex = materialIndices[triangleIndex];
188
+ let group = groups.get(materialIndex);
189
+ if (!group) {
190
+ group = [];
191
+ groups.set(materialIndex, group);
192
+ if (!materialOrder.includes(materialIndex)) {
193
+ materialOrder.push(materialIndex);
194
+ }
195
+ }
196
+ const indexOffset = triangleIndex * 3;
197
+ group.push(indices[indexOffset], indices[indexOffset + 1], indices[indexOffset + 2]);
198
+ }
199
+ const groupedIndices = [];
200
+ const materialInfos = [];
201
+ for (const materialIndex of materialOrder) {
202
+ const group = groups.get(materialIndex);
203
+ if (!group?.length) {
204
+ continue;
205
+ }
206
+ const materialInfo = new VertexDataMaterialInfo();
207
+ materialInfo.materialIndex = materialIndex;
208
+ materialInfo.indexStart = groupedIndices.length;
209
+ materialInfo.indexCount = group.length;
210
+ materialInfo.verticesStart = 0;
211
+ materialInfo.verticesCount = vertexCount;
212
+ groupedIndices.push(...group);
213
+ materialInfos.push(materialInfo);
214
+ }
215
+ return { indices: groupedIndices, materialInfos };
216
+ }
217
+ function _IsFlatFace(face) {
218
+ return face.cornerNormals.every((normal) => Vector3.Dot(normal, face.normal) > 1 - PositionEpsilon);
219
+ }
220
+ function _IsBevelPolygonPoint(point) {
221
+ return point.position !== undefined;
222
+ }
223
+ function _GetCapPointPosition(point) {
224
+ return _IsBevelPolygonPoint(point) ? point.position : point;
225
+ }
226
+ function _GetCapPointNormal(point, fallback) {
227
+ return _IsBevelPolygonPoint(point) ? point.normal : fallback;
228
+ }
229
+ function _GetCapPointAttributes(point) {
230
+ return _IsBevelPolygonPoint(point) ? point.attributes : [];
231
+ }
232
+ function _GetCapPointMaterialIndex(point) {
233
+ return _IsBevelPolygonPoint(point) ? point.materialIndex : 0;
234
+ }
235
+ function _BuildTopology(vertexData) {
236
+ const positions = vertexData.positions;
237
+ const normals = vertexData.normals;
238
+ if (!positions || positions.length < 9) {
239
+ return null;
240
+ }
241
+ const vertexCount = positions.length / 3;
242
+ const indices = vertexData.indices && vertexData.indices.length ? Array.from(vertexData.indices) : Array.from({ length: vertexCount }, (_, index) => index);
243
+ const weldedPositionMap = new Map();
244
+ const originalToWelded = [];
245
+ const weldedPositions = [];
246
+ for (let index = 0; index < vertexCount; index++) {
247
+ const x = positions[index * 3];
248
+ const y = positions[index * 3 + 1];
249
+ const z = positions[index * 3 + 2];
250
+ const key = _PositionKey(x, y, z);
251
+ let weldedIndex = weldedPositionMap.get(key);
252
+ if (weldedIndex === undefined) {
253
+ weldedIndex = weldedPositions.length;
254
+ weldedPositionMap.set(key, weldedIndex);
255
+ weldedPositions.push(new Vector3(x, y, z));
256
+ }
257
+ originalToWelded[index] = weldedIndex;
258
+ }
259
+ const faces = [];
260
+ const edges = new Map();
261
+ const vertexFaces = new Map();
262
+ const edge0 = new Vector3();
263
+ const edge1 = new Vector3();
264
+ const normal = new Vector3();
265
+ for (let index = 0; index < indices.length; index += 3) {
266
+ let originalIndices = [indices[index], indices[index + 1], indices[index + 2]];
267
+ const i0 = originalToWelded[originalIndices[0]];
268
+ const i1 = originalToWelded[originalIndices[1]];
269
+ const i2 = originalToWelded[originalIndices[2]];
270
+ if (i0 === i1 || i1 === i2 || i2 === i0) {
271
+ continue;
272
+ }
273
+ let faceIndices = [i0, i1, i2];
274
+ const p0 = weldedPositions[faceIndices[0]];
275
+ const p1 = weldedPositions[faceIndices[1]];
276
+ const p2 = weldedPositions[faceIndices[2]];
277
+ p1.subtractToRef(p0, edge0);
278
+ p2.subtractToRef(p0, edge1);
279
+ Vector3.CrossToRef(edge0, edge1, normal);
280
+ if (normal.lengthSquared() < NormalEpsilon) {
281
+ continue;
282
+ }
283
+ let cornerNormals = normals
284
+ ? [
285
+ _NormalizeNormalOrFallback(Vector3.FromArray(normals, originalIndices[0] * 3), normal),
286
+ _NormalizeNormalOrFallback(Vector3.FromArray(normals, originalIndices[1] * 3), normal),
287
+ _NormalizeNormalOrFallback(Vector3.FromArray(normals, originalIndices[2] * 3), normal),
288
+ ]
289
+ : [normal.normalizeToNew(), normal.normalizeToNew(), normal.normalizeToNew()];
290
+ const averageCornerNormal = _NormalizeNormalOrFallback(cornerNormals[0].add(cornerNormals[1]).addInPlace(cornerNormals[2]), normal);
291
+ if (normals && Vector3.Dot(normal, averageCornerNormal) < 0) {
292
+ faceIndices = [i0, i2, i1];
293
+ originalIndices = [originalIndices[0], originalIndices[2], originalIndices[1]];
294
+ cornerNormals = [cornerNormals[0], cornerNormals[2], cornerNormals[1]];
295
+ normal.scaleInPlace(-1);
296
+ }
297
+ const faceNormal = normal.normalizeToNew();
298
+ if (!normals) {
299
+ cornerNormals = [faceNormal.clone(), faceNormal.clone(), faceNormal.clone()];
300
+ }
301
+ const faceIndex = faces.length;
302
+ faces.push({
303
+ indices: faceIndices,
304
+ originalIndices,
305
+ normal: faceNormal,
306
+ cornerNormals,
307
+ materialIndex: _GetMaterialIndex(vertexData, index),
308
+ });
309
+ for (const vertexIndex of faceIndices) {
310
+ let faceList = vertexFaces.get(vertexIndex);
311
+ if (!faceList) {
312
+ faceList = [];
313
+ vertexFaces.set(vertexIndex, faceList);
314
+ }
315
+ faceList.push(faceIndex);
316
+ }
317
+ for (let edgeIndex = 0; edgeIndex < 3; edgeIndex++) {
318
+ const v0 = faceIndices[edgeIndex];
319
+ const v1 = faceIndices[(edgeIndex + 1) % 3];
320
+ const key = _EdgeKey(v0, v1);
321
+ let edge = edges.get(key);
322
+ if (!edge) {
323
+ edge = {
324
+ key,
325
+ v0: Math.min(v0, v1),
326
+ v1: Math.max(v0, v1),
327
+ faces: [],
328
+ };
329
+ edges.set(key, edge);
330
+ }
331
+ edge.faces.push({ faceIndex });
332
+ }
333
+ }
334
+ if (!faces.length) {
335
+ return null;
336
+ }
337
+ return {
338
+ positions: weldedPositions,
339
+ faces,
340
+ edges,
341
+ vertexFaces,
342
+ };
343
+ }
344
+ function _ClonePolygonPoint(point) {
345
+ return {
346
+ position: point.position.clone(),
347
+ normal: point.normal.clone(),
348
+ attributes: point.attributes.slice(),
349
+ materialIndex: point.materialIndex,
350
+ };
351
+ }
352
+ function _InterpolatePolygonPoint(start, end, amount) {
353
+ return {
354
+ position: Vector3.Lerp(start.position, end.position, amount),
355
+ normal: _NormalizeNormalOrFallback(Vector3.Lerp(start.normal, end.normal, amount), start.normal),
356
+ attributes: _InterpolateAttributes(start.attributes, end.attributes, amount),
357
+ materialIndex: start.materialIndex,
358
+ };
359
+ }
360
+ function _InterpolateSegmentNormal(segment, t) {
361
+ const denominator = segment.tMax - segment.tMin;
362
+ if (denominator <= PositionEpsilon) {
363
+ return segment.minNormal.clone();
364
+ }
365
+ const amount = Math.min(1, Math.max(0, (t - segment.tMin) / denominator));
366
+ return _NormalizeNormalOrFallback(Vector3.Lerp(segment.minNormal, segment.maxNormal, amount), segment.minNormal);
367
+ }
368
+ function _InterpolateSegmentPoint(segment, t) {
369
+ if (t <= segment.tMin + PositionEpsilon) {
370
+ return segment.minPoint;
371
+ }
372
+ if (t >= segment.tMax - PositionEpsilon) {
373
+ return segment.maxPoint;
374
+ }
375
+ return Vector3.Lerp(segment.minPoint, segment.maxPoint, (t - segment.tMin) / (segment.tMax - segment.tMin));
376
+ }
377
+ function _InterpolateSegmentAttributes(segment, t) {
378
+ if (t <= segment.tMin + PositionEpsilon) {
379
+ return segment.minAttributes;
380
+ }
381
+ if (t >= segment.tMax - PositionEpsilon) {
382
+ return segment.maxAttributes;
383
+ }
384
+ return _InterpolateAttributes(segment.minAttributes, segment.maxAttributes, (t - segment.tMin) / (segment.tMax - segment.tMin));
385
+ }
386
+ function _ClipPolygonAgainstEdge(polygon, edgeStart, inward, amount) {
387
+ if (!polygon.length) {
388
+ return polygon;
389
+ }
390
+ const output = [];
391
+ let previous = polygon[polygon.length - 1];
392
+ let previousDistance = Vector3.Dot(previous.position.subtract(edgeStart), inward) - amount;
393
+ let previousInside = previousDistance >= -PositionEpsilon;
394
+ for (const current of polygon) {
395
+ const currentDistance = Vector3.Dot(current.position.subtract(edgeStart), inward) - amount;
396
+ const currentInside = currentDistance >= -PositionEpsilon;
397
+ if (currentInside !== previousInside) {
398
+ const denominator = previousDistance - currentDistance;
399
+ if (Math.abs(denominator) > NormalEpsilon) {
400
+ const t = previousDistance / denominator;
401
+ output.push(_InterpolatePolygonPoint(previous, current, t));
402
+ }
403
+ }
404
+ if (currentInside) {
405
+ output.push(_ClonePolygonPoint(current));
406
+ }
407
+ previous = current;
408
+ previousDistance = currentDistance;
409
+ previousInside = currentInside;
410
+ }
411
+ return output;
412
+ }
413
+ function _SlerpDirections(start, end, amount) {
414
+ const dot = Math.min(1, Math.max(-1, Vector3.Dot(start, end)));
415
+ if (dot > 1 - PositionEpsilon) {
416
+ return Vector3.Lerp(start, end, amount).normalize();
417
+ }
418
+ const theta = Math.acos(dot);
419
+ const sinTheta = Math.sin(theta);
420
+ if (Math.abs(sinTheta) < NormalEpsilon) {
421
+ return Vector3.Lerp(start, end, amount).normalize();
422
+ }
423
+ const startScale = Math.sin((1 - amount) * theta) / sinTheta;
424
+ const endScale = Math.sin(amount * theta) / sinTheta;
425
+ const result = start.scale(startScale).addInPlace(end.scale(endScale));
426
+ if (result.lengthSquared() < NormalEpsilon) {
427
+ return Vector3.Lerp(start, end, amount).normalize();
428
+ }
429
+ return result.normalize();
430
+ }
431
+ function _AddUniquePoint(points, point, normal, attributes, materialIndex) {
432
+ const key = _VectorKey(point);
433
+ const normalizedNormal = _NormalizeNormalOrFallback(normal, normal);
434
+ for (const existing of points) {
435
+ if (_VectorKey(_GetCapPointPosition(existing)) === key) {
436
+ if (_IsBevelPolygonPoint(existing)) {
437
+ existing.normal.addInPlace(normalizedNormal).normalize();
438
+ existing.attributes = _AverageAttributes([existing.attributes, attributes], attributes.length);
439
+ }
440
+ return;
441
+ }
442
+ }
443
+ points.push({
444
+ position: point.clone(),
445
+ normal: normalizedNormal,
446
+ attributes: attributes.slice(),
447
+ materialIndex,
448
+ });
449
+ }
450
+ function _AddUniqueNormal(normals, normal) {
451
+ for (const existing of normals) {
452
+ if (Vector3.Dot(existing, normal) > 1 - PositionEpsilon) {
453
+ return;
454
+ }
455
+ }
456
+ normals.push(normal.clone());
457
+ }
458
+ function _SolveThreePlaneIntersection(normals, distances) {
459
+ const cross12 = Vector3.Cross(normals[1], normals[2]);
460
+ const denominator = Vector3.Dot(normals[0], cross12);
461
+ if (Math.abs(denominator) < NormalEpsilon) {
462
+ return null;
463
+ }
464
+ const result = cross12.scale(distances[0]);
465
+ result.addInPlace(Vector3.Cross(normals[2], normals[0]).scale(distances[1]));
466
+ result.addInPlace(Vector3.Cross(normals[0], normals[1]).scale(distances[2]));
467
+ result.scaleInPlace(1 / denominator);
468
+ return result;
469
+ }
470
+ function _BuildCoplanarFaceClipEdges(topology, selectedEdges) {
471
+ const result = new Map();
472
+ const visitedFaces = new Set();
473
+ for (let startFaceIndex = 0; startFaceIndex < topology.faces.length; startFaceIndex++) {
474
+ if (visitedFaces.has(startFaceIndex)) {
475
+ continue;
476
+ }
477
+ const group = [];
478
+ const stack = [startFaceIndex];
479
+ const groupNormal = topology.faces[startFaceIndex].normal;
480
+ visitedFaces.add(startFaceIndex);
481
+ while (stack.length) {
482
+ const faceIndex = stack.pop();
483
+ const face = topology.faces[faceIndex];
484
+ group.push(faceIndex);
485
+ for (let edgeIndex = 0; edgeIndex < 3; edgeIndex++) {
486
+ const key = _EdgeKey(face.indices[edgeIndex], face.indices[(edgeIndex + 1) % 3]);
487
+ const edge = topology.edges.get(key);
488
+ if (!edge) {
489
+ continue;
490
+ }
491
+ for (const edgeFace of edge.faces) {
492
+ if (visitedFaces.has(edgeFace.faceIndex)) {
493
+ continue;
494
+ }
495
+ if (Vector3.Dot(groupNormal, topology.faces[edgeFace.faceIndex].normal) < 1 - PositionEpsilon) {
496
+ continue;
497
+ }
498
+ visitedFaces.add(edgeFace.faceIndex);
499
+ stack.push(edgeFace.faceIndex);
500
+ }
501
+ }
502
+ }
503
+ const clipEdges = [];
504
+ const addedClipEdges = new Set();
505
+ for (const faceIndex of group) {
506
+ const face = topology.faces[faceIndex];
507
+ for (let edgeIndex = 0; edgeIndex < 3; edgeIndex++) {
508
+ const start = face.indices[edgeIndex];
509
+ const end = face.indices[(edgeIndex + 1) % 3];
510
+ const key = _EdgeKey(start, end);
511
+ if (!selectedEdges.has(key) || addedClipEdges.has(key)) {
512
+ continue;
513
+ }
514
+ const edgeStart = topology.positions[start];
515
+ const edgeEnd = topology.positions[end];
516
+ const edgeDirection = edgeEnd.subtract(edgeStart).normalize();
517
+ const inward = Vector3.Cross(face.normal, edgeDirection).normalize();
518
+ clipEdges.push({ key, start, end, inward });
519
+ addedClipEdges.add(key);
520
+ }
521
+ }
522
+ for (const faceIndex of group) {
523
+ result.set(faceIndex, clipEdges);
524
+ }
525
+ }
526
+ return result;
527
+ }
528
+ function _InsertPointOnPolygonBoundary(polygon, point) {
529
+ for (const existing of polygon) {
530
+ if (existing.position.subtract(point).lengthSquared() <= PositionEpsilon * PositionEpsilon) {
531
+ return;
532
+ }
533
+ }
534
+ let bestEdgeIndex = -1;
535
+ let bestDistanceSquared = Number.MAX_VALUE;
536
+ let bestProjection = 0;
537
+ for (let index = 0; index < polygon.length; index++) {
538
+ const start = polygon[index].position;
539
+ const end = polygon[(index + 1) % polygon.length].position;
540
+ const edge = end.subtract(start);
541
+ const edgeLengthSquared = edge.lengthSquared();
542
+ if (edgeLengthSquared < PositionEpsilon * PositionEpsilon) {
543
+ continue;
544
+ }
545
+ const projection = Vector3.Dot(point.subtract(start), edge) / edgeLengthSquared;
546
+ if (projection < -PositionEpsilon || projection > 1 + PositionEpsilon) {
547
+ continue;
548
+ }
549
+ const closest = start.add(edge.scale(projection));
550
+ const distanceSquared = closest.subtract(point).lengthSquared();
551
+ if (distanceSquared < bestDistanceSquared) {
552
+ bestDistanceSquared = distanceSquared;
553
+ bestEdgeIndex = index;
554
+ bestProjection = projection;
555
+ }
556
+ }
557
+ if (bestEdgeIndex !== -1 && bestDistanceSquared <= OutputPositionEpsilon * OutputPositionEpsilon) {
558
+ const insertedPoint = _InterpolatePolygonPoint(polygon[bestEdgeIndex], polygon[(bestEdgeIndex + 1) % polygon.length], bestProjection);
559
+ insertedPoint.position = point.clone();
560
+ polygon.splice(bestEdgeIndex + 1, 0, insertedPoint);
561
+ }
562
+ }
563
+ function _BuildMergedBoundaryPolygon(polygons) {
564
+ const points = new Map();
565
+ const edgeUseCounts = new Map();
566
+ for (const polygon of polygons) {
567
+ for (const point of polygon) {
568
+ const key = _VectorKey(point.position);
569
+ const existing = points.get(key);
570
+ if (existing) {
571
+ existing.normal.addInPlace(point.normal).normalize();
572
+ }
573
+ else {
574
+ points.set(key, _ClonePolygonPoint(point));
575
+ }
576
+ }
577
+ for (let index = 0; index < polygon.length; index++) {
578
+ const key0 = _VectorKey(polygon[index].position);
579
+ const key1 = _VectorKey(polygon[(index + 1) % polygon.length].position);
580
+ if (key0 === key1) {
581
+ continue;
582
+ }
583
+ const edgeKey = key0 < key1 ? `${key0}|${key1}` : `${key1}|${key0}`;
584
+ const edgeUseCount = edgeUseCounts.get(edgeKey);
585
+ if (edgeUseCount) {
586
+ edgeUseCount.count++;
587
+ }
588
+ else {
589
+ edgeUseCounts.set(edgeKey, { count: 1, key0, key1 });
590
+ }
591
+ }
592
+ }
593
+ const adjacency = new Map();
594
+ for (const edge of Array.from(edgeUseCounts.values())) {
595
+ if (edge.count !== 1) {
596
+ continue;
597
+ }
598
+ let adjacency0 = adjacency.get(edge.key0);
599
+ if (!adjacency0) {
600
+ adjacency0 = [];
601
+ adjacency.set(edge.key0, adjacency0);
602
+ }
603
+ adjacency0.push(edge.key1);
604
+ let adjacency1 = adjacency.get(edge.key1);
605
+ if (!adjacency1) {
606
+ adjacency1 = [];
607
+ adjacency.set(edge.key1, adjacency1);
608
+ }
609
+ adjacency1.push(edge.key0);
610
+ }
611
+ if (!adjacency.size || Array.from(adjacency.values()).some((neighbors) => neighbors.length !== 2)) {
612
+ return null;
613
+ }
614
+ const startKey = Array.from(adjacency.keys()).sort()[0];
615
+ const orderedKeys = [];
616
+ let previousKey = "";
617
+ let currentKey = startKey;
618
+ do {
619
+ orderedKeys.push(currentKey);
620
+ const neighbors = adjacency.get(currentKey);
621
+ const nextKey = neighbors[0] === previousKey ? neighbors[1] : neighbors[0];
622
+ previousKey = currentKey;
623
+ currentKey = nextKey;
624
+ if (orderedKeys.length > adjacency.size) {
625
+ return null;
626
+ }
627
+ } while (currentKey !== startKey);
628
+ if (orderedKeys.length !== adjacency.size) {
629
+ return null;
630
+ }
631
+ return orderedKeys.map((key) => points.get(key));
632
+ }
633
+ function _BevelVertexData(vertexData, amount, segments, angle) {
634
+ const topology = _BuildTopology(vertexData);
635
+ if (!topology || amount <= PositionEpsilon || segments <= 0) {
636
+ return _CloneVertexData(vertexData);
637
+ }
638
+ const selectedEdges = new Set();
639
+ const selectedVertices = new Set();
640
+ const selectedEdgeCountPerVertex = new Map();
641
+ const threshold = Math.cos(angle + AngleEpsilon);
642
+ let shortestSelectedEdgeLength = Number.MAX_VALUE;
643
+ for (const edge of Array.from(topology.edges.values())) {
644
+ if (edge.faces.length !== 2) {
645
+ continue;
646
+ }
647
+ const face0 = topology.faces[edge.faces[0].faceIndex];
648
+ const face1 = topology.faces[edge.faces[1].faceIndex];
649
+ if (Vector3.Dot(face0.normal, face1.normal) < threshold) {
650
+ selectedEdges.add(edge.key);
651
+ selectedVertices.add(edge.v0);
652
+ selectedVertices.add(edge.v1);
653
+ selectedEdgeCountPerVertex.set(edge.v0, (selectedEdgeCountPerVertex.get(edge.v0) ?? 0) + 1);
654
+ selectedEdgeCountPerVertex.set(edge.v1, (selectedEdgeCountPerVertex.get(edge.v1) ?? 0) + 1);
655
+ shortestSelectedEdgeLength = Math.min(shortestSelectedEdgeLength, Vector3.Distance(topology.positions[edge.v0], topology.positions[edge.v1]));
656
+ }
657
+ }
658
+ if (!selectedEdges.size) {
659
+ return _CloneVertexData(vertexData);
660
+ }
661
+ const bevelAmount = Math.min(amount, shortestSelectedEdgeLength * 0.5);
662
+ if (bevelAmount <= PositionEpsilon) {
663
+ return _CloneVertexData(vertexData);
664
+ }
665
+ const faceClipEdges = _BuildCoplanarFaceClipEdges(topology, selectedEdges);
666
+ const attributeDescriptors = _BuildAttributeDescriptors(vertexData, vertexData.positions.length / 3);
667
+ const attributeLength = _GetAttributeLength(attributeDescriptors);
668
+ const outputPositions = [];
669
+ const outputNormals = [];
670
+ const outputNormalContributions = [];
671
+ const outputVertexAttributes = [];
672
+ const outputIndices = [];
673
+ const outputTriangleMaterialIndices = [];
674
+ const outputVertexBuckets = new Map();
675
+ const outputVertexGroups = [];
676
+ const faceEdgeSegments = new Map();
677
+ const capPoints = new Map();
678
+ const preparedFaces = [];
679
+ const edgeIntervals = new Map();
680
+ const getOrCreateVertex = (position, normal, smoothingGroup, attributes) => {
681
+ const normalizedNormal = normal.normalizeToNew();
682
+ const vertexAttributes = attributeLength ? (attributes.length === attributeLength ? attributes : new Array(attributeLength).fill(0)) : [];
683
+ const qx = _OutputQuantize(position.x);
684
+ const qy = _OutputQuantize(position.y);
685
+ const qz = _OutputQuantize(position.z);
686
+ let canonicalIndex = -1;
687
+ for (let dz = -1; dz <= 1; dz++) {
688
+ for (let dy = -1; dy <= 1; dy++) {
689
+ for (let dx = -1; dx <= 1; dx++) {
690
+ const bucket = outputVertexBuckets.get(_OutputPositionKey(qx + dx, qy + dy, qz + dz));
691
+ if (!bucket) {
692
+ continue;
693
+ }
694
+ for (const index of bucket) {
695
+ const outputIndex = index * 3;
696
+ const deltaX = outputPositions[outputIndex] - position.x;
697
+ const deltaY = outputPositions[outputIndex + 1] - position.y;
698
+ const deltaZ = outputPositions[outputIndex + 2] - position.z;
699
+ if (deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ <= OutputPositionEpsilon * OutputPositionEpsilon) {
700
+ if (canonicalIndex === -1) {
701
+ canonicalIndex = index;
702
+ }
703
+ if (outputVertexGroups[index] !== smoothingGroup) {
704
+ continue;
705
+ }
706
+ if (!_AttributesMatch(outputVertexAttributes[index], vertexAttributes)) {
707
+ continue;
708
+ }
709
+ const normalContributions = outputNormalContributions[index];
710
+ if (!normalContributions.some((normal) => Vector3.Dot(normal, normalizedNormal) > 1 - PositionEpsilon)) {
711
+ normalContributions.push(normalizedNormal);
712
+ outputNormals[index].addInPlace(normalizedNormal);
713
+ }
714
+ return index;
715
+ }
716
+ }
717
+ }
718
+ }
719
+ }
720
+ const index = outputPositions.length / 3;
721
+ const outputX = canonicalIndex === -1 ? position.x : outputPositions[canonicalIndex * 3];
722
+ const outputY = canonicalIndex === -1 ? position.y : outputPositions[canonicalIndex * 3 + 1];
723
+ const outputZ = canonicalIndex === -1 ? position.z : outputPositions[canonicalIndex * 3 + 2];
724
+ const key = _OutputPositionKey(_OutputQuantize(outputX), _OutputQuantize(outputY), _OutputQuantize(outputZ));
725
+ let bucket = outputVertexBuckets.get(key);
726
+ if (!bucket) {
727
+ bucket = [];
728
+ outputVertexBuckets.set(key, bucket);
729
+ }
730
+ bucket.push(index);
731
+ outputPositions.push(outputX, outputY, outputZ);
732
+ outputNormals.push(normalizedNormal);
733
+ outputNormalContributions.push([normalizedNormal.clone()]);
734
+ outputVertexAttributes.push(vertexAttributes.slice());
735
+ outputVertexGroups.push(smoothingGroup);
736
+ return index;
737
+ };
738
+ const addCapPoint = (vertexIndex, point, normal, attributes, materialIndex) => {
739
+ let points = capPoints.get(vertexIndex);
740
+ if (!points) {
741
+ points = [];
742
+ capPoints.set(vertexIndex, points);
743
+ }
744
+ _AddUniquePoint(points, point, normal, attributes, materialIndex);
745
+ };
746
+ const addTriangle = (p0, p1, p2, targetNormal, n0 = targetNormal, n1 = targetNormal, n2 = targetNormal, smoothingGroup = "smooth", p0Attributes = [], p1Attributes = [], p2Attributes = [], materialIndex = 0) => {
747
+ const edge0 = p1.subtract(p0);
748
+ const edge1 = p2.subtract(p0);
749
+ const normal = Vector3.Cross(edge0, edge1);
750
+ if (normal.lengthSquared() < TriangleAreaEpsilon) {
751
+ return;
752
+ }
753
+ let v1 = p1;
754
+ let v2 = p2;
755
+ let vn1 = n1;
756
+ let vn2 = n2;
757
+ let va1 = p1Attributes;
758
+ let va2 = p2Attributes;
759
+ if (Vector3.Dot(normal, targetNormal) > 0) {
760
+ v1 = p2;
761
+ v2 = p1;
762
+ vn1 = n2;
763
+ vn2 = n1;
764
+ va1 = p2Attributes;
765
+ va2 = p1Attributes;
766
+ }
767
+ const i0 = getOrCreateVertex(p0, n0, smoothingGroup, p0Attributes);
768
+ const i1 = getOrCreateVertex(v1, vn1, smoothingGroup, va1);
769
+ const i2 = getOrCreateVertex(v2, vn2, smoothingGroup, va2);
770
+ if (i0 === i1 || i1 === i2 || i2 === i0) {
771
+ return;
772
+ }
773
+ outputIndices.push(i0, i1, i2);
774
+ outputTriangleMaterialIndices.push(materialIndex);
775
+ };
776
+ const addFacePolygon = (polygon, normal, smoothingGroup, useFlatNormals) => {
777
+ const center = new Vector3();
778
+ const centerAttributes = _AverageAttributes(polygon.map((point) => point.attributes), attributeLength);
779
+ const materialIndex = polygon[0]?.materialIndex ?? 0;
780
+ const centerNormal = useFlatNormals
781
+ ? normal
782
+ : _NormalizeNormalOrFallback(polygon.reduce((accumulator, point) => accumulator.addInPlace(point.normal), new Vector3()), normal);
783
+ for (const point of polygon) {
784
+ center.addInPlace(point.position);
785
+ }
786
+ center.scaleInPlace(1 / polygon.length);
787
+ for (let index = 0; index < polygon.length; index++) {
788
+ const nextIndex = (index + 1) % polygon.length;
789
+ addTriangle(center, polygon[index].position, polygon[nextIndex].position, normal, centerNormal, useFlatNormals ? normal : polygon[index].normal, useFlatNormals ? normal : polygon[nextIndex].normal, smoothingGroup, centerAttributes, polygon[index].attributes, polygon[nextIndex].attributes, materialIndex);
790
+ }
791
+ };
792
+ for (let faceIndex = 0; faceIndex < topology.faces.length; faceIndex++) {
793
+ const face = topology.faces[faceIndex];
794
+ const isFlat = _IsFlatFace(face);
795
+ let polygon = face.indices.map((index, cornerIndex) => ({
796
+ position: topology.positions[index].clone(),
797
+ normal: face.cornerNormals[cornerIndex].clone(),
798
+ attributes: _GetVertexAttributes(attributeDescriptors, face.originalIndices[cornerIndex]),
799
+ materialIndex: face.materialIndex,
800
+ }));
801
+ const selectedFaceEdges = [];
802
+ for (let edgeIndex = 0; edgeIndex < 3; edgeIndex++) {
803
+ const start = face.indices[edgeIndex];
804
+ const end = face.indices[(edgeIndex + 1) % 3];
805
+ const key = _EdgeKey(start, end);
806
+ if (!selectedEdges.has(key)) {
807
+ continue;
808
+ }
809
+ const edgeStart = topology.positions[start];
810
+ const edgeEnd = topology.positions[end];
811
+ const edgeDirection = edgeEnd.subtract(edgeStart).normalize();
812
+ const inward = Vector3.Cross(face.normal, edgeDirection).normalize();
813
+ selectedFaceEdges.push({ key, start, end, inward });
814
+ }
815
+ const clipEdges = faceClipEdges.get(faceIndex) ?? selectedFaceEdges;
816
+ for (const clipEdge of clipEdges) {
817
+ polygon = _ClipPolygonAgainstEdge(polygon, topology.positions[clipEdge.start], clipEdge.inward, bevelAmount);
818
+ }
819
+ if (isFlat) {
820
+ for (const point of polygon) {
821
+ point.normal = face.normal.clone();
822
+ }
823
+ }
824
+ preparedFaces.push({ faceIndex, polygon, selectedFaceEdges, isFlat });
825
+ for (const selectedFaceEdge of selectedFaceEdges) {
826
+ const edge = topology.edges.get(selectedFaceEdge.key);
827
+ const edgeStart = topology.positions[edge.v0];
828
+ const edgeEnd = topology.positions[edge.v1];
829
+ const axis = edgeEnd.subtract(edgeStart);
830
+ const edgeLength = axis.length();
831
+ if (edgeLength < PositionEpsilon) {
832
+ continue;
833
+ }
834
+ axis.normalize();
835
+ const pointsOnLine = [];
836
+ const orientedEdgeStart = topology.positions[selectedFaceEdge.start];
837
+ for (const point of polygon) {
838
+ const distance = Vector3.Dot(point.position.subtract(orientedEdgeStart), selectedFaceEdge.inward);
839
+ if (Math.abs(distance - bevelAmount) < PositionEpsilon * 10) {
840
+ const t = Vector3.Dot(point.position.subtract(edgeStart), axis);
841
+ if (t >= -PositionEpsilon && t <= edgeLength + PositionEpsilon) {
842
+ pointsOnLine.push({ point, t: Math.min(edgeLength, Math.max(0, t)) });
843
+ }
844
+ }
845
+ }
846
+ if (pointsOnLine.length < 2) {
847
+ continue;
848
+ }
849
+ pointsOnLine.sort((a, b) => a.t - b.t);
850
+ const minPoint = pointsOnLine[0].point.position;
851
+ const maxPoint = pointsOnLine[pointsOnLine.length - 1].point.position;
852
+ const minNormal = pointsOnLine[0].point.normal;
853
+ const maxNormal = pointsOnLine[pointsOnLine.length - 1].point.normal;
854
+ const minAttributes = pointsOnLine[0].point.attributes;
855
+ const maxAttributes = pointsOnLine[pointsOnLine.length - 1].point.attributes;
856
+ const segment = {
857
+ edgeKey: selectedFaceEdge.key,
858
+ faceIndex,
859
+ inward: selectedFaceEdge.inward,
860
+ tMin: pointsOnLine[0].t,
861
+ tMax: pointsOnLine[pointsOnLine.length - 1].t,
862
+ minPoint,
863
+ maxPoint,
864
+ minNormal,
865
+ maxNormal,
866
+ minAttributes,
867
+ maxAttributes,
868
+ materialIndex: face.materialIndex,
869
+ };
870
+ faceEdgeSegments.set(`${faceIndex}|${selectedFaceEdge.key}`, segment);
871
+ addCapPoint(edge.v0, minPoint, pointsOnLine[0].point.normal, minAttributes, face.materialIndex);
872
+ addCapPoint(edge.v1, maxPoint, pointsOnLine[pointsOnLine.length - 1].point.normal, maxAttributes, face.materialIndex);
873
+ }
874
+ }
875
+ for (const edgeKey of Array.from(selectedEdges)) {
876
+ const edge = topology.edges.get(edgeKey);
877
+ const face0 = edge.faces[0].faceIndex;
878
+ const face1 = edge.faces[1].faceIndex;
879
+ const segment0 = faceEdgeSegments.get(`${face0}|${edgeKey}`);
880
+ const segment1 = faceEdgeSegments.get(`${face1}|${edgeKey}`);
881
+ if (!segment0 || !segment1) {
882
+ continue;
883
+ }
884
+ const edgeStart = topology.positions[edge.v0];
885
+ const edgeEnd = topology.positions[edge.v1];
886
+ const axis = edgeEnd.subtract(edgeStart);
887
+ const edgeLength = axis.length();
888
+ if (edgeLength < PositionEpsilon) {
889
+ continue;
890
+ }
891
+ axis.normalize();
892
+ const tStart = Math.max(segment0.tMin, segment1.tMin);
893
+ const tEnd = Math.min(segment0.tMax, segment1.tMax);
894
+ if (tEnd - tStart <= PositionEpsilon) {
895
+ continue;
896
+ }
897
+ edgeIntervals.set(edgeKey, { tStart, tEnd });
898
+ }
899
+ for (const preparedFace of preparedFaces) {
900
+ for (const selectedFaceEdge of preparedFace.selectedFaceEdges) {
901
+ const edge = topology.edges.get(selectedFaceEdge.key);
902
+ const interval = edgeIntervals.get(selectedFaceEdge.key);
903
+ const segment = faceEdgeSegments.get(`${preparedFace.faceIndex}|${selectedFaceEdge.key}`);
904
+ if (!interval || !segment) {
905
+ continue;
906
+ }
907
+ const edgeStart = topology.positions[edge.v0];
908
+ const edgeEnd = topology.positions[edge.v1];
909
+ const axis = edgeEnd.subtract(edgeStart);
910
+ const edgeLength = axis.length();
911
+ if (edgeLength < PositionEpsilon) {
912
+ continue;
913
+ }
914
+ axis.normalize();
915
+ for (const t of [interval.tStart, interval.tEnd]) {
916
+ if (t <= segment.tMin + PositionEpsilon || t >= segment.tMax - PositionEpsilon) {
917
+ continue;
918
+ }
919
+ _InsertPointOnPolygonBoundary(preparedFace.polygon, edgeStart.add(axis.scale(t)).addInPlace(segment.inward.scale(bevelAmount)));
920
+ }
921
+ }
922
+ }
923
+ const emittedMergedFaces = new Set();
924
+ const preparedFacesByPlane = new Map();
925
+ for (const preparedFace of preparedFaces) {
926
+ if (!preparedFace.selectedFaceEdges.length || !preparedFace.isFlat) {
927
+ continue;
928
+ }
929
+ const face = topology.faces[preparedFace.faceIndex];
930
+ const distance = Vector3.Dot(topology.positions[face.indices[0]], face.normal);
931
+ const planeKey = `${_PositionKey(face.normal.x, face.normal.y, face.normal.z)}:${_Quantize(distance)}`;
932
+ let group = preparedFacesByPlane.get(planeKey);
933
+ if (!group) {
934
+ group = [];
935
+ preparedFacesByPlane.set(planeKey, group);
936
+ }
937
+ group.push(preparedFace);
938
+ }
939
+ for (const group of Array.from(preparedFacesByPlane.values())) {
940
+ if (group.length < 2) {
941
+ continue;
942
+ }
943
+ const mergedPolygon = _BuildMergedBoundaryPolygon(group.map((preparedFace) => preparedFace.polygon));
944
+ if (!mergedPolygon || mergedPolygon.length < 3) {
945
+ continue;
946
+ }
947
+ const face = topology.faces[group[0].faceIndex];
948
+ addFacePolygon(mergedPolygon, face.normal, `face-flat:${_PositionKey(face.normal.x, face.normal.y, face.normal.z)}`, true);
949
+ for (const preparedFace of group) {
950
+ emittedMergedFaces.add(preparedFace.faceIndex);
951
+ }
952
+ }
953
+ for (const preparedFace of preparedFaces) {
954
+ if (emittedMergedFaces.has(preparedFace.faceIndex)) {
955
+ continue;
956
+ }
957
+ const face = topology.faces[preparedFace.faceIndex];
958
+ const smoothingGroup = preparedFace.selectedFaceEdges.length && !preparedFace.isFlat ? "smooth" : `face-flat:${_PositionKey(face.normal.x, face.normal.y, face.normal.z)}`;
959
+ if (preparedFace.polygon.length >= 3) {
960
+ if (smoothingGroup === "smooth") {
961
+ addFacePolygon(preparedFace.polygon, face.normal, smoothingGroup, false);
962
+ }
963
+ else {
964
+ for (let index = 1; index < preparedFace.polygon.length - 1; index++) {
965
+ addTriangle(preparedFace.polygon[0].position, preparedFace.polygon[index].position, preparedFace.polygon[index + 1].position, face.normal, face.normal, face.normal, face.normal, smoothingGroup, preparedFace.polygon[0].attributes, preparedFace.polygon[index].attributes, preparedFace.polygon[index + 1].attributes, face.materialIndex);
966
+ }
967
+ }
968
+ }
969
+ }
970
+ for (const edgeKey of Array.from(selectedEdges)) {
971
+ const edge = topology.edges.get(edgeKey);
972
+ const face0 = edge.faces[0].faceIndex;
973
+ const face1 = edge.faces[1].faceIndex;
974
+ const segment0 = faceEdgeSegments.get(`${face0}|${edgeKey}`);
975
+ const segment1 = faceEdgeSegments.get(`${face1}|${edgeKey}`);
976
+ const interval = edgeIntervals.get(edgeKey);
977
+ if (!segment0 || !segment1 || !interval) {
978
+ continue;
979
+ }
980
+ const edgeStart = topology.positions[edge.v0];
981
+ const edgeEnd = topology.positions[edge.v1];
982
+ const axis = edgeEnd.subtract(edgeStart);
983
+ const edgeLength = axis.length();
984
+ if (edgeLength < PositionEpsilon) {
985
+ continue;
986
+ }
987
+ axis.normalize();
988
+ const { tStart, tEnd } = interval;
989
+ const faceNormal0 = topology.faces[segment0.faceIndex].normal;
990
+ const faceNormal1 = topology.faces[segment1.faceIndex].normal;
991
+ const segment0Start = _InterpolateSegmentPoint(segment0, tStart);
992
+ const segment1Start = _InterpolateSegmentPoint(segment1, tStart);
993
+ const segment0End = _InterpolateSegmentPoint(segment0, tEnd);
994
+ const segment1End = _InterpolateSegmentPoint(segment1, tEnd);
995
+ const segment0StartNormal = _InterpolateSegmentNormal(segment0, tStart);
996
+ const segment1StartNormal = _InterpolateSegmentNormal(segment1, tStart);
997
+ const segment0EndNormal = _InterpolateSegmentNormal(segment0, tEnd);
998
+ const segment1EndNormal = _InterpolateSegmentNormal(segment1, tEnd);
999
+ const segment0StartAttributes = _InterpolateSegmentAttributes(segment0, tStart);
1000
+ const segment1StartAttributes = _InterpolateSegmentAttributes(segment1, tStart);
1001
+ const segment0EndAttributes = _InterpolateSegmentAttributes(segment0, tEnd);
1002
+ const segment1EndAttributes = _InterpolateSegmentAttributes(segment1, tEnd);
1003
+ const centerStart = segment0Start
1004
+ .subtract(faceNormal0.scale(bevelAmount))
1005
+ .addInPlace(segment1Start.subtract(faceNormal1.scale(bevelAmount)))
1006
+ .scaleInPlace(0.5);
1007
+ const centerEnd = segment0End
1008
+ .subtract(faceNormal0.scale(bevelAmount))
1009
+ .addInPlace(segment1End.subtract(faceNormal1.scale(bevelAmount)))
1010
+ .scaleInPlace(0.5);
1011
+ const startProfilePoints = [];
1012
+ const endProfilePoints = [];
1013
+ const startProfileNormals = [];
1014
+ const endProfileNormals = [];
1015
+ const startProfileAttributes = [];
1016
+ const endProfileAttributes = [];
1017
+ for (let segmentIndex = 0; segmentIndex <= segments; segmentIndex++) {
1018
+ const profileAmount = segmentIndex / segments;
1019
+ const profileNormal = _SlerpDirections(faceNormal0, faceNormal1, profileAmount);
1020
+ const startNormal = _SlerpDirections(segment0StartNormal, segment1StartNormal, profileAmount);
1021
+ const endNormal = _SlerpDirections(segment0EndNormal, segment1EndNormal, profileAmount);
1022
+ const startAttributes = _InterpolateAttributes(segment0StartAttributes, segment1StartAttributes, profileAmount);
1023
+ const endAttributes = _InterpolateAttributes(segment0EndAttributes, segment1EndAttributes, profileAmount);
1024
+ let startPoint = centerStart.add(profileNormal.scale(bevelAmount));
1025
+ let endPoint = centerEnd.add(profileNormal.scale(bevelAmount));
1026
+ if (segmentIndex === 0) {
1027
+ startPoint = segment0Start;
1028
+ endPoint = segment0End;
1029
+ }
1030
+ else if (segmentIndex === segments) {
1031
+ startPoint = segment1Start;
1032
+ endPoint = segment1End;
1033
+ }
1034
+ startProfilePoints.push(startPoint);
1035
+ endProfilePoints.push(endPoint);
1036
+ startProfileNormals.push(startNormal);
1037
+ endProfileNormals.push(endNormal);
1038
+ startProfileAttributes.push(startAttributes);
1039
+ endProfileAttributes.push(endAttributes);
1040
+ addCapPoint(edge.v0, startPoint, startNormal, startAttributes, segment0.materialIndex);
1041
+ addCapPoint(edge.v1, endPoint, endNormal, endAttributes, segment0.materialIndex);
1042
+ }
1043
+ for (let segmentIndex = 0; segmentIndex < segments; segmentIndex++) {
1044
+ const normalTarget = _SlerpDirections(faceNormal0, faceNormal1, (segmentIndex + 0.5) / segments);
1045
+ addTriangle(startProfilePoints[segmentIndex], endProfilePoints[segmentIndex], endProfilePoints[segmentIndex + 1], normalTarget, startProfileNormals[segmentIndex], endProfileNormals[segmentIndex], endProfileNormals[segmentIndex + 1], "smooth", startProfileAttributes[segmentIndex], endProfileAttributes[segmentIndex], endProfileAttributes[segmentIndex + 1], segment0.materialIndex);
1046
+ addTriangle(startProfilePoints[segmentIndex], endProfilePoints[segmentIndex + 1], startProfilePoints[segmentIndex + 1], normalTarget, startProfileNormals[segmentIndex], endProfileNormals[segmentIndex + 1], startProfileNormals[segmentIndex + 1], "smooth", startProfileAttributes[segmentIndex], endProfileAttributes[segmentIndex + 1], startProfileAttributes[segmentIndex + 1], segment0.materialIndex);
1047
+ }
1048
+ }
1049
+ const addSphericalCornerPatch = (vertexIndex) => {
1050
+ if ((selectedEdgeCountPerVertex.get(vertexIndex) ?? 0) < 3) {
1051
+ return false;
1052
+ }
1053
+ const incidentNormals = [];
1054
+ const vertexPosition = topology.positions[vertexIndex];
1055
+ const cornerCapPoints = capPoints.get(vertexIndex) ?? [];
1056
+ const cornerAttributes = _AverageAttributes(cornerCapPoints.map((point) => _GetCapPointAttributes(point)), attributeLength);
1057
+ const materialIndex = cornerCapPoints.length ? _GetCapPointMaterialIndex(cornerCapPoints[0]) : 0;
1058
+ for (const edgeKey of Array.from(selectedEdges)) {
1059
+ const edge = topology.edges.get(edgeKey);
1060
+ if (edge.v0 !== vertexIndex && edge.v1 !== vertexIndex) {
1061
+ continue;
1062
+ }
1063
+ for (const edgeFace of edge.faces) {
1064
+ _AddUniqueNormal(incidentNormals, topology.faces[edgeFace.faceIndex].normal);
1065
+ }
1066
+ }
1067
+ if (incidentNormals.length !== 3) {
1068
+ return false;
1069
+ }
1070
+ const averageNormal = incidentNormals[0].add(incidentNormals[1]).addInPlace(incidentNormals[2]).normalize();
1071
+ const tangent = incidentNormals[0].subtract(averageNormal.scale(Vector3.Dot(incidentNormals[0], averageNormal))).normalize();
1072
+ const bitangent = Vector3.Cross(averageNormal, tangent).normalize();
1073
+ incidentNormals.sort((a, b) => Math.atan2(Vector3.Dot(a, bitangent), Vector3.Dot(a, tangent)) - Math.atan2(Vector3.Dot(b, bitangent), Vector3.Dot(b, tangent)));
1074
+ const distances = incidentNormals.map((normal) => Vector3.Dot(vertexPosition, normal) - bevelAmount);
1075
+ const center = _SolveThreePlaneIntersection(incidentNormals, distances);
1076
+ if (!center) {
1077
+ return false;
1078
+ }
1079
+ const rows = [];
1080
+ for (let rowIndex = 0; rowIndex <= segments; rowIndex++) {
1081
+ const rowAmount = rowIndex / segments;
1082
+ const left = _SlerpDirections(incidentNormals[0], incidentNormals[1], rowAmount);
1083
+ const right = _SlerpDirections(incidentNormals[0], incidentNormals[2], rowAmount);
1084
+ const row = [];
1085
+ for (let columnIndex = 0; columnIndex <= rowIndex; columnIndex++) {
1086
+ const columnAmount = rowIndex === 0 ? 0 : columnIndex / rowIndex;
1087
+ const direction = rowIndex === 0 ? incidentNormals[0].clone() : _SlerpDirections(left, right, columnAmount);
1088
+ row.push({
1089
+ position: center.add(direction.scale(bevelAmount)),
1090
+ normal: direction,
1091
+ attributes: cornerAttributes,
1092
+ materialIndex,
1093
+ });
1094
+ }
1095
+ rows.push(row);
1096
+ }
1097
+ for (let rowIndex = 0; rowIndex < segments; rowIndex++) {
1098
+ const row = rows[rowIndex];
1099
+ const nextRow = rows[rowIndex + 1];
1100
+ for (let columnIndex = 0; columnIndex < row.length; columnIndex++) {
1101
+ const targetNormal0 = row[columnIndex].position.subtract(center).normalize();
1102
+ const targetNormal1 = nextRow[columnIndex].position.subtract(center).normalize();
1103
+ const targetNormal2 = nextRow[columnIndex + 1].position.subtract(center).normalize();
1104
+ const targetNormal = targetNormal0.addInPlace(targetNormal1).addInPlace(targetNormal2).normalize();
1105
+ addTriangle(row[columnIndex].position, nextRow[columnIndex].position, nextRow[columnIndex + 1].position, targetNormal, targetNormal0, targetNormal1, targetNormal2, "smooth", row[columnIndex].attributes, nextRow[columnIndex].attributes, nextRow[columnIndex + 1].attributes, materialIndex);
1106
+ if (columnIndex < row.length - 1) {
1107
+ const secondTargetNormal0 = row[columnIndex].position.subtract(center).normalize();
1108
+ const secondTargetNormal1 = nextRow[columnIndex + 1].position.subtract(center).normalize();
1109
+ const secondTargetNormal2 = row[columnIndex + 1].position.subtract(center).normalize();
1110
+ const secondTargetNormal = secondTargetNormal0.add(secondTargetNormal1).addInPlace(secondTargetNormal2).normalize();
1111
+ addTriangle(row[columnIndex].position, nextRow[columnIndex + 1].position, row[columnIndex + 1].position, secondTargetNormal, secondTargetNormal0, secondTargetNormal1, secondTargetNormal2, "smooth", row[columnIndex].attributes, nextRow[columnIndex + 1].attributes, row[columnIndex + 1].attributes, materialIndex);
1112
+ }
1113
+ }
1114
+ }
1115
+ return true;
1116
+ };
1117
+ const verticesWithSphericalPatch = new Set();
1118
+ for (const vertexIndex of Array.from(selectedVertices)) {
1119
+ if (addSphericalCornerPatch(vertexIndex)) {
1120
+ verticesWithSphericalPatch.add(vertexIndex);
1121
+ }
1122
+ }
1123
+ for (const [vertexIndex, points] of Array.from(capPoints)) {
1124
+ if (points.length < 3) {
1125
+ continue;
1126
+ }
1127
+ if (verticesWithSphericalPatch.has(vertexIndex)) {
1128
+ continue;
1129
+ }
1130
+ const center = new Vector3();
1131
+ for (const point of points) {
1132
+ center.addInPlace(_GetCapPointPosition(point));
1133
+ }
1134
+ center.scaleInPlace(1 / points.length);
1135
+ let normal = new Vector3();
1136
+ const incidentFaces = topology.vertexFaces.get(vertexIndex) || [];
1137
+ for (const faceIndex of incidentFaces) {
1138
+ normal.addInPlace(topology.faces[faceIndex].normal);
1139
+ }
1140
+ if (normal.lengthSquared() < NormalEpsilon) {
1141
+ normal = topology.positions[vertexIndex].subtract(center);
1142
+ }
1143
+ if (normal.lengthSquared() < NormalEpsilon) {
1144
+ continue;
1145
+ }
1146
+ normal.normalize();
1147
+ const reference = Math.abs(normal.y) < 0.9 ? Vector3.Up() : Vector3.Right();
1148
+ const tangent = Vector3.Cross(reference, normal).normalize();
1149
+ const bitangent = Vector3.Cross(normal, tangent).normalize();
1150
+ const sortedPoints = points.slice().sort((a, b) => {
1151
+ const da = _GetCapPointPosition(a).subtract(center);
1152
+ const db = _GetCapPointPosition(b).subtract(center);
1153
+ const angleA = Math.atan2(Vector3.Dot(da, bitangent), Vector3.Dot(da, tangent));
1154
+ const angleB = Math.atan2(Vector3.Dot(db, bitangent), Vector3.Dot(db, tangent));
1155
+ return angleA - angleB;
1156
+ });
1157
+ if ((selectedEdgeCountPerVertex.get(vertexIndex) ?? 0) === 2) {
1158
+ const polygonPoints = sortedPoints.map((point) => ({
1159
+ position: _GetCapPointPosition(point),
1160
+ normal: _GetCapPointNormal(point, normal),
1161
+ attributes: _GetCapPointAttributes(point),
1162
+ materialIndex: _GetCapPointMaterialIndex(point),
1163
+ }));
1164
+ const addPatchTriangle = (point0, point1, point2) => {
1165
+ const targetNormal = _NormalizeNormalOrFallback(point0.normal.add(point1.normal).addInPlace(point2.normal), normal);
1166
+ addTriangle(point0.position, point1.position, point2.position, targetNormal, point0.normal, point1.normal, point2.normal, "smooth", point0.attributes, point1.attributes, point2.attributes, point0.materialIndex);
1167
+ };
1168
+ while (polygonPoints.length > 3) {
1169
+ let bestIndex = -1;
1170
+ let bestScore = Number.MAX_VALUE;
1171
+ for (let index = 0; index < polygonPoints.length; index++) {
1172
+ const previous = polygonPoints[(index + polygonPoints.length - 1) % polygonPoints.length];
1173
+ const current = polygonPoints[index];
1174
+ const next = polygonPoints[(index + 1) % polygonPoints.length];
1175
+ const edge0 = current.position.subtract(previous.position);
1176
+ const edge1 = next.position.subtract(previous.position);
1177
+ const triangleNormal = Vector3.Cross(edge0, edge1);
1178
+ if (triangleNormal.lengthSquared() < TriangleAreaEpsilon) {
1179
+ continue;
1180
+ }
1181
+ const score = previous.position.subtract(next.position).lengthSquared();
1182
+ if (score < bestScore) {
1183
+ bestScore = score;
1184
+ bestIndex = index;
1185
+ }
1186
+ }
1187
+ if (bestIndex === -1) {
1188
+ break;
1189
+ }
1190
+ addPatchTriangle(polygonPoints[(bestIndex + polygonPoints.length - 1) % polygonPoints.length], polygonPoints[bestIndex], polygonPoints[(bestIndex + 1) % polygonPoints.length]);
1191
+ polygonPoints.splice(bestIndex, 1);
1192
+ }
1193
+ if (polygonPoints.length === 3) {
1194
+ addPatchTriangle(polygonPoints[0], polygonPoints[1], polygonPoints[2]);
1195
+ }
1196
+ continue;
1197
+ }
1198
+ const centerAttributes = _AverageAttributes(sortedPoints.map((point) => _GetCapPointAttributes(point)), attributeLength);
1199
+ const centerMaterialIndex = sortedPoints.length ? _GetCapPointMaterialIndex(sortedPoints[0]) : 0;
1200
+ for (let index = 0; index < sortedPoints.length; index++) {
1201
+ const point0 = sortedPoints[index];
1202
+ const point1 = sortedPoints[(index + 1) % sortedPoints.length];
1203
+ const point0Position = _GetCapPointPosition(point0);
1204
+ const point1Position = _GetCapPointPosition(point1);
1205
+ addTriangle(center, point0Position, point1Position, normal, normal, _GetCapPointNormal(point0, normal), _GetCapPointNormal(point1, normal), "smooth", centerAttributes, _GetCapPointAttributes(point0), _GetCapPointAttributes(point1), centerMaterialIndex);
1206
+ }
1207
+ }
1208
+ if (!outputPositions.length || !outputIndices.length) {
1209
+ return _CloneVertexData(vertexData);
1210
+ }
1211
+ const result = new VertexData();
1212
+ result.positions = outputPositions;
1213
+ result.normals = [];
1214
+ for (let index = 0; index < outputNormals.length; index++) {
1215
+ const normal = outputNormals[index];
1216
+ normal.normalize();
1217
+ result.normals.push(normal.x, normal.y, normal.z);
1218
+ }
1219
+ _AssignAttributeOutputs(result, attributeDescriptors, outputVertexAttributes);
1220
+ const materialResult = _BuildMaterialInfoResult(vertexData, outputIndices, outputTriangleMaterialIndices, outputPositions.length / 3);
1221
+ result.indices = materialResult.indices;
1222
+ result.materialInfos = materialResult.materialInfos;
1223
+ return result;
1224
+ }
1225
+ /**
1226
+ * Block used to bevel sharp edges in a geometry.
1227
+ */
1228
+ export class BevelBlock extends NodeGeometryBlock {
1229
+ /**
1230
+ * Creates a new BevelBlock.
1231
+ * @param name defines the block name
1232
+ */
1233
+ constructor(name) {
1234
+ super(name);
1235
+ /**
1236
+ * Gets or sets a boolean indicating that this block can evaluate context.
1237
+ * Build performance is improved when this value is set to false as the system will cache values instead of reevaluating everything per context change.
1238
+ */
1239
+ this.evaluateContext = false;
1240
+ this.registerInput("geometry", NodeGeometryBlockConnectionPointTypes.Geometry);
1241
+ this.registerInput("amount", NodeGeometryBlockConnectionPointTypes.Float, true, 0.1, 0, 1);
1242
+ this.registerInput("segments", NodeGeometryBlockConnectionPointTypes.Int, true, 1, 1, 64);
1243
+ this.registerInput("angle", NodeGeometryBlockConnectionPointTypes.Float, true, 30, 0, 180);
1244
+ this.registerOutput("output", NodeGeometryBlockConnectionPointTypes.Geometry);
1245
+ }
1246
+ /**
1247
+ * Gets the current class name.
1248
+ * @returns the class name
1249
+ */
1250
+ getClassName() {
1251
+ return "BevelBlock";
1252
+ }
1253
+ /**
1254
+ * Gets the geometry input component.
1255
+ */
1256
+ get geometry() {
1257
+ return this._inputs[0];
1258
+ }
1259
+ /**
1260
+ * Gets the bevel amount input component.
1261
+ */
1262
+ get amount() {
1263
+ return this._inputs[1];
1264
+ }
1265
+ /**
1266
+ * Gets the bevel segment count input component.
1267
+ */
1268
+ get segments() {
1269
+ return this._inputs[2];
1270
+ }
1271
+ /**
1272
+ * Gets the angle threshold input component in degrees.
1273
+ */
1274
+ get angle() {
1275
+ return this._inputs[3];
1276
+ }
1277
+ /**
1278
+ * Gets the geometry output component.
1279
+ */
1280
+ get output() {
1281
+ return this._outputs[0];
1282
+ }
1283
+ _buildBlock(state) {
1284
+ const func = (state) => {
1285
+ const source = this.geometry.getConnectedValue(state);
1286
+ if (!source || !source.positions) {
1287
+ return null;
1288
+ }
1289
+ const amount = Math.min(1, Math.max(0, this.amount.getConnectedValue(state) ?? 0.1));
1290
+ const segments = Math.min(64, Math.max(1, Math.floor(this.segments.getConnectedValue(state) ?? 1)));
1291
+ const angleDegrees = Math.max(0, Math.min(180, this.angle.getConnectedValue(state) ?? 30));
1292
+ const angle = (angleDegrees * Math.PI) / 180;
1293
+ return _BevelVertexData(source, amount, segments, angle);
1294
+ };
1295
+ if (this.evaluateContext) {
1296
+ this.output._storedFunction = func;
1297
+ }
1298
+ else {
1299
+ this.output._storedFunction = null;
1300
+ this.output._storedValue = func(state);
1301
+ }
1302
+ }
1303
+ _dumpPropertiesCode() {
1304
+ return super._dumpPropertiesCode() + `${this._codeVariableName}.evaluateContext = ${this.evaluateContext ? "true" : "false"};\n`;
1305
+ }
1306
+ /**
1307
+ * Serializes this block in a JSON representation.
1308
+ * @returns the serialized block object
1309
+ */
1310
+ serialize() {
1311
+ const serializationObject = super.serialize();
1312
+ serializationObject.evaluateContext = this.evaluateContext;
1313
+ return serializationObject;
1314
+ }
1315
+ /** @internal */
1316
+ _deserialize(serializationObject) {
1317
+ super._deserialize(serializationObject);
1318
+ if (serializationObject.evaluateContext !== undefined) {
1319
+ this.evaluateContext = serializationObject.evaluateContext;
1320
+ }
1321
+ }
1322
+ }
1323
+ __decorate([
1324
+ editableInPropertyPage("Evaluate context", 0 /* PropertyTypeForEdition.Boolean */, "ADVANCED", { embedded: true, notifiers: { rebuild: true } })
1325
+ ], BevelBlock.prototype, "evaluateContext", void 0);
1326
+ RegisterClass("BABYLON.BevelBlock", BevelBlock);
1327
+ //# sourceMappingURL=bevelBlock.js.map