我目前正在开发一个涉及使用 Three.js 进行区域创建和碰撞检测的项目,其中我的应用程序可以正确处理碰撞并生成
BufferGeometry
作为结果。我的目标是在这些区域周围直观地呈现一个框架,以清楚地描绘出它们的边界。
虽然我可以轻松地为简单的几何形状创建边界框(效果很好,如附图所示),但我遇到了更复杂的几何形状的问题。边界框不能准确反映区域的实际复杂形状。
为了解决这个问题,我尝试通过从顶点创建
LineSegments
来突出显示几何体的边缘。这种方法在一定程度上有效,但会导致显示太多内部线。我需要一种方法来过滤掉这些内部线条,以便只有外部边缘(复杂形状的实际轮廓)可见。
这是我的代码的相关部分,我尝试提取并显示这些边缘:
const calculateZoneCollision = (zone: PotreeVolume) => {
const viewer: PotreeViewer = (window as any).viewer;
if (!zone) return;
zone.updateMatrixWorld(true);
const allVolumes = viewer.scene.volumes.filter((volume) => volume.uuid !== zone.uuid);
const volumeMeshes = allVolumes.map((v) => {
return createMeshFromVolume(v);
});
let zoneMesh = createMeshFromVolume(zone);
let zoneOBB = createOBBFromMesh(zoneMesh, viewer);
let resultMesh;
volumeMeshes.forEach((volume) => {
volume.updateMatrixWorld(true);
let volumeOBB = createOBBFromMesh(volume, viewer);
if (zoneOBB.intersectsOBB(volumeOBB)) {
console.log(`Collision detected with volume: ${volume.name}`);
let originalCSGZone;
if (!resultMesh) {
originalCSGZone = CSG.fromMesh(zoneMesh);
} else {
originalCSGZone = CSG.fromMesh(resultMesh);
}
let csgZone = originalCSGZone.clone();
let csgVolume = CSG.fromMesh(volume);
csgZone = csgZone.subtract(csgVolume);
const collisionInfo = calculateCollisionInfo(zoneOBB, volumeOBB);
const details = getCollisionDetails(zoneOBB, volumeOBB, collisionInfo);
if (details) {
console.log(`Point of collide: `, details.collisionPoint);
console.log(`Side of impact: `, details.side);
console.log('Volume: ', zone, zoneMesh, zoneOBB);
}
const resultGeometry = CSG.toGeometry(csgZone, zoneMesh.matrix);
const resultMaterial = new MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.001 });
resultMesh = new Mesh(resultGeometry, resultMaterial);
resultMesh.applyMatrix4(zoneMesh.matrixWorld);
const vertices = [];
const positionAttribute = resultGeometry.getAttribute('position');
for (let i = 0; i < positionAttribute.count; i++) {
const vertex = new Vector3();
vertex.fromBufferAttribute(positionAttribute, i);
vertices.push([vertex.x, vertex.y, vertex.z]);
}
const matrixElements = resultMesh.matrix.elements;
} else {
console.log(`Collision not detected`);
}
});
if (resultMesh) {
const outerEdges = extractOuterEdges(resultMesh.geometry);
outerEdges.applyMatrix4(zoneMesh.matrixWorld);
viewer.scene.scene.add(outerEdges);
viewer.scene.scene.add(resultMesh);
setTimeout(() => {
viewer.scene.scene.remove(resultMesh);
viewer.scene.scene.remove(outerEdges);
}, 160);
}
};
function extractOuterEdges(geometry) {
const indexAttribute = geometry.index;
const positionAttribute = geometry.getAttribute('position');
const normalsAttribute = geometry.getAttribute('normal');
const edgeMap = new Map();
const edgeFaces = new Map();
for (let i = 0; i < indexAttribute.count; i += 3) {
let indices = [indexAttribute.getX(i), indexAttribute.getX(i + 1), indexAttribute.getX(i + 2)];
let triangleNormals = [
new Vector3(normalsAttribute.getX(i), normalsAttribute.getY(i), normalsAttribute.getZ(i)),
new Vector3(normalsAttribute.getX(i + 1), normalsAttribute.getY(i + 1), normalsAttribute.getZ(i + 1)),
new Vector3(normalsAttribute.getX(i + 2), normalsAttribute.getY(i + 2), normalsAttribute.getZ(i + 2)),
];
for (let j = 0; j < 3; j++) {
let edgeKey = [indices[j], indices[(j + 1) % 3]].sort().join('_');
if (!edgeMap.has(edgeKey)) {
edgeMap.set(edgeKey, 1);
edgeFaces.set(edgeKey, [triangleNormals[j]]);
} else {
edgeMap.set(edgeKey, edgeMap.get(edgeKey) + 1);
edgeFaces.get(edgeKey).push(triangleNormals[j]);
}
}
}
const outerEdgesGeometry = new BufferGeometry();
const vertices = [];
edgeMap.forEach((count, key) => {
if (count === 1 || (count === 2 && !areNormalsCoPlanar(edgeFaces.get(key)[0], edgeFaces.get(key)[1]))) {
let vertexIndices = key.split('_').map(Number);
vertices.push(
new Vector3().fromBufferAttribute(positionAttribute, vertexIndices[0]),
new Vector3().fromBufferAttribute(positionAttribute, vertexIndices[1])
);
}
});
outerEdgesGeometry.setFromPoints(vertices);
const lineMaterial = new LineBasicMaterial({ color: 0xffffff });
const lineSegments = new LineSegments(outerEdgesGeometry, lineMaterial);
return lineSegments;
}
function areNormalsCoPlanar(normal1, normal2) {
return normal1.dot(normal2) > 0.995; // Stricter coplanarity check
}
const calculateZoneCollision = (zone: PotreeVolume) => {
const viewer: PotreeViewer = (window as any).viewer;
if (!zone) return;
zone.updateMatrixWorld(true);
const allVolumes = viewer.scene.volumes.filter((volume) => volume.uuid !== zone.uuid);
const volumeMeshes = allVolumes.map((v) => {
return createMeshFromVolume(v);
});
let zoneMesh = createMeshFromVolume(zone);
let zoneOBB = createOBBFromMesh(zoneMesh, viewer);
let resultMesh;
volumeMeshes.forEach((volume) => {
volume.updateMatrixWorld(true);
let volumeOBB = createOBBFromMesh(volume, viewer);
if (zoneOBB.intersectsOBB(volumeOBB)) {
console.log(`Collision detected with volume: ${volume.name}`);
let originalCSGZone;
if (!resultMesh) {
originalCSGZone = CSG.fromMesh(zoneMesh);
} else {
originalCSGZone = CSG.fromMesh(resultMesh);
}
let csgZone = originalCSGZone.clone();
let csgVolume = CSG.fromMesh(volume);
csgZone = csgZone.subtract(csgVolume);
const collisionInfo = calculateCollisionInfo(zoneOBB, volumeOBB);
const details = getCollisionDetails(zoneOBB, volumeOBB, collisionInfo);
if (details) {
console.log(`Point of collide: `, details.collisionPoint);
console.log(`Side of impact: `, details.side);
console.log('Volume: ', zone, zoneMesh, zoneOBB);
}
const resultGeometry = CSG.toGeometry(csgZone, zoneMesh.matrix);
const resultMaterial = new MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.001 });
resultMesh = new Mesh(resultGeometry, resultMaterial);
resultMesh.applyMatrix4(zoneMesh.matrixWorld);
const vertices = [];
const positionAttribute = resultGeometry.getAttribute('position');
for (let i = 0; i < positionAttribute.count; i++) {
const vertex = new Vector3();
vertex.fromBufferAttribute(positionAttribute, i);
vertices.push([vertex.x, vertex.y, vertex.z]);
}
const matrixElements = resultMesh.matrix.elements;
} else {
console.log(`Collision not detected`);
}
});
if (resultMesh) {
let volume = CSG.fromMesh(resultMesh);
const frame = filterOuterEdges(volume);
viewer.scene.scene.add(frame);
viewer.scene.scene.add(resultMesh);
setTimeout(() => {
viewer.scene.scene.remove(resultMesh);
// viewer.scene.scene.remove(frame);
}, 160);
}
};
export default calculateZoneCollision;
function filterOuterEdges(csg) {
const edges = new Map();
// Goes trough all poygons and all edges
csg.polygons.forEach((polygon) => {
const vertices = polygon.vertices;
for (let i = 0; i < vertices.length; i++) {
const start = vertices[i].pos;
const end = vertices[(i + 1) % vertices.length].pos;
const edgeKey = vertexKey(start, end);
if (edges.has(edgeKey)) {
// If bouth polygons have same or look a like normal that might be inner edge
const edge = edges.get(edgeKey);
if (edge.normal.dot(polygon.plane.normal) > 0.95) {
edges.delete(edgeKey); // Delete becouse that will be inner edge
}
} else {
// Save edge with his normal for later check
edges.set(edgeKey, { start: start, end: end, normal: polygon.plane.normal });
}
}
});
// Now we make geometry with outside edges
const outerEdgesGeometry = new BufferGeometry();
const positions = new Float32Array(edges.size * 2 * 3);
let positionIndex = 0;
edges.forEach((edge) => {
positions.set([edge.start.x, edge.start.y, edge.start.z], positionIndex);
positionIndex += 3;
positions.set([edge.end.x, edge.end.y, edge.end.z], positionIndex);
positionIndex += 3;
});
outerEdgesGeometry.setAttribute('position', new BufferAttribute(positions, 3));
const lineMaterial = new LineBasicMaterial({ color: 0xff0000 });
const lineSegments = new LineSegments(outerEdgesGeometry, lineMaterial);
return lineSegments;
}
function vertexKey(vertex1, vertex2) {
const minVertex =
vertex1.x < vertex2.x
? vertex1
: vertex1.x === vertex2.x && vertex1.y < vertex2.y
? vertex1
: vertex1.x === vertex2.x && vertex1.y === vertex2.y && vertex1.z < vertex2.z
? vertex1
: vertex2;
const maxVertex = vertex1 === minVertex ? vertex2 : vertex1;
return `${minVertex.x}_${minVertex.y}_${minVertex.z}-${maxVertex.x}_${maxVertex.y}_${maxVertex.z}`;
}
到目前为止,问题的最佳解决方案,但仍然不够完美。现在我使用 CSG 中的顶点并过滤它们。一切都进展得足够快,但仍然有一些内部问题我仍然需要过滤掉。 、(红框就是我要过滤的BufferGeometry)