three-bvh-csg glb分割
复杂模型报错:
E:\project\3d_label\three-bvh-csg-main\examples\demo.html
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8" /> <title>Stable GLB BVH CSG Cut</title> <style> body { margin: 0; overflow: hidden; } canvas { display: block; } </style> </head> <body> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/", "three-bvh-csg": "https://unpkg.com/three-bvh-csg@0.0.18/build/index.module.js" } } </script> <script type="module"> 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>