Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/11.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Algorithm 在THREEJS网格中分组共面三角形的方法?_Algorithm_Three.js_Computational Geometry_Geometry Surface - Fatal编程技术网

Algorithm 在THREEJS网格中分组共面三角形的方法?

Algorithm 在THREEJS网格中分组共面三角形的方法?,algorithm,three.js,computational-geometry,geometry-surface,Algorithm,Three.js,Computational Geometry,Geometry Surface,我正在开发一个建模工具,可以让你直接操纵网格。例如,您可以抓取一个面并将其拖动。用户对“脸”的感知可能不止一个共面三角形。例如,立方体的顶部“面”实际上是两个三角形,它们作为一个正方形拖在一起 为了实现这一点,我想收集任何特定三角形的所有共面、相邻面,以便在拖动时使用。我已经看过了,也看过了一些例子,但我想保留基本的三角形,而不是减少/删除它们 在过去的好日子里,你会建立一个边缘模型ala,在那里你可以走每一条边去看相邻的面并检查法线 我希望可能有一些代码已经为3J编写,将共面三角形组合在一起。

我正在开发一个建模工具,可以让你直接操纵网格。例如,您可以抓取一个面并将其拖动。用户对“脸”的感知可能不止一个共面三角形。例如,立方体的顶部“面”实际上是两个三角形,它们作为一个正方形拖在一起

为了实现这一点,我想收集任何特定三角形的所有共面、相邻面,以便在拖动时使用。我已经看过了,也看过了一些例子,但我想保留基本的三角形,而不是减少/删除它们

在过去的好日子里,你会建立一个边缘模型ala,在那里你可以走每一条边去看相邻的面并检查法线

我希望可能有一些代码已经为3J编写,将共面三角形组合在一起。如果我从头开始写,我能想到的最好的算法是O(n^2),类似于:

  • 通过遍历所有面的所有顶点来构建边散列(两个方向)。每个条目都是由2个面指针组成的数组。创建或修改网格时,只需执行此步骤一次
  • 当用户拾取要操纵的面时,创建空的计算堆栈并将拾取的面放入该堆栈中。另外,创建空的共面面阵列
  • 将面弹出评估堆栈,然后遍历该面的边。在“边”散列中查找与该边相邻的所有面。如果面是共面的,则将该面推送到计算堆栈上并存储在共面面数组中
  • 重复步骤3-4,直到计算堆栈为空
  • 此算法完成后,您应该拥有一个所有面的阵列,这些面共面并与您开始使用的面相邻。但在我看来,这似乎效率相对较低

    欢迎任何和所有建议/建议

    你的想法行得通

    我添加了一个角度阈值,这样你就可以抓取稍微不共面的地形。我必须制作一个事件,以允许不确定的递归时间。应该对其进行修改,将vertexHash放在mesh.userData中

    //编辑。我已经更新了该类,以使用一个钳制参数,该参数允许您在设置为true时将maxAngle钳制到原始面。当设置为false时,它会将每个面与下一个面进行比较

    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:

  • 在上述算法中相邻面的所有共面顶点集中,找到距离原点最远的顶点
  • 找到离开该顶点的所有边
  • 沿着从原点到该顶点的向量到下一个顶点形成最小角度的边行走
  • At t
    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();