Your marker-material idea is valid, but there are two issues in the PG.
First, the call is using the old CSG argument order. CSG2.toMesh takes name, scene, options, so it should be:
const finalResultMesh = mainCSG .subtract(cutterCSG) .toMesh("finalResultMesh", scene);
You normally should not replace finalResultMesh.material afterward. If the base and cutter have different material instances before CSG2.FromMesh, CSG2 automatically creates the appropriate MultiMaterial.
Internally, FromMesh records the source material’s uniqueId as provenance. For A.subtract(B), the new inner boundary is derived from the cutter surface, so it receives the cutter material provenance. The output materialIndex is only a sequential slot in the generated MultiMaterial, however, so don’t assume the cutter is always index 1.
The reason pickResult.subMeshId reports the outer material is a CSG2/submesh picking quirk. toMesh() first creates a global submesh covering the complete index buffer, then appends the actual material submeshes. Picking tests the global submesh first; the material submesh hits the same triangle at the same distance, so the global submesh remains selected.
A non-destructive workaround is to resolve the material submesh from the mesh-wide faceId:
const multi = finalResultMesh.material; const cutMaterialIndex = multi.subMaterials.findIndex( (material) => material?.uniqueId === innerCutMaterial.uniqueId );
scene.onPointerDown = (\_, pick) => { if (pick.pickedMesh !== finalResultMesh || pick.faceId < 0) { return; }
const indexOffset = pick.faceId \* 3; const materialSubMesh = finalResultMesh.subMeshes.find( (subMesh) => !subMesh.IsGlobal && indexOffset >= subMesh.indexStart && indexOffset < subMesh.indexStart + subMesh.indexCount );
if (materialSubMesh?.materialIndex === cutMaterialIndex) { console.log("Cut surface selected"); } else { console.log("Original surface selected"); } };
Another option is to save the generated non-global submesh ranges, call releaseSubMeshes(), and recreate only those ranges. Normal subMeshId picking will then work without the overlapping global submesh.
One limitation is that this identifies cutter-derived faces through material provenance; it is not a generic “new triangle” flag. CSG2 may split or retriangulate original faces while retaining their original material. Also, the base and cutter must use different material instances, otherwise their faces cannot be distinguished this way.