4 Nov 2019

Get Volume and Surface Area in the Viewer

Not all file formats contain information about the Volume and Surface Area of the geometry in it. If you need it, then one way could be calculating it from the facets/triangles of the model inside the Viewer.

It might not be as precise as the calculation in Inventor, since this one is depending on the tessellated geometry, but it might be good enough for your needs.

I used Philippe's article to get the triangles for the geometry, and used the answer here to get the Volume from the model triangles - the area is easier since three.js Triangle already has a function for that area()/getArea().

Here are the functions:

function getVolume(viewer, dbId) {
  var volume = 0;
  var it = viewer.model.getData().instanceTree;

  it.enumNodeFragments(dbId, function (fragId) {
    getVertices(viewer, fragId, (p1, p2, p3) => {
      volume += getTriangleVolume(p1, p2, p3)
    })
  }, true);

  return volume;
}

function getSurfaceArea(viewer, dbId) {
  var area = 0;
  var it = viewer.model.getData().instanceTree;

  it.enumNodeFragments(dbId, function (fragId) {
    getVertices(viewer, fragId, (p1, p2, p3) => {
      area += getTriangleArea(p1, p2, p3)
    })
  }, true);

  return area;
}

function getTriangleArea(p1, p2, p3) {
  var tr = new THREE.Triangle(p1, p2, p3);
  return tr.area();
}

function getTriangleVolume(p1, p2, p3) {
  return p1.dot(p2.cross(p3)) / 6.0;
}

function getVertices(viewer, fragId, callback) {
  var fragProxy = viewer.impl.getFragmentProxy(
    viewer.model,
    fragId);

  var renderProxy = viewer.impl.getRenderProxy(
    viewer.model,
    fragId);

  fragProxy.updateAnimTransform();

  var matrix = new THREE.Matrix4();
  fragProxy.getWorldMatrix(matrix);

  var geometry = renderProxy.geometry;

  var attributes = geometry.attributes;

  var vA = new THREE.Vector3();
  var vB = new THREE.Vector3();
  var vC = new THREE.Vector3();

  if (attributes.index !== undefined) {

    var indices = attributes.index.array || geometry.ib;
    var positions = geometry.vb ? geometry.vb : attributes.position.array;
    var stride = geometry.vb ? geometry.vbstride : 3;
    var offsets = geometry.offsets;

    if (!offsets || offsets.length === 0) {

      offsets = [{ start: 0, count: indices.length, index: 0 }];
    }

    for (var oi = 0, ol = offsets.length; oi < ol; ++oi) {

      var start = offsets[oi].start;
      var count = offsets[oi].count;
      var index = offsets[oi].index;

      for (var i = start, il = start + count; i < il; i += 3) {

        var a = index + indices[i];
        var b = index + indices[i + 1];
        var c = index + indices[i + 2];

        vA.fromArray(positions, a * stride);
        vB.fromArray(positions, b * stride);
        vC.fromArray(positions, c * stride);

        vA.applyMatrix4(matrix);
        vB.applyMatrix4(matrix);
        vC.applyMatrix4(matrix);

        callback(vA, vB, vC)
      }
    }
  }
  else {
    var positions = geometry.vb ? geometry.vb : attributes.position.array;
    var stride = geometry.vb ? geometry.vbstride : 3;

    for (var i = 0, j = 0, il = positions.length; i < il; i += 3, j += 9) {

      var a = i;
      var b = i + 1;
      var c = i + 2;

      vA.fromArray(positions, a * stride);
      vB.fromArray(positions, b * stride);
      vC.fromArray(positions, c * stride);

      vA.applyMatrix4(matrix);
      vB.applyMatrix4(matrix);
      vC.applyMatrix4(matrix);

      callback(vA, vB, vC)
    }
  }
}

You can see here the values provided by Inventor for the same model - they are pretty close to the ones we got inside the Viewer ?

Volume and Surface Area values in Inventor

Related Article