brep-io-kernel 1.0.0-ci.9
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.
- package/LICENSE.md +32 -0
- package/README.md +154 -0
- package/dist-kernel/brep-kernel.js +74699 -0
- package/package.json +58 -0
- package/src/BREP/AssemblyComponent.js +42 -0
- package/src/BREP/BREP.js +43 -0
- package/src/BREP/BetterSolid.js +805 -0
- package/src/BREP/Edge.js +103 -0
- package/src/BREP/Extrude.js +403 -0
- package/src/BREP/Face.js +187 -0
- package/src/BREP/MeshRepairer.js +634 -0
- package/src/BREP/OffsetShellSolid.js +614 -0
- package/src/BREP/PointCloudWrap.js +302 -0
- package/src/BREP/Revolve.js +345 -0
- package/src/BREP/SolidMethods/authoring.js +112 -0
- package/src/BREP/SolidMethods/booleanOps.js +230 -0
- package/src/BREP/SolidMethods/chamfer.js +122 -0
- package/src/BREP/SolidMethods/edgeResolution.js +25 -0
- package/src/BREP/SolidMethods/fillet.js +792 -0
- package/src/BREP/SolidMethods/index.js +72 -0
- package/src/BREP/SolidMethods/io.js +105 -0
- package/src/BREP/SolidMethods/lifecycle.js +103 -0
- package/src/BREP/SolidMethods/manifoldOps.js +375 -0
- package/src/BREP/SolidMethods/meshCleanup.js +2512 -0
- package/src/BREP/SolidMethods/meshQueries.js +264 -0
- package/src/BREP/SolidMethods/metadata.js +106 -0
- package/src/BREP/SolidMethods/metrics.js +51 -0
- package/src/BREP/SolidMethods/transforms.js +361 -0
- package/src/BREP/SolidMethods/visualize.js +508 -0
- package/src/BREP/SolidShared.js +26 -0
- package/src/BREP/Sweep.js +1596 -0
- package/src/BREP/Tube.js +857 -0
- package/src/BREP/Vertex.js +43 -0
- package/src/BREP/applyBooleanOperation.js +704 -0
- package/src/BREP/boundsUtils.js +48 -0
- package/src/BREP/chamfer.js +551 -0
- package/src/BREP/edgePolylineUtils.js +85 -0
- package/src/BREP/fillets/common.js +388 -0
- package/src/BREP/fillets/fillet.js +1422 -0
- package/src/BREP/fillets/filletGeometry.js +15 -0
- package/src/BREP/fillets/inset.js +389 -0
- package/src/BREP/fillets/offsetHelper.js +143 -0
- package/src/BREP/fillets/outset.js +88 -0
- package/src/BREP/helix.js +193 -0
- package/src/BREP/meshToBrep.js +234 -0
- package/src/BREP/primitives.js +279 -0
- package/src/BREP/setupManifold.js +71 -0
- package/src/BREP/threadGeometry.js +1120 -0
- package/src/BREP/triangleUtils.js +8 -0
- package/src/BREP/triangulate.js +608 -0
- package/src/FeatureRegistry.js +183 -0
- package/src/PartHistory.js +1132 -0
- package/src/UI/AccordionWidget.js +292 -0
- package/src/UI/CADmaterials.js +850 -0
- package/src/UI/EnvMonacoEditor.js +522 -0
- package/src/UI/FloatingWindow.js +396 -0
- package/src/UI/HistoryWidget.js +457 -0
- package/src/UI/MainToolbar.js +131 -0
- package/src/UI/ModelLibraryView.js +194 -0
- package/src/UI/OrthoCameraIdle.js +206 -0
- package/src/UI/PluginsWidget.js +280 -0
- package/src/UI/SceneListing.js +606 -0
- package/src/UI/SelectionFilter.js +629 -0
- package/src/UI/ViewCube.js +389 -0
- package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +329 -0
- package/src/UI/assembly/AssemblyConstraintControlsWidget.js +282 -0
- package/src/UI/assembly/AssemblyConstraintsWidget.css +292 -0
- package/src/UI/assembly/AssemblyConstraintsWidget.js +1373 -0
- package/src/UI/assembly/constraintFaceUtils.js +115 -0
- package/src/UI/assembly/constraintHighlightUtils.js +70 -0
- package/src/UI/assembly/constraintLabelUtils.js +31 -0
- package/src/UI/assembly/constraintPointUtils.js +64 -0
- package/src/UI/assembly/constraintSelectionUtils.js +185 -0
- package/src/UI/assembly/constraintStatusUtils.js +142 -0
- package/src/UI/componentSelectorModal.js +240 -0
- package/src/UI/controls/CombinedTransformControls.js +386 -0
- package/src/UI/dialogs.js +351 -0
- package/src/UI/expressionsManager.js +100 -0
- package/src/UI/featureDialogWidgets/booleanField.js +25 -0
- package/src/UI/featureDialogWidgets/booleanOperationField.js +97 -0
- package/src/UI/featureDialogWidgets/buttonField.js +45 -0
- package/src/UI/featureDialogWidgets/componentSelectorField.js +102 -0
- package/src/UI/featureDialogWidgets/defaultField.js +23 -0
- package/src/UI/featureDialogWidgets/fileField.js +66 -0
- package/src/UI/featureDialogWidgets/index.js +34 -0
- package/src/UI/featureDialogWidgets/numberField.js +165 -0
- package/src/UI/featureDialogWidgets/optionsField.js +33 -0
- package/src/UI/featureDialogWidgets/referenceSelectionField.js +208 -0
- package/src/UI/featureDialogWidgets/stringField.js +24 -0
- package/src/UI/featureDialogWidgets/textareaField.js +28 -0
- package/src/UI/featureDialogWidgets/threadDesignationField.js +160 -0
- package/src/UI/featureDialogWidgets/transformField.js +252 -0
- package/src/UI/featureDialogWidgets/utils.js +43 -0
- package/src/UI/featureDialogWidgets/vec3Field.js +133 -0
- package/src/UI/featureDialogs.js +1414 -0
- package/src/UI/fileManagerWidget.js +615 -0
- package/src/UI/history/HistoryCollectionWidget.js +1294 -0
- package/src/UI/history/historyCollectionWidget.css.js +257 -0
- package/src/UI/history/historyDisplayInfo.js +133 -0
- package/src/UI/mobile.js +28 -0
- package/src/UI/objectDump.js +442 -0
- package/src/UI/pmi/AnnotationCollectionWidget.js +120 -0
- package/src/UI/pmi/AnnotationHistory.js +353 -0
- package/src/UI/pmi/AnnotationRegistry.js +90 -0
- package/src/UI/pmi/BaseAnnotation.js +269 -0
- package/src/UI/pmi/LabelOverlay.css +102 -0
- package/src/UI/pmi/LabelOverlay.js +191 -0
- package/src/UI/pmi/PMIMode.js +1550 -0
- package/src/UI/pmi/PMIViewsWidget.js +1098 -0
- package/src/UI/pmi/annUtils.js +729 -0
- package/src/UI/pmi/dimensions/AngleDimensionAnnotation.js +647 -0
- package/src/UI/pmi/dimensions/ExplodeBodyAnnotation.js +507 -0
- package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +462 -0
- package/src/UI/pmi/dimensions/LeaderAnnotation.js +403 -0
- package/src/UI/pmi/dimensions/LinearDimensionAnnotation.js +532 -0
- package/src/UI/pmi/dimensions/NoteAnnotation.js +110 -0
- package/src/UI/pmi/dimensions/RadialDimensionAnnotation.js +659 -0
- package/src/UI/pmi/pmiStyle.js +44 -0
- package/src/UI/sketcher/SketchMode3D.js +4095 -0
- package/src/UI/sketcher/dimensions.js +674 -0
- package/src/UI/sketcher/glyphs.js +236 -0
- package/src/UI/sketcher/highlights.js +60 -0
- package/src/UI/toolbarButtons/aboutButton.js +5 -0
- package/src/UI/toolbarButtons/exportButton.js +609 -0
- package/src/UI/toolbarButtons/flatPatternButton.js +307 -0
- package/src/UI/toolbarButtons/importButton.js +160 -0
- package/src/UI/toolbarButtons/inspectorToggleButton.js +12 -0
- package/src/UI/toolbarButtons/metadataButton.js +1063 -0
- package/src/UI/toolbarButtons/orientToFaceButton.js +114 -0
- package/src/UI/toolbarButtons/registerDefaultButtons.js +46 -0
- package/src/UI/toolbarButtons/saveButton.js +99 -0
- package/src/UI/toolbarButtons/scriptRunnerButton.js +302 -0
- package/src/UI/toolbarButtons/testsButton.js +26 -0
- package/src/UI/toolbarButtons/undoRedoButtons.js +25 -0
- package/src/UI/toolbarButtons/wireframeToggleButton.js +5 -0
- package/src/UI/toolbarButtons/zoomToFitButton.js +5 -0
- package/src/UI/triangleDebuggerWindow.js +945 -0
- package/src/UI/viewer.js +4228 -0
- package/src/assemblyConstraints/AssemblyConstraintHistory.js +1576 -0
- package/src/assemblyConstraints/AssemblyConstraintRegistry.js +120 -0
- package/src/assemblyConstraints/BaseAssemblyConstraint.js +66 -0
- package/src/assemblyConstraints/constraintExpressionUtils.js +35 -0
- package/src/assemblyConstraints/constraintUtils/parallelAlignment.js +676 -0
- package/src/assemblyConstraints/constraints/AngleConstraint.js +485 -0
- package/src/assemblyConstraints/constraints/CoincidentConstraint.js +194 -0
- package/src/assemblyConstraints/constraints/DistanceConstraint.js +616 -0
- package/src/assemblyConstraints/constraints/FixedConstraint.js +78 -0
- package/src/assemblyConstraints/constraints/ParallelConstraint.js +252 -0
- package/src/assemblyConstraints/constraints/TouchAlignConstraint.js +961 -0
- package/src/core/entities/HistoryCollectionBase.js +72 -0
- package/src/core/entities/ListEntityBase.js +109 -0
- package/src/core/entities/schemaProcesser.js +121 -0
- package/src/exporters/sheetMetalFlatPattern.js +659 -0
- package/src/exporters/sheetMetalUnfold.js +862 -0
- package/src/exporters/step.js +1135 -0
- package/src/exporters/threeMF.js +575 -0
- package/src/features/assemblyComponent/AssemblyComponentFeature.js +780 -0
- package/src/features/boolean/BooleanFeature.js +94 -0
- package/src/features/chamfer/ChamferFeature.js +116 -0
- package/src/features/datium/DatiumFeature.js +80 -0
- package/src/features/edgeFeatureUtils.js +41 -0
- package/src/features/extrude/ExtrudeFeature.js +143 -0
- package/src/features/fillet/FilletFeature.js +197 -0
- package/src/features/helix/HelixFeature.js +405 -0
- package/src/features/hole/HoleFeature.js +1050 -0
- package/src/features/hole/screwClearance.js +86 -0
- package/src/features/hole/threadDesignationCatalog.js +149 -0
- package/src/features/imageHeightSolid/ImageHeightmapSolidFeature.js +463 -0
- package/src/features/imageToFace/ImageToFaceFeature.js +727 -0
- package/src/features/imageToFace/imageEditor.js +1270 -0
- package/src/features/imageToFace/traceUtils.js +971 -0
- package/src/features/import3dModel/Import3dModelFeature.js +151 -0
- package/src/features/loft/LoftFeature.js +605 -0
- package/src/features/mirror/MirrorFeature.js +151 -0
- package/src/features/offsetFace/OffsetFaceFeature.js +370 -0
- package/src/features/offsetShell/OffsetShellFeature.js +89 -0
- package/src/features/overlapCleanup/OverlapCleanupFeature.js +85 -0
- package/src/features/pattern/PatternFeature.js +275 -0
- package/src/features/patternLinear/PatternLinearFeature.js +120 -0
- package/src/features/patternRadial/PatternRadialFeature.js +186 -0
- package/src/features/plane/PlaneFeature.js +154 -0
- package/src/features/primitiveCone/primitiveConeFeature.js +99 -0
- package/src/features/primitiveCube/primitiveCubeFeature.js +70 -0
- package/src/features/primitiveCylinder/primitiveCylinderFeature.js +91 -0
- package/src/features/primitivePyramid/primitivePyramidFeature.js +72 -0
- package/src/features/primitiveSphere/primitiveSphereFeature.js +62 -0
- package/src/features/primitiveTorus/primitiveTorusFeature.js +109 -0
- package/src/features/remesh/RemeshFeature.js +97 -0
- package/src/features/revolve/RevolveFeature.js +111 -0
- package/src/features/selectionUtils.js +118 -0
- package/src/features/sheetMetal/SheetMetalContourFlangeFeature.js +1656 -0
- package/src/features/sheetMetal/SheetMetalCutoutFeature.js +1056 -0
- package/src/features/sheetMetal/SheetMetalFlangeFeature.js +1568 -0
- package/src/features/sheetMetal/SheetMetalHemFeature.js +43 -0
- package/src/features/sheetMetal/SheetMetalObject.js +141 -0
- package/src/features/sheetMetal/SheetMetalTabFeature.js +176 -0
- package/src/features/sheetMetal/UNFOLD_NEUTRAL_REQUIREMENTS.md +153 -0
- package/src/features/sheetMetal/contour-flange-rebuild-spec.md +261 -0
- package/src/features/sheetMetal/profileUtils.js +25 -0
- package/src/features/sheetMetal/sheetMetalCleanup.js +9 -0
- package/src/features/sheetMetal/sheetMetalFaceTypes.js +146 -0
- package/src/features/sheetMetal/sheetMetalMetadata.js +165 -0
- package/src/features/sheetMetal/sheetMetalPipeline.js +169 -0
- package/src/features/sheetMetal/sheetMetalProfileUtils.js +216 -0
- package/src/features/sheetMetal/sheetMetalTabUtils.js +29 -0
- package/src/features/sheetMetal/sheetMetalTree.js +210 -0
- package/src/features/sketch/SketchFeature.js +955 -0
- package/src/features/sketch/sketchSolver2D/ConstraintEngine.js +800 -0
- package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +704 -0
- package/src/features/sketch/sketchSolver2D/mathHelpersMod.js +307 -0
- package/src/features/spline/SplineEditorSession.js +988 -0
- package/src/features/spline/SplineFeature.js +1388 -0
- package/src/features/spline/splineUtils.js +218 -0
- package/src/features/sweep/SweepFeature.js +110 -0
- package/src/features/transform/TransformFeature.js +152 -0
- package/src/features/tube/TubeFeature.js +635 -0
- package/src/fs.proxy.js +625 -0
- package/src/idbStorage.js +254 -0
- package/src/index.js +12 -0
- package/src/main.js +15 -0
- package/src/metadataManager.js +64 -0
- package/src/path.proxy.js +277 -0
- package/src/plugins/ghLoader.worker.js +151 -0
- package/src/plugins/pluginManager.js +286 -0
- package/src/pmi/PMIViewsManager.js +134 -0
- package/src/services/componentLibrary.js +198 -0
- package/src/tests/ConsoleCapture.js +189 -0
- package/src/tests/S7-diagnostics-2025-12-23T18-37-23-570Z.json +630 -0
- package/src/tests/browserTests.js +597 -0
- package/src/tests/debugBoolean.js +225 -0
- package/src/tests/partFiles/badBoolean.json +957 -0
- package/src/tests/partFiles/extrudeTest.json +88 -0
- package/src/tests/partFiles/filletFail.json +58 -0
- package/src/tests/partFiles/import_TEst.part.part.json +646 -0
- package/src/tests/partFiles/sheetMetalHem.BREP.json +734 -0
- package/src/tests/test_boolean_subtract.js +27 -0
- package/src/tests/test_chamfer.js +17 -0
- package/src/tests/test_extrudeFace.js +24 -0
- package/src/tests/test_fillet.js +17 -0
- package/src/tests/test_fillet_nonClosed.js +45 -0
- package/src/tests/test_filletsMoreDifficult.js +46 -0
- package/src/tests/test_history_features_basic.js +149 -0
- package/src/tests/test_hole.js +282 -0
- package/src/tests/test_mirror.js +16 -0
- package/src/tests/test_offsetShellGrouping.js +85 -0
- package/src/tests/test_plane.js +4 -0
- package/src/tests/test_primitiveCone.js +11 -0
- package/src/tests/test_primitiveCube.js +7 -0
- package/src/tests/test_primitiveCylinder.js +8 -0
- package/src/tests/test_primitivePyramid.js +9 -0
- package/src/tests/test_primitiveSphere.js +17 -0
- package/src/tests/test_primitiveTorus.js +21 -0
- package/src/tests/test_pushFace.js +126 -0
- package/src/tests/test_sheetMetalContourFlange.js +125 -0
- package/src/tests/test_sheetMetal_features.js +80 -0
- package/src/tests/test_sketch_openLoop.js +45 -0
- package/src/tests/test_solidMetrics.js +58 -0
- package/src/tests/test_stlLoader.js +1889 -0
- package/src/tests/test_sweepFace.js +55 -0
- package/src/tests/test_tube.js +45 -0
- package/src/tests/test_tube_closedLoop.js +67 -0
- package/src/tests/tests.js +493 -0
- package/src/tools/assemblyConstraintDialogCapturePage.js +56 -0
- package/src/tools/dialogCapturePageFactory.js +227 -0
- package/src/tools/featureDialogCapturePage.js +47 -0
- package/src/tools/pmiAnnotationDialogCapturePage.js +60 -0
- package/src/utils/axisHelpers.js +99 -0
- package/src/utils/deepClone.js +69 -0
- package/src/utils/geometryTolerance.js +37 -0
- package/src/utils/normalizeTypeString.js +8 -0
- package/src/utils/xformMath.js +51 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
// Helper functions for vector conversion
|
|
4
|
+
function arrToV(p) { return new THREE.Vector3(p[0], p[1], p[2]); }
|
|
5
|
+
function vToArr(v) { return [v.x, v.y, v.z]; }
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate endcap faces to create a manifold mesh by triangulating a boundary loop.
|
|
9
|
+
* This function creates triangular faces that close off open boundaries in a mesh,
|
|
10
|
+
* which is essential for maintaining manifold topology in CSG operations.
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} solid - The solid object to add triangles to
|
|
13
|
+
* @param {string} faceName - Name/ID for the endcap face
|
|
14
|
+
* @param {Array<THREE.Vector3|Array>} boundaryPoints - Ordered boundary loop vertices
|
|
15
|
+
* @param {THREE.Vector3} [normal] - Optional normal vector for orientation (auto-computed if not provided)
|
|
16
|
+
* @param {Object} [options] - Configuration options
|
|
17
|
+
* @param {number} [options.minTriangleArea=1e-12] - Minimum triangle area threshold
|
|
18
|
+
* @param {boolean} [options.ensureCounterClockwise=true] - Ensure proper winding order
|
|
19
|
+
* @param {string} [options.triangulationMethod='fan'] - Method: 'fan', 'earcut', or 'centroid'
|
|
20
|
+
* @returns {number} Number of triangles generated
|
|
21
|
+
*/
|
|
22
|
+
export function generateEndcapFaces(solid, faceName, boundaryPoints, normal = null, options = {}) {
|
|
23
|
+
const {
|
|
24
|
+
minTriangleArea = 1e-12,
|
|
25
|
+
ensureCounterClockwise = true,
|
|
26
|
+
triangulationMethod = 'fan'
|
|
27
|
+
} = options;
|
|
28
|
+
|
|
29
|
+
if (!solid || typeof solid.addTriangle !== 'function') {
|
|
30
|
+
throw new Error('generateEndcapFaces: solid must have addTriangle method');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!Array.isArray(boundaryPoints) || boundaryPoints.length < 3) {
|
|
34
|
+
console.warn('generateEndcapFaces: insufficient boundary points for triangulation');
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Convert boundary points to Vector3 if needed and validate
|
|
39
|
+
const points = boundaryPoints.map(p => {
|
|
40
|
+
const v = Array.isArray(p) ? arrToV(p) : p;
|
|
41
|
+
if (!v || typeof v.x !== 'number' || !Number.isFinite(v.x + v.y + v.z)) {
|
|
42
|
+
throw new Error('generateEndcapFaces: invalid point in boundary');
|
|
43
|
+
}
|
|
44
|
+
return v;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Remove duplicate consecutive points
|
|
48
|
+
const cleanPoints = [];
|
|
49
|
+
const eps = Math.max(minTriangleArea, 1e-10);
|
|
50
|
+
for (let i = 0; i < points.length; i++) {
|
|
51
|
+
const curr = points[i];
|
|
52
|
+
const next = points[(i + 1) % points.length];
|
|
53
|
+
if (curr.distanceTo(next) > eps) {
|
|
54
|
+
cleanPoints.push(curr);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (cleanPoints.length < 3) {
|
|
59
|
+
console.warn('generateEndcapFaces: insufficient unique points after cleaning');
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Auto-compute normal if not provided using Newell's method for robustness
|
|
64
|
+
let capNormal = normal;
|
|
65
|
+
if (!capNormal || !Number.isFinite(capNormal.x + capNormal.y + capNormal.z)) {
|
|
66
|
+
capNormal = computePolygonNormal(cleanPoints);
|
|
67
|
+
if (capNormal.lengthSq() < eps) {
|
|
68
|
+
console.warn('generateEndcapFaces: degenerate polygon, cannot compute normal');
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Ensure consistent winding order
|
|
74
|
+
if (ensureCounterClockwise) {
|
|
75
|
+
const signedArea = computeSignedArea(cleanPoints, capNormal);
|
|
76
|
+
if (signedArea < 0) {
|
|
77
|
+
cleanPoints.reverse();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let triangleCount = 0;
|
|
82
|
+
|
|
83
|
+
// Choose triangulation method
|
|
84
|
+
switch (triangulationMethod) {
|
|
85
|
+
case 'centroid':
|
|
86
|
+
triangleCount = triangulateCentroid(solid, faceName, cleanPoints, minTriangleArea);
|
|
87
|
+
break;
|
|
88
|
+
case 'earcut':
|
|
89
|
+
triangleCount = triangulateEarcut(solid, faceName, cleanPoints, capNormal, minTriangleArea);
|
|
90
|
+
break;
|
|
91
|
+
case 'fan':
|
|
92
|
+
default:
|
|
93
|
+
triangleCount = triangulateFan(solid, faceName, cleanPoints, minTriangleArea);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return triangleCount;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Compute polygon normal using Newell's method (robust for non-planar polygons)
|
|
102
|
+
*/
|
|
103
|
+
function computePolygonNormal(points) {
|
|
104
|
+
const normal = new THREE.Vector3();
|
|
105
|
+
const n = points.length;
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < n; i++) {
|
|
108
|
+
const p0 = points[i];
|
|
109
|
+
const p1 = points[(i + 1) % n];
|
|
110
|
+
|
|
111
|
+
normal.x += (p0.y - p1.y) * (p0.z + p1.z);
|
|
112
|
+
normal.y += (p0.z - p1.z) * (p0.x + p1.x);
|
|
113
|
+
normal.z += (p0.x - p1.x) * (p0.y + p1.y);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return normal.normalize();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Compute signed area of polygon projected onto plane with given normal
|
|
121
|
+
*/
|
|
122
|
+
function computeSignedArea(points, normal) {
|
|
123
|
+
const n = points.length;
|
|
124
|
+
let area = 0;
|
|
125
|
+
|
|
126
|
+
// Find the most significant component of the normal to choose projection plane
|
|
127
|
+
const absNormal = new THREE.Vector3(Math.abs(normal.x), Math.abs(normal.y), Math.abs(normal.z));
|
|
128
|
+
let maxComponent = 0; // 0=x, 1=y, 2=z
|
|
129
|
+
if (absNormal.y > absNormal.x) maxComponent = 1;
|
|
130
|
+
if (absNormal.z > absNormal[maxComponent === 0 ? 'x' : 'y']) maxComponent = 2;
|
|
131
|
+
|
|
132
|
+
// Project to 2D and compute signed area
|
|
133
|
+
for (let i = 0; i < n; i++) {
|
|
134
|
+
const p0 = points[i];
|
|
135
|
+
const p1 = points[(i + 1) % n];
|
|
136
|
+
|
|
137
|
+
let u0, v0, u1, v1;
|
|
138
|
+
if (maxComponent === 0) {
|
|
139
|
+
u0 = p0.y; v0 = p0.z; u1 = p1.y; v1 = p1.z;
|
|
140
|
+
} else if (maxComponent === 1) {
|
|
141
|
+
u0 = p0.z; v0 = p0.x; u1 = p1.z; v1 = p1.x;
|
|
142
|
+
} else {
|
|
143
|
+
u0 = p0.x; v0 = p0.y; u1 = p1.x; v1 = p1.y;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
area += (u0 * v1 - u1 * v0);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return area * 0.5;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Simple fan triangulation from first vertex
|
|
154
|
+
*/
|
|
155
|
+
function triangulateFan(solid, faceName, points, minArea) {
|
|
156
|
+
const n = points.length;
|
|
157
|
+
if (n < 3) return 0;
|
|
158
|
+
|
|
159
|
+
let count = 0;
|
|
160
|
+
const p0 = points[0];
|
|
161
|
+
|
|
162
|
+
for (let i = 1; i < n - 1; i++) {
|
|
163
|
+
const p1 = points[i];
|
|
164
|
+
const p2 = points[i + 1];
|
|
165
|
+
|
|
166
|
+
const area = computeTriangleArea(p0, p1, p2);
|
|
167
|
+
if (area > minArea) {
|
|
168
|
+
solid.addTriangle(faceName, vToArr(p0), vToArr(p1), vToArr(p2));
|
|
169
|
+
count++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return count;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Centroid-based triangulation (good for convex polygons)
|
|
178
|
+
*/
|
|
179
|
+
function triangulateCentroid(solid, faceName, points, minArea) {
|
|
180
|
+
const n = points.length;
|
|
181
|
+
if (n < 3) return 0;
|
|
182
|
+
|
|
183
|
+
// Compute centroid
|
|
184
|
+
const centroid = new THREE.Vector3();
|
|
185
|
+
for (const point of points) {
|
|
186
|
+
centroid.add(point);
|
|
187
|
+
}
|
|
188
|
+
centroid.multiplyScalar(1 / n);
|
|
189
|
+
|
|
190
|
+
let count = 0;
|
|
191
|
+
for (let i = 0; i < n; i++) {
|
|
192
|
+
const p0 = points[i];
|
|
193
|
+
const p1 = points[(i + 1) % n];
|
|
194
|
+
|
|
195
|
+
const area = computeTriangleArea(centroid, p0, p1);
|
|
196
|
+
if (area > minArea) {
|
|
197
|
+
solid.addTriangle(faceName, vToArr(centroid), vToArr(p0), vToArr(p1));
|
|
198
|
+
count++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return count;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Ear clipping triangulation (handles non-convex polygons)
|
|
207
|
+
*/
|
|
208
|
+
function triangulateEarcut(solid, faceName, points, normal, minArea) {
|
|
209
|
+
const n = points.length;
|
|
210
|
+
if (n < 3) return 0;
|
|
211
|
+
|
|
212
|
+
// Simple ear clipping implementation
|
|
213
|
+
const vertices = [...points];
|
|
214
|
+
let count = 0;
|
|
215
|
+
|
|
216
|
+
while (vertices.length > 3) {
|
|
217
|
+
let earFound = false;
|
|
218
|
+
|
|
219
|
+
for (let i = 0; i < vertices.length; i++) {
|
|
220
|
+
const p0 = vertices[(i - 1 + vertices.length) % vertices.length];
|
|
221
|
+
const p1 = vertices[i];
|
|
222
|
+
const p2 = vertices[(i + 1) % vertices.length];
|
|
223
|
+
|
|
224
|
+
if (isEar(vertices, i, normal)) {
|
|
225
|
+
const area = computeTriangleArea(p0, p1, p2);
|
|
226
|
+
if (area > minArea) {
|
|
227
|
+
solid.addTriangle(faceName, vToArr(p0), vToArr(p1), vToArr(p2));
|
|
228
|
+
count++;
|
|
229
|
+
}
|
|
230
|
+
vertices.splice(i, 1);
|
|
231
|
+
earFound = true;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!earFound) {
|
|
237
|
+
// Fallback to fan triangulation if ear clipping fails
|
|
238
|
+
console.warn('generateEndcapFaces: ear clipping failed, falling back to fan');
|
|
239
|
+
return count + triangulateFan(solid, faceName, vertices, minArea);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Add final triangle
|
|
244
|
+
if (vertices.length === 3) {
|
|
245
|
+
const area = computeTriangleArea(vertices[0], vertices[1], vertices[2]);
|
|
246
|
+
if (area > minArea) {
|
|
247
|
+
solid.addTriangle(faceName, vToArr(vertices[0]), vToArr(vertices[1]), vToArr(vertices[2]));
|
|
248
|
+
count++;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return count;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Check if vertex at index i is an ear (convex and contains no other vertices)
|
|
257
|
+
*/
|
|
258
|
+
function isEar(vertices, i, normal) {
|
|
259
|
+
const n = vertices.length;
|
|
260
|
+
const p0 = vertices[(i - 1 + n) % n];
|
|
261
|
+
const p1 = vertices[i];
|
|
262
|
+
const p2 = vertices[(i + 1) % n];
|
|
263
|
+
|
|
264
|
+
// Check if angle is convex
|
|
265
|
+
const v1 = new THREE.Vector3().subVectors(p0, p1);
|
|
266
|
+
const v2 = new THREE.Vector3().subVectors(p2, p1);
|
|
267
|
+
const cross = new THREE.Vector3().crossVectors(v1, v2);
|
|
268
|
+
|
|
269
|
+
if (cross.dot(normal) <= 0) return false; // Not convex
|
|
270
|
+
|
|
271
|
+
// Check if any other vertex is inside the triangle
|
|
272
|
+
for (let j = 0; j < n; j++) {
|
|
273
|
+
if (j === (i - 1 + n) % n || j === i || j === (i + 1) % n) continue;
|
|
274
|
+
if (isPointInTriangle(vertices[j], p0, p1, p2)) return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Test if point is inside triangle using barycentric coordinates
|
|
282
|
+
*/
|
|
283
|
+
function isPointInTriangle(point, a, b, c) {
|
|
284
|
+
const v0 = new THREE.Vector3().subVectors(c, a);
|
|
285
|
+
const v1 = new THREE.Vector3().subVectors(b, a);
|
|
286
|
+
const v2 = new THREE.Vector3().subVectors(point, a);
|
|
287
|
+
|
|
288
|
+
const dot00 = v0.dot(v0);
|
|
289
|
+
const dot01 = v0.dot(v1);
|
|
290
|
+
const dot02 = v0.dot(v2);
|
|
291
|
+
const dot11 = v1.dot(v1);
|
|
292
|
+
const dot12 = v1.dot(v2);
|
|
293
|
+
|
|
294
|
+
const invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
|
|
295
|
+
const u = (dot11 * dot02 - dot01 * dot12) * invDenom;
|
|
296
|
+
const v = (dot00 * dot12 - dot01 * dot02) * invDenom;
|
|
297
|
+
|
|
298
|
+
return (u >= 0) && (v >= 0) && (u + v <= 1);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Compute triangle area using cross product
|
|
303
|
+
*/
|
|
304
|
+
function computeTriangleArea(p0, p1, p2) {
|
|
305
|
+
const v1 = new THREE.Vector3().subVectors(p1, p0);
|
|
306
|
+
const v2 = new THREE.Vector3().subVectors(p2, p0);
|
|
307
|
+
return v1.cross(v2).length() * 0.5;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Remove triangles with area below the tolerance and rebuild supporting arrays.
|
|
311
|
+
export function removeDegenerateTrianglesAuthoring(solid, areaEps = 1e-12) {
|
|
312
|
+
const vp = solid._vertProperties;
|
|
313
|
+
const tv = solid._triVerts;
|
|
314
|
+
const ids = solid._triIDs;
|
|
315
|
+
const triCount = (tv.length / 3) | 0;
|
|
316
|
+
if (triCount === 0) return 0;
|
|
317
|
+
const keep = new Uint8Array(triCount);
|
|
318
|
+
let removed = 0;
|
|
319
|
+
const A = new THREE.Vector3(), B = new THREE.Vector3(), C = new THREE.Vector3();
|
|
320
|
+
for (let t = 0; t < triCount; t++) {
|
|
321
|
+
const i0 = tv[t * 3 + 0] * 3;
|
|
322
|
+
const i1 = tv[t * 3 + 1] * 3;
|
|
323
|
+
const i2 = tv[t * 3 + 2] * 3;
|
|
324
|
+
A.set(vp[i0 + 0], vp[i0 + 1], vp[i0 + 2]);
|
|
325
|
+
B.set(vp[i1 + 0], vp[i1 + 1], vp[i1 + 2]);
|
|
326
|
+
C.set(vp[i2 + 0], vp[i2 + 1], vp[i2 + 2]);
|
|
327
|
+
const area = B.clone().sub(A).cross(C.clone().sub(A)).length() * 0.5;
|
|
328
|
+
if (Number.isFinite(area) && area > areaEps) keep[t] = 1; else removed++;
|
|
329
|
+
}
|
|
330
|
+
if (removed === 0) return 0;
|
|
331
|
+
const used = new Uint8Array((vp.length / 3) | 0);
|
|
332
|
+
const newTriVerts = [];
|
|
333
|
+
const newTriIDs = [];
|
|
334
|
+
for (let t = 0; t < triCount; t++) {
|
|
335
|
+
if (!keep[t]) continue;
|
|
336
|
+
const a = tv[t * 3 + 0] >>> 0;
|
|
337
|
+
const b = tv[t * 3 + 1] >>> 0;
|
|
338
|
+
const c = tv[t * 3 + 2] >>> 0;
|
|
339
|
+
newTriVerts.push(a, b, c);
|
|
340
|
+
if (ids) newTriIDs.push(ids[t]);
|
|
341
|
+
used[a] = 1; used[b] = 1; used[c] = 1;
|
|
342
|
+
}
|
|
343
|
+
const oldToNew = new Int32Array((vp.length / 3) | 0);
|
|
344
|
+
for (let i = 0; i < oldToNew.length; i++) oldToNew[i] = -1;
|
|
345
|
+
const newVP = [];
|
|
346
|
+
let w = 0;
|
|
347
|
+
for (let i = 0; i < used.length; i++) {
|
|
348
|
+
if (!used[i]) continue;
|
|
349
|
+
const j = i * 3;
|
|
350
|
+
newVP.push(vp[j + 0], vp[j + 1], vp[j + 2]);
|
|
351
|
+
oldToNew[i] = w++;
|
|
352
|
+
}
|
|
353
|
+
for (let k = 0; k < newTriVerts.length; k++) newTriVerts[k] = oldToNew[newTriVerts[k]];
|
|
354
|
+
solid._vertProperties = newVP;
|
|
355
|
+
solid._triVerts = newTriVerts;
|
|
356
|
+
solid._triIDs = ids ? newTriIDs : null;
|
|
357
|
+
solid._vertKeyToIndex = new Map();
|
|
358
|
+
for (let i = 0; i < newVP.length; i += 3) {
|
|
359
|
+
solid._vertKeyToIndex.set(`${newVP[i]},${newVP[i + 1]},${newVP[i + 2]}`, (i / 3) | 0);
|
|
360
|
+
}
|
|
361
|
+
solid._dirty = true;
|
|
362
|
+
solid._faceIndex = null;
|
|
363
|
+
solid.fixTriangleWindingsByAdjacency();
|
|
364
|
+
return removed;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Snap authoring vertices to a 3D grid and rebuild lookup tables.
|
|
368
|
+
export function quantizeVerticesAuthoring(solid, q = 1e-6) {
|
|
369
|
+
if (!(q > 0)) return 0;
|
|
370
|
+
const vp = solid._vertProperties;
|
|
371
|
+
let changes = 0;
|
|
372
|
+
for (let i = 0; i < vp.length; i++) {
|
|
373
|
+
const v = vp[i];
|
|
374
|
+
const snapped = Math.round(v / q) * q;
|
|
375
|
+
if (snapped !== v) { vp[i] = snapped; changes++; }
|
|
376
|
+
}
|
|
377
|
+
if (changes) {
|
|
378
|
+
solid._vertKeyToIndex = new Map();
|
|
379
|
+
for (let i = 0; i < vp.length; i += 3) {
|
|
380
|
+
const x = vp[i + 0], y = vp[i + 1], z = vp[i + 2];
|
|
381
|
+
solid._vertKeyToIndex.set(`${x},${y},${z}`, (i / 3) | 0);
|
|
382
|
+
}
|
|
383
|
+
solid._dirty = true;
|
|
384
|
+
solid._faceIndex = null;
|
|
385
|
+
solid.fixTriangleWindingsByAdjacency();
|
|
386
|
+
}
|
|
387
|
+
return (changes / 3) | 0;
|
|
388
|
+
}
|