three-bvh-csg glb分割

目录

切割失败:

[加载glb stl代码:](#加载glb stl代码:)


切割失败:

bash 复制代码
const negPart = csgEvaluator.evaluate(sb, positiveBox, SUBTRACTION);
const posPart = csgEvaluator.evaluate(sb, negativeBox, SUBTRACTION);
bash 复制代码
		function sanitizeGeometry(g) {

			const clean = new THREE.BufferGeometry();

			clean.setAttribute('position', g.getAttribute('position'));
			clean.setAttribute('normal', g.getAttribute('normal'));

			clean.clearGroups();
			clean.addGroup(0, clean.attributes.position.count, 0);

			return clean;
		}

				sb.geometry = sanitizeGeometry(sb.geometry);
				positiveBox.geometry = sanitizeGeometry(positiveBox.geometry);
				negativeBox.geometry = sanitizeGeometry(negativeBox.geometry);


				console.log(
					Object.keys(sb.geometry.attributes)
				);

				console.log(
					Object.keys(positiveBox.geometry.attributes)
				);
                console.log(`sb.geometry.index`);

				const pos = sb.geometry.attributes.position.array;

				let bad = 0;

				for (let i = 0; i < pos.length; i++) {
					if (!Number.isFinite(pos[i])) {
						console.log("Bad:", i, pos[i]);
						bad++;
						break;
					}
				}
				const positions = sb.geometry.attributes.position.array;
				for (let i = 0; i < positions.length; i++) {
					if (!isFinite(positions[i])) {
						console.error(`${name}: 发现无效顶点数据 at index ${i}`);
						positions[i] = 0; // 修复无效数据
					}
				}

加载glb stl代码:

html 复制代码
       // new GLTFLoader().load(MODEL_PATH, gltf => {
        //     const model = gltf.scene;
        //     model.traverse(c => { if (c.isMesh) { c.castShadow = true; c.receiveShadow = true; } });
        //     model.position.set(0, -0.3, 0); model.scale.set(0.8, 0.8, 0.8); scene.add(model);
        //     originalModelGroup = model.clone(); originalModelGroup.visible = false; scene.add(originalModelGroup);
        //     currentModelObject = model;
        //     cutPlaneMesh = createVisualCutPlane(currentCutPlaneSize);
        //     cutPlaneMesh.position.set(0, 0, 0);
        //     selectObject('model');
        //     const box = new THREE.Box3().setFromObject(model); const ct = box.getCenter(new THREE.Vector3()); const sz = box.getSize(new THREE.Vector3()); const md = Math.max(sz.x, sz.y, sz.z);
        //     camera.position.set(ct.x + md * 1.3, ct.y + md * 0.9, ct.z + md * 1.6); orbitControls.target.copy(ct); orbitControls.update();
        // }, undefined, err => {
        //     console.error("默认模型加载失败:", err);
        //     const fg = new THREE.BoxGeometry(1.6, 1.6, 1.6); const fm = new THREE.Mesh(fg, new THREE.MeshStandardMaterial({ color: 0x88aaff, metalness: 0.5, roughness: 0.3 }));
        //     fm.position.y = 0; scene.add(fm);
        //     originalModelGroup = fm.clone(); originalModelGroup.visible = false; scene.add(originalModelGroup);
        //     currentModelObject = fm;
        //     cutPlaneMesh = createVisualCutPlane(currentCutPlaneSize);
        //     cutPlaneMesh.position.set(0, 0, 0);
        //     selectObject('model');
        // });

		new STLLoader().load(MODEL_PATH, geometry => {
			// STL 返回的是 BufferGeometry,需要手动创建 Mesh
			const material = new THREE.MeshStandardMaterial({ color: 0x88aaff, metalness: 0.5, roughness: 0.3 });
			const model = new THREE.Mesh(geometry, material);

			model.traverse(c => { if (c.isMesh) { c.castShadow = true; c.receiveShadow = true; } });
			model.position.set(0, -0.3, 0);
			model.scale.set(0.8, 0.8, 0.8);
			scene.add(model);

			// 保存原始模型(注意:STL 没有 clone 层级结构,需要用 geometry 单独处理)
			originalModelGroup = model.clone();
			originalModelGroup.visible = false;
			scene.add(originalModelGroup);

			currentModelObject = model;
			cutPlaneMesh = createVisualCutPlane(currentCutPlaneSize);
			cutPlaneMesh.position.set(0, 0, 0);
			selectObject('model');

			// 计算包围盒并调整相机
			geometry.computeBoundingBox();
			const box = geometry.boundingBox;
			const ct = new THREE.Vector3(
				(box.min.x + box.max.x) / 2,
				(box.min.y + box.max.y) / 2,
				(box.min.z + box.max.z) / 2
			);
			const sz = new THREE.Vector3(
				box.max.x - box.min.x,
				box.max.y - box.min.y,
				box.max.z - box.min.z
			);
			const md = Math.max(sz.x, sz.y, sz.z);
			camera.position.set(ct.x + md * 1.3, ct.y + md * 0.9, ct.z + md * 1.6);
			orbitControls.target.copy(ct);
			orbitControls.update();
		}, undefined, err => {
			console.error("STL 模型加载失败:", err);
			// fallback 逻辑保持不变
		});

复杂模型报错:

E:\project\3d_label\three-bvh-csg-main\examples\demo.html

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>
相关推荐
牢姐与蒯1 小时前
c++数据结构之c++11(二)
开发语言·c++
很楠爱上1 小时前
TypeScript 核心知识精要
javascript·ubuntu·typescript
z200509301 小时前
【linux学习】深入理解 Linux 进程间通信:管道的艺术与实现
linux·开发语言
zhangfeng11331 小时前
workbuddy ,node.js 每次会在 项目目录上安装 node_modules,能不能一次安装多次使用,为什么 npm 不把包装在全局
前端·npm·node.js
lcj25111 小时前
【stack、queue、deque、priority_queue】C++ 栈 / 队列 / 优先级队列全解析!手撕实现 + 二叉树层序遍历(附源码)
开发语言·c++·笔记
一次旅行1 小时前
CopilotKit实战:用生成式UI打造智能Agent前端
前端·人工智能·ui
奋斗的小方1 小时前
Java进阶篇1-2:泛型
java·开发语言·windows
say_fall1 小时前
模拟量输入输出技术超详细知识点总结
linux·开发语言·嵌入式硬件·学习·php
我是一颗柠檬1 小时前
C++最全面复习:从入门到精通(2026年)
开发语言·c++·visualstudio