Algorithm 在THREEJS网格中分组共面三角形的方法?
我正在开发一个建模工具,可以让你直接操纵网格。例如,您可以抓取一个面并将其拖动。用户对“脸”的感知可能不止一个共面三角形。例如,立方体的顶部“面”实际上是两个三角形,它们作为一个正方形拖在一起 为了实现这一点,我想收集任何特定三角形的所有共面、相邻面,以便在拖动时使用。我已经看过了,也看过了一些例子,但我想保留基本的三角形,而不是减少/删除它们 在过去的好日子里,你会建立一个边缘模型ala,在那里你可以走每一条边去看相邻的面并检查法线 我希望可能有一些代码已经为3J编写,将共面三角形组合在一起。如果我从头开始写,我能想到的最好的算法是O(n^2),类似于:Algorithm 在THREEJS网格中分组共面三角形的方法?,algorithm,three.js,computational-geometry,geometry-surface,Algorithm,Three.js,Computational Geometry,Geometry Surface,我正在开发一个建模工具,可以让你直接操纵网格。例如,您可以抓取一个面并将其拖动。用户对“脸”的感知可能不止一个共面三角形。例如,立方体的顶部“面”实际上是两个三角形,它们作为一个正方形拖在一起 为了实现这一点,我想收集任何特定三角形的所有共面、相邻面,以便在拖动时使用。我已经看过了,也看过了一些例子,但我想保留基本的三角形,而不是减少/删除它们 在过去的好日子里,你会建立一个边缘模型ala,在那里你可以走每一条边去看相邻的面并检查法线 我希望可能有一些代码已经为3J编写,将共面三角形组合在一起。
faceUtils = function(){};
faceUtils.vertexHash = function(geometry){
geometry.vertexHash = [];
var faces = geometry.faces;
var vLen = geometry.vertices.length;
for(var i=0;i<vLen;i++){
geometry.vertexHash[i] = [];
for(var f in faces){
if(faces[f].a == i || faces[f].b == i || faces[f].c == i){
geometry.vertexHash[i].push(faces[f]);
}
}
}
}
faceUtils.prototype.getCoplanar = function(maxAngle, geometry, face, clamp, out, originFace){
if(clamp == undefined){
clamp = true;
}
if(this.originFace == undefined){
this.originFace = face;
}
if(this.pendingRecursive == undefined){
this.pendingRecursive = 0;
}
this.result = out;
if(out == undefined){
this.result = {count:0};
}
if(geometry.vertexHash == undefined){
faceUtils.vertexHash(geometry);
}
this.pendingRecursive++;
var vertexes = ["a","b","c"];
for (var i in vertexes){
var vertexIndex = face[vertexes[i]];
var adjacentFaces = geometry.vertexHash[vertexIndex];
for(var a in adjacentFaces){
var newface = adjacentFaces[a];
var testF = this.originFace;
if(clamp == false){
testF = face
}
if(testF.normal.angleTo(newface.normal) * (180/ Math.PI) <= maxAngle){
if(this.result["f"+newface.a+newface.b+newface.c] == undefined){
this.result["f"+newface.a+newface.b+newface.c] = newface;
this.result.count++;
this.getCoplanar(maxAngle, geometry, newface, clamp, this.result, this.originFace);
}
}
}
}
this.pendingRecursive--;
if(this.pendingRecursive == 0 && this.onCoplanar != undefined){
delete this.result.count;
this.onCoplanar(this.result);
}
}
我把这门课加在别人的小提琴上,效果很好
我更新了小提琴以使用钳夹选项:
我想这是非常低效的,正如你所建议的,但这取决于网格。我添加了一个“pendingRecursive”变量。只要它不等于零,你就可以放置一个gif,当值再次为零时将其删除
无论如何,这是一个起点。我相信聪明的人可以在没有嵌套for循环的情况下在脸上翻来覆去。我已经编写了一个适合我的解决方案,与我最初发布的问题中的项目符号一致,并且不使用递归。也许这对某人有用。(注意:我使用下划线是为了方便使用散列和数组等) 该算法首先在网格顶点上添加映射,列出每个顶点所属的所有面。从那里,我可以从一个特定的面开始,然后查找所有与起始面共享至少一个顶点的共面面面(并从那里开始)。如果两个顶点是共享的,那么就可以了
var COPLANAR_ANGLE_TOLERANCE = .1; // degrees, not radians
var RAD_TO_DEG = 180 / Math.PI;
var FACELEN = 3; // meshes have triangles by default
function checkCoplanarity(f1, f2) {
return ((f1.normal.angleTo(f2.normal) * RAD_TO_DEG) <= COPLANAR_ANGLE_TOLERANCE);
}
function assignVertexFaceHashes(geometry) {
var vertices = geometry.vertices;
var faces = geometry.faces, face;
var theVertex;
for (var faceIndex in faces) {
face = geometry.faces[faceIndex];
for (var vertIndex of [face.a, face.b, face.c]) {
theVertex = vertices[vertIndex];
if (!theVertex.hasOwnProperty('inFaces')) {
theVertex.inFaces = {};
}
theVertex.inFaces[faceIndex] = true;
}
}
}
function findCoplanarAdjacentFaces(startFaceIndex, geometry) {
var adjoiningFaceIndexes;
var coplanarAdjacentFaces = {};
var coplanarAdjacentVertices = {};
var examQueue = [];
var examined = {};
var examFace, examFaceIndex;
var adjoiningFace, adjoiningFaceIndex;
var faces = geometry.faces;
var vertices = geometry.vertices;
var startFace = faces[startFaceIndex];
examQueue.push(startFaceIndex);
// include the start face as a coplanar face
coplanarAdjacentVertices[startFace.a] = true;
coplanarAdjacentVertices[startFace.b] = true;
coplanarAdjacentVertices[startFace.c] = true;
coplanarAdjacentFaces[startFaceIndex] = true;
// Map vertices back to all faces they belong to
assignVertexFaceHashes(geometry);
while (examQueue.length > 0) {
examFaceIndex = examQueue.pop();
examFace = faces[examFaceIndex];
// console.log('examQueue:', examQueue.length);
adjoiningFaceIndexes = [];
for (var vertIndex of [examFace.a, examFace.b, examFace.c]) {
adjoiningFaceIndexes = _.union(adjoiningFaceIndexes, _.map(_.keys(vertices[vertIndex].inFaces), function(c) { return parseInt(c); }));
}
//console.log('adjoiningFaceIndexes:', adjoiningFaceIndexes);
for (adjoiningFaceIndex of adjoiningFaceIndexes) {
//console.log('Examining adjoining face index:', adjoiningFaceIndex);
if (!examined.hasOwnProperty(adjoiningFaceIndex)) {
if ((adjoiningFaceIndex != examFaceIndex) && (!coplanarAdjacentFaces.hasOwnProperty(adjoiningFaceIndex))) {
//console.log('adjoiningFaceIndex:', adjoiningFaceIndex);
adjoiningFace = faces[adjoiningFaceIndex];
if (checkCoplanarity(examFace, adjoiningFace)) {
var overlap1 = [adjoiningFace.a, adjoiningFace.b, adjoiningFace.c];
var overlap2 = [examFace.a, examFace.b, examFace.c];
var vertsInCommon = _.intersection(overlap1, overlap2);
// Check for vertices in common. If any vertices are in comment, these coplanar faces touch at least one vertex.
if (vertsInCommon.length > 0) {
//console.log('Pushing adjoining face due to vertices in common:', adjoiningFaceIndex);
coplanarAdjacentFaces[adjoiningFaceIndex] = true;
examQueue.push(adjoiningFaceIndex);
coplanarAdjacentVertices[adjoiningFace.a] = true;
coplanarAdjacentVertices[adjoiningFace.b] = true;
coplanarAdjacentVertices[adjoiningFace.c] = true;
} else {
// it's possible the adjoining face only touches vertices to the middle of edges, so check for that.
edgeIntersectExam:
for (var i = 0; i < FACELEN; ++i) {
adjoinP1 = overlap1[i];
adjoinP2 = overlap1[(i + 1) % FACELEN];
for (var j = 0; j < FACELEN; ++j) {
splitPoint = distToSegmentSquared3d(vertices[overlap2[j]], vertices[adjoinP1], vertices[adjoinP2]);
if (splitPoint.distance < POINT_ON_LINE_TOLERANCE) {
console.log('adding adjoining face due to edge intersection:', adjoiningFaceIndex);
console.log('j=', j, 'Source face:', examFaceIndex, examFace, 'We found split point on adjoining face index:', adjoiningFaceIndex, adjoiningFace);
coplanarAdjacentFaces[adjoiningFaceIndex] = true;
examQueue.push(adjoiningFaceIndex);
coplanarAdjacentVertices[adjoiningFace.a] = true;
coplanarAdjacentVertices[adjoiningFace.b] = true;
coplanarAdjacentVertices[adjoiningFace.c] = true;
break edgeIntersectExam;
}
}
}
}
}
}
}
}
examined[examFaceIndex] = true;
}
return ({ faces: coplanarAdjacentFaces, vertices: coplanarAdjacentVertices });
}
function assignFacesToCoplanarGroups(csgPrimitive) {
var geometry = csgPrimitive.geometry;
var faceIndexList = _.mapObject(_.keys(geometry.faces), function() { return true; });
var processedFaces = {};
var coplanarFaces;
var faces = geometry.faces;
var intIndex;
var coplanarGroupMax;
var coplanarGroups = [];
for (var processFaceIndex in faceIndexList) {
intIndex = parseInt(processFaceIndex);
if (!processedFaces.hasOwnProperty(intIndex)) {
coplanars = findCoplanarAdjacentFaces(processFaceIndex, geometry);
coplanarGroups.push({ faces: coplanars.faces, vertices: coplanars.vertices });
coplanarGroupMax = coplanarGroups.length - 1;
for (var groupedFaceIndex in coplanars.faces) {
faces[groupedFaceIndex].coplanarGroupIndex = coplanarGroupMax;
faces[groupedFaceIndex].color.setHex(0x0000ff); // just to help see the results
processedFaces[groupedFaceIndex] = true;
}
}
}
geometry.coplanarGroups = coplanarGroups;
geometry.colorsNeedUpdate = true;
}
function assignFacesToAllCoplanarGroups() {
var now = new Date();
var startTime = now.getTime();
for (var csgPrimitive of csgPrimitives.children) {
assignFacesToCoplanarGroups(csgPrimitive);
}
var later = new Date();
var duration = later.getTime() - startTime;
console.log('Done assigning faces to coplanar groups in:', duration, 'ms');
}
每个生成的共面组包含一个共面面数组和这些面使用的唯一顶点数组。使用顶点数组,我现在可以通过简单地应用Vector3.add()函数,一次抓取并拖动网格中的所有共面面
下面的屏幕截图可能会澄清这项工作的原因。为了创建所示的网格,生成一个立方体,然后使用上面提到的CSG库从中布尔减去一个球体
球体的距离足够远,以至于它实际上不与立方体相交,但是,在操作之后,立方体有许多额外的共面三角形,但不是一致的边界表示。例如,如图中所示,一些三角形与其他三角形的边的中间相接触(红色箭头指示了几个示例)
我写了另一个算法,当三角形像这样接触时,试图进一步分割三角形。该算法在一定程度上改善了这种情况:
但仍然是不完美的,因为CSG库有时会生成近似直线的三角形(两个顶点非常接近),从而导致舍入错误,使我的算法失效。如果一个三角形边与网格中的多个其他三角形相交,则该方法也不起作用
考虑到所有这些,在一个完美的世界中,我实际上希望将所有共面面重新组合成一个面,然后让THREEjs正确地对生成的(更大的)面进行三角化(或者使用其他类似的库)
我正在寻找一种方法来做到这一点,现在我已经把所有共面三角形组合在一起了。在我看来,应该有一个算法,给定所有这些共面三角形的所有边,可以找到它们的周长。有了它,我可以生成一个新的三角形面来替换它们,比如创建一组新的三角形插入到我的原始网格中。最终的结果是,在应用所有这些之后,我将返回到由CSG库转换为BSP后首先创建的精确几何体,然后重新转换为网格
如果有人对实现第二个目标的最佳方法有任何想法,请发表评论。如果我想出一些好办法,我可能会把它贴在这里。现在我有最好的想法:
想法1:
var COPLANAR_ANGLE_TOLERANCE = .1; // degrees, not radians
var RAD_TO_DEG = 180 / Math.PI;
var FACELEN = 3; // meshes have triangles by default
function checkCoplanarity(f1, f2) {
return ((f1.normal.angleTo(f2.normal) * RAD_TO_DEG) <= COPLANAR_ANGLE_TOLERANCE);
}
function assignVertexFaceHashes(geometry) {
var vertices = geometry.vertices;
var faces = geometry.faces, face;
var theVertex;
for (var faceIndex in faces) {
face = geometry.faces[faceIndex];
for (var vertIndex of [face.a, face.b, face.c]) {
theVertex = vertices[vertIndex];
if (!theVertex.hasOwnProperty('inFaces')) {
theVertex.inFaces = {};
}
theVertex.inFaces[faceIndex] = true;
}
}
}
function findCoplanarAdjacentFaces(startFaceIndex, geometry) {
var adjoiningFaceIndexes;
var coplanarAdjacentFaces = {};
var coplanarAdjacentVertices = {};
var examQueue = [];
var examined = {};
var examFace, examFaceIndex;
var adjoiningFace, adjoiningFaceIndex;
var faces = geometry.faces;
var vertices = geometry.vertices;
var startFace = faces[startFaceIndex];
examQueue.push(startFaceIndex);
// include the start face as a coplanar face
coplanarAdjacentVertices[startFace.a] = true;
coplanarAdjacentVertices[startFace.b] = true;
coplanarAdjacentVertices[startFace.c] = true;
coplanarAdjacentFaces[startFaceIndex] = true;
// Map vertices back to all faces they belong to
assignVertexFaceHashes(geometry);
while (examQueue.length > 0) {
examFaceIndex = examQueue.pop();
examFace = faces[examFaceIndex];
// console.log('examQueue:', examQueue.length);
adjoiningFaceIndexes = [];
for (var vertIndex of [examFace.a, examFace.b, examFace.c]) {
adjoiningFaceIndexes = _.union(adjoiningFaceIndexes, _.map(_.keys(vertices[vertIndex].inFaces), function(c) { return parseInt(c); }));
}
//console.log('adjoiningFaceIndexes:', adjoiningFaceIndexes);
for (adjoiningFaceIndex of adjoiningFaceIndexes) {
//console.log('Examining adjoining face index:', adjoiningFaceIndex);
if (!examined.hasOwnProperty(adjoiningFaceIndex)) {
if ((adjoiningFaceIndex != examFaceIndex) && (!coplanarAdjacentFaces.hasOwnProperty(adjoiningFaceIndex))) {
//console.log('adjoiningFaceIndex:', adjoiningFaceIndex);
adjoiningFace = faces[adjoiningFaceIndex];
if (checkCoplanarity(examFace, adjoiningFace)) {
var overlap1 = [adjoiningFace.a, adjoiningFace.b, adjoiningFace.c];
var overlap2 = [examFace.a, examFace.b, examFace.c];
var vertsInCommon = _.intersection(overlap1, overlap2);
// Check for vertices in common. If any vertices are in comment, these coplanar faces touch at least one vertex.
if (vertsInCommon.length > 0) {
//console.log('Pushing adjoining face due to vertices in common:', adjoiningFaceIndex);
coplanarAdjacentFaces[adjoiningFaceIndex] = true;
examQueue.push(adjoiningFaceIndex);
coplanarAdjacentVertices[adjoiningFace.a] = true;
coplanarAdjacentVertices[adjoiningFace.b] = true;
coplanarAdjacentVertices[adjoiningFace.c] = true;
} else {
// it's possible the adjoining face only touches vertices to the middle of edges, so check for that.
edgeIntersectExam:
for (var i = 0; i < FACELEN; ++i) {
adjoinP1 = overlap1[i];
adjoinP2 = overlap1[(i + 1) % FACELEN];
for (var j = 0; j < FACELEN; ++j) {
splitPoint = distToSegmentSquared3d(vertices[overlap2[j]], vertices[adjoinP1], vertices[adjoinP2]);
if (splitPoint.distance < POINT_ON_LINE_TOLERANCE) {
console.log('adding adjoining face due to edge intersection:', adjoiningFaceIndex);
console.log('j=', j, 'Source face:', examFaceIndex, examFace, 'We found split point on adjoining face index:', adjoiningFaceIndex, adjoiningFace);
coplanarAdjacentFaces[adjoiningFaceIndex] = true;
examQueue.push(adjoiningFaceIndex);
coplanarAdjacentVertices[adjoiningFace.a] = true;
coplanarAdjacentVertices[adjoiningFace.b] = true;
coplanarAdjacentVertices[adjoiningFace.c] = true;
break edgeIntersectExam;
}
}
}
}
}
}
}
}
examined[examFaceIndex] = true;
}
return ({ faces: coplanarAdjacentFaces, vertices: coplanarAdjacentVertices });
}
function assignFacesToCoplanarGroups(csgPrimitive) {
var geometry = csgPrimitive.geometry;
var faceIndexList = _.mapObject(_.keys(geometry.faces), function() { return true; });
var processedFaces = {};
var coplanarFaces;
var faces = geometry.faces;
var intIndex;
var coplanarGroupMax;
var coplanarGroups = [];
for (var processFaceIndex in faceIndexList) {
intIndex = parseInt(processFaceIndex);
if (!processedFaces.hasOwnProperty(intIndex)) {
coplanars = findCoplanarAdjacentFaces(processFaceIndex, geometry);
coplanarGroups.push({ faces: coplanars.faces, vertices: coplanars.vertices });
coplanarGroupMax = coplanarGroups.length - 1;
for (var groupedFaceIndex in coplanars.faces) {
faces[groupedFaceIndex].coplanarGroupIndex = coplanarGroupMax;
faces[groupedFaceIndex].color.setHex(0x0000ff); // just to help see the results
processedFaces[groupedFaceIndex] = true;
}
}
}
geometry.coplanarGroups = coplanarGroups;
geometry.colorsNeedUpdate = true;
}
function assignFacesToAllCoplanarGroups() {
var now = new Date();
var startTime = now.getTime();
for (var csgPrimitive of csgPrimitives.children) {
assignFacesToCoplanarGroups(csgPrimitive);
}
var later = new Date();
var duration = later.getTime() - startTime;
console.log('Done assigning faces to coplanar groups in:', duration, 'ms');
}
function assignFacesToAllCoplanarGroups() {
var now = new Date();
var startTime = now.getTime();
for (var csgPrimitive of csgPrimitives.children) {
assignFacesToCoplanarGroups(csgPrimitive);
}
var later = new Date();
var duration = later.getTime() - startTime;
console.log('Done assigning faces to coplanar groups in:', duration, 'ms');
}
var box = new THREE.Mesh( new THREE.BoxGeometry( width, height, length ) );
// CSG GEOMETRY
cube_bsp = new ThreeBSP( box );
var cutgeo = new THREE.SphereGeometry( 0.5,32,32 );
// move geometry to where the cut should be
var matrix = new THREE.Matrix4();
matrix.setPosition( new THREE.Vector3(0.25, 0, 1.88) ); // NB: sphere does not intersect with cube
cutgeo.applyMatrix( matrix );
var sub = new THREE.Mesh( cutgeo );
var substract_bsp = new ThreeBSP( sub );
var subtract_bsp = cube_bsp.subtract( substract_bsp );
csgPrimitiveMesh = subtract_bsp.toMesh();