three-bvh-csg glb分割 复杂模型报错E:\project\3d_label\three-bvh-csg-main\examples\demo.html!DOCTYPE html html langzh head meta charsetUTF-8 / titleStable GLB BVH CSG Cut/title style body { margin: 0; overflow: hidden; } canvas { display: block; } /style /head body script typeimportmap { imports: { three: https://unpkg.com/three0.160.0/build/three.module.js, three/addons/: https://unpkg.com/three0.160.0/examples/jsm/, three-bvh-csg: https://unpkg.com/three-bvh-csg0.0.18/build/index.module.js } } /script script typemodule import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js; import { GLTFLoader } from three/addons/loaders/GLTFLoader.js; import { Brush, Evaluator, INTERSECTION, SUBTRACTION } from three-bvh-csg; /* 基础场景 */ const scene new THREE.Scene(); scene.background new THREE.Color(0x222222); const camera new THREE.PerspectiveCamera(60, innerWidth/innerHeight, 0.1, 1000); camera.position.set(3, 3, 3); const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(innerWidth, innerHeight); document.body.appendChild(renderer.domElement); const controls new OrbitControls(camera, renderer.domElement); controls.update(); scene.add(new THREE.AmbientLight(0xffffff, 0.8)); const light new THREE.DirectionalLight(0xffffff, 1); light.position.set(5, 10, 5); scene.add(light); /* CSG */ const evaluator new Evaluator(); /* GLB */ const loader new GLTFLoader(); function cleanGeometry(mesh) { mesh.updateWorldMatrix(true, false); let geo mesh.geometry.clone(); // ❗ 1. 转 world space geo.applyMatrix4(mesh.matrixWorld); // ❗ 2. 确保有 position attribute if (!geo.attributes || !geo.attributes.position) { console.warn(Invalid geometry skipped); return null; } // ❗ 3. 转 indexed关键 if (!geo.index) { geo THREE.BufferGeometryUtils.mergeVertices(geo); } // ❗ 4. 清理 NaN / 冗余 geo.computeVertexNormals(); geo.computeBoundingBox(); geo.computeBoundingSphere(); return geo; } loader.load(yotown_clear.glb, (gltf) { const model gltf.scene; scene.add(model); model.updateMatrixWorld(true); /* 计算包围盒 */ const box new THREE.Box3().setFromObject(model); const center new THREE.Vector3(); const size new THREE.Vector3(); box.getCenter(center); box.getSize(size); console.log(center:, center); console.log(size:, size); /* 创建切割平面 */ const planeGeo new THREE.PlaneGeometry(200, 200); const planeMesh new THREE.Mesh( planeGeo, new THREE.MeshBasicMaterial() ); planeMesh.position.copy(center); planeMesh.rotation.x Math.PI / 2; // 水平切割XZ 平面 /* 收集 meshes */ const meshes []; model.traverse(o { if (o.isMesh) { o.geometry.computeVertexNormals(); meshes.push(o); } }); const upperGroup new THREE.Group(); const lowerGroup new THREE.Group(); /* 执行切割 */ meshes.forEach(mesh { const geometry cleanGeometry(mesh); if (!geometry) return; const brushA new Brush(geometry); /* 切割体 */ const upperPlane new Brush(new THREE.BoxGeometry(1000, 1000, 1000)); upperPlane.position.set(center.x, center.y 1000, center.z); upperPlane.updateMatrixWorld(true); const lowerPlane new Brush(new THREE.BoxGeometry(1000, 1000, 1000)); lowerPlane.position.set(center.x, center.y - 1000, center.z); lowerPlane.updateMatrixWorld(true); /* 上半 */ const upper evaluator.evaluate( brushA, upperPlane, INTERSECTION ); if (upper?.geometry?.attributes?.position) { upperGroup.add(new THREE.Mesh(upper.geometry, mesh.material.clone())); } /* 下半 */ const lower evaluator.evaluate( brushA, lowerPlane, INTERSECTION ); if (lower?.geometry?.attributes?.position) { const m new THREE.Mesh(lower.geometry, mesh.material.clone()); m.position.x size.x * 1.2; lowerGroup.add(m); } }); scene.add(upperGroup); scene.add(lowerGroup); }); /* 渲染 */ function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } animate(); window.addEventListener(resize, () { camera.aspect innerWidth / innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); }); /script /body /html