Calculate the volume of a model by calculating the sum of volumes of all meshes.

This commit is contained in:
kovacsv 2022-06-18 16:24:15 +02:00
parent 7372b40c3a
commit 2593bbba4e
11 changed files with 157 additions and 133 deletions

View File

@ -52,7 +52,7 @@ import { MeshInstanceId, MeshInstance } from './model/meshinstance.js';
import { GetMeshType, CalculateTriangleNormal, TransformMesh, FlipMeshTrianglesOrientation, MeshType } from './model/meshutils.js';
import { Model } from './model/model.js';
import { FinalizeModel, CheckModel } from './model/modelfinalization.js';
import { IsModelEmpty, GetBoundingBox, GetTopology, IsSolid, HasDefaultMaterial, ReplaceDefaultMaterialColor } from './model/modelutils.js';
import { IsModelEmpty, GetBoundingBox, GetTopology, IsTwoManifold, HasDefaultMaterial, ReplaceDefaultMaterialColor } from './model/modelutils.js';
import { Node, NodeType } from './model/node.js';
import { Object3D, ModelObject3D } from './model/object.js';
import { Property, PropertyGroup, PropertyToString, PropertyType } from './model/property.js';
@ -242,7 +242,7 @@ export {
IsModelEmpty,
GetBoundingBox,
GetTopology,
IsSolid,
IsTwoManifold,
HasDefaultMaterial,
ReplaceDefaultMaterialColor,
Node,

View File

@ -1,6 +1,7 @@
import { BoundingBoxCalculator3D } from '../geometry/box3d.js';
import { Octree } from '../geometry/octree.js';
import { GetMeshType, MeshType } from './meshutils.js';
import { Model } from './model.js';
import { Topology } from './topology.js';
export function IsModelEmpty (model)
@ -48,7 +49,7 @@ export function GetTopology (object3D)
return topology;
}
export function IsSolid (object3D)
export function IsTwoManifold (object3D)
{
function GetEdgeOrientationInTriangle (topology, triangleIndex, edgeIndex)
{
@ -68,28 +69,30 @@ export function IsSolid (object3D)
return null;
}
const topology = GetTopology (object3D);
for (let edgeIndex = 0; edgeIndex < topology.edges.length; edgeIndex++) {
const edge = topology.edges[edgeIndex];
let triCount = edge.triangles.length;
if (triCount === 0 || triCount % 2 !== 0) {
return false;
}
let edgesDirection = 0;
for (let triIndex = 0; triIndex < edge.triangles.length; triIndex++) {
const triangleIndex = edge.triangles[triIndex];
const edgeOrientation = GetEdgeOrientationInTriangle (topology, triangleIndex, edgeIndex);
if (edgeOrientation) {
edgesDirection += 1;
} else {
edgesDirection -= 1;
if (object3D instanceof Model) {
let isSolid = true;
object3D.EnumerateMeshInstances ((meshInstance) => {
if (isSolid) {
isSolid = IsTwoManifold (meshInstance);
}
});
return isSolid;
} else {
const topology = GetTopology (object3D);
for (let edgeIndex = 0; edgeIndex < topology.edges.length; edgeIndex++) {
const edge = topology.edges[edgeIndex];
if (edge.triangles.length !== 2) {
return false;
}
let edgeOrientation1 = GetEdgeOrientationInTriangle (topology, edge.triangles[0], edgeIndex);
let edgeOrientation2 = GetEdgeOrientationInTriangle (topology, edge.triangles[1], edgeIndex);
if (edgeOrientation1 === null || edgeOrientation2 === null || edgeOrientation1 === edgeOrientation2) {
return false;
}
}
if (edgesDirection !== 0) {
return false;
}
return true;
}
return true;
}
export function HasDefaultMaterial (model)

View File

@ -1,4 +1,5 @@
import { CoordDistance3D, CrossVector3D, DotVector3D } from '../geometry/coord3d.js';
import { Model } from './model.js';
export function GetTriangleArea (v0, v1, v2)
{
@ -20,11 +21,19 @@ export function GetTetrahedronSignedVolume (v0, v1, v2)
export function CalculateVolume (object3D)
{
let volume = 0.0;
object3D.EnumerateTriangleVertices ((v0, v1, v2) => {
volume += GetTetrahedronSignedVolume (v0, v1, v2);
});
return volume;
if (object3D instanceof Model) {
let volume = 0.0;
object3D.EnumerateMeshInstances ((meshInstance) => {
volume += CalculateVolume (meshInstance);
});
return volume;
} else {
let volume = 0.0;
object3D.EnumerateTriangleVertices ((v0, v1, v2) => {
volume += GetTetrahedronSignedVolume (v0, v1, v2);
});
return volume;
}
}
export function CalculateSurfaceArea (object3D)

View File

@ -76,6 +76,7 @@ export class Topology
}
let triangleIndex = this.triangles.length;
let triangle = new TopologyTriangle ();
triangle.triEdge1 = this.AddTriangleEdge (vertex1, vertex2);
triangle.triEdge2 = this.AddTriangleEdge (vertex2, vertex3);

View File

@ -1,6 +1,6 @@
import { RunTaskAsync } from '../engine/core/taskrunner.js';
import { SubCoord3D } from '../engine/geometry/coord3d.js';
import { GetBoundingBox, IsSolid } from '../engine/model/modelutils.js';
import { GetBoundingBox, IsTwoManifold } from '../engine/model/modelutils.js';
import { CalculateVolume, CalculateSurfaceArea } from '../engine/model/quantities.js';
import { Property, PropertyToString, PropertyType } from '../engine/model/property.js';
import { AddDiv, AddDomElement, ClearDomElement } from '../engine/viewer/domutils.js';
@ -39,7 +39,7 @@ export class SidebarDetailsPanel extends SidebarPanel
this.AddProperty (table, new Property (PropertyType.Number, 'Size Y', size.y));
this.AddProperty (table, new Property (PropertyType.Number, 'Size Z', size.z));
this.AddCalculatedProperty (table, 'Volume', () => {
if (!IsSolid (object3D)) {
if (!IsTwoManifold (object3D)) {
return null;
}
const volume = CalculateVolume (object3D);

View File

@ -10,6 +10,7 @@ import geometry_test from './tests/geometry_test.js';
import meshbuffer_test from './tests/meshbuffer_test.js';
import mesh_test from './tests/mesh_test.js';
import modelutils_test from './tests/modelutils_test.js';
import topology_test from './tests/topology_test.js';
import node_test from './tests/node_test.js';
import model_test from './tests/model_test.js';
import quantities_test from './tests/quantities_test.js';
@ -41,6 +42,7 @@ geometry_test ();
meshbuffer_test ();
mesh_test ();
modelutils_test ();
topology_test ();
node_test ();
model_test ();
quantities_test ();

View File

@ -7,7 +7,7 @@ export default function suite ()
describe ('Generator', function () {
it ('Cuboid with Default Parameters', function () {
const cuboid = OV.GenerateCuboid (null, 1.0, 1.0, 1.0);
assert.ok (OV.IsSolid (cuboid));
assert.ok (OV.IsTwoManifold (cuboid));
assert.ok (OV.IsEqual (OV.CalculateVolume (cuboid), 1.0));
});
@ -22,53 +22,53 @@ describe ('Generator', function () {
it ('Cylinder with Default Parameters', function () {
const cylinder = OV.GenerateCylinder (null, 0.5, 1.0, 25, false);
assert.ok (OV.IsSolid (cylinder));
assert.ok (OV.IsTwoManifold (cylinder));
assert.ok (OV.IsEqualEps (OV.CalculateVolume (cylinder), Math.PI * 0.5 * 0.5 * 1.0, 0.1));
});
it ('Cone with Default Parameters', function () {
const cone = OV.GenerateCone (null, 0.2, 0.5, 1.0, 20, false);
assert.ok (OV.IsSolid (cone));
assert.ok (OV.IsTwoManifold (cone));
assert.ok (OV.IsEqualEps (OV.CalculateVolume (cone), Math.PI / 3.0 * 1.0 * (0.2 * 0.2 + 0.2 * 0.5 + 0.5 * 0.5), 0.1));
});
it ('Cone Zero Top', function () {
const cone = OV.GenerateCone (null, 0.0, 0.5, 1.0, 20, false);
assert.ok (OV.IsSolid (cone));
assert.ok (OV.IsTwoManifold (cone));
assert.ok (OV.IsEqualEps (OV.CalculateVolume (cone), 0.5 * 0.5 * Math.PI * 1.0 / 3.0, 1.0));
});
it ('Cone Zero Bottom', function () {
const cone = OV.GenerateCone (null, 0.5, 0.0, 1.0, 20, false);
assert.ok (OV.IsSolid (cone));
assert.ok (OV.IsTwoManifold (cone));
assert.ok (OV.IsEqualEps (OV.CalculateVolume (cone), 0.5 * 0.5 * Math.PI * 1.0 / 3.0, 1.0));
});
it ('Sphere with Default Parameters', function () {
const cylinder = OV.GenerateSphere (null, 0.5, 20, false);
assert.ok (OV.IsSolid (cylinder));
assert.ok (OV.IsTwoManifold (cylinder));
assert.ok (OV.IsEqualEps (OV.CalculateVolume (cylinder), Math.PI * 0.5 * 0.5 * 0.5 * 4.0 / 3.0, 0.1));
});
it ('Platonic Solids', function () {
let tetrahedron = OV.GeneratePlatonicSolid (null, 'tetrahedron', 1.0);
assert.ok (OV.IsSolid (tetrahedron));
assert.ok (OV.IsTwoManifold (tetrahedron));
assert.ok (OV.IsEqual (OV.CalculateVolume (tetrahedron), 0.5132002392796676));
let hexahedron = OV.GeneratePlatonicSolid (null, 'hexahedron', 1.0);
assert.ok (OV.IsSolid (hexahedron));
assert.ok (OV.IsTwoManifold (hexahedron));
assert.ok (OV.IsEqual (OV.CalculateVolume (hexahedron), 1.5396007178390028));
let octahedron = OV.GeneratePlatonicSolid (null, 'octahedron', 1.0);
assert.ok (OV.IsSolid (octahedron));
assert.ok (OV.IsTwoManifold (octahedron));
assert.ok (OV.IsEqual (OV.CalculateVolume (octahedron), 1.3333333333333333));
let dodecahedron = OV.GeneratePlatonicSolid (null, 'dodecahedron', 1.0);
assert.ok (OV.IsSolid (dodecahedron));
assert.ok (OV.IsTwoManifold (dodecahedron));
assert.ok (OV.IsEqual (OV.CalculateVolume (dodecahedron), 2.7851638631226248));
let icosahedron = OV.GeneratePlatonicSolid (null, 'icosahedron', 1.0);
assert.ok (OV.IsSolid (icosahedron));
assert.ok (OV.IsTwoManifold (icosahedron));
assert.ok (OV.IsEqual (OV.CalculateVolume (icosahedron), 2.5361507101204093));
});
});

View File

@ -115,7 +115,7 @@ describe ('O3dv Importer', function () {
assert.strictEqual (model.MeshCount (), 5);
assert.strictEqual (model.MeshInstanceCount (), 5);
assert.ok (OV.IsSolid (model));
assert.ok (OV.IsTwoManifold (model));
assert.ok (OV.IsEqual (OV.CalculateVolume (model), 8.707448863695035));
assert.ok (OV.IsEqual (OV.CalculateSurfaceArea (model), 39.636169009449105));

View File

@ -1,6 +1,5 @@
import * as assert from 'assert';
import * as OV from '../../source/engine/main.js';
import { GetModelWithOneMesh, GetTetrahedronMesh, GetTwoCubesConnectingInOneEdgeModel, GetTwoCubesConnectingInOneFaceModel, GetTwoCubesConnectingInOneVertexModel } from '../utils/testutils.js';
export default function suite ()
{
@ -55,80 +54,6 @@ describe ('Model Utils', function () {
assert.ok (OV.CoordIsEqual3D (modelBounds.min, new OV.Coord3D (0.0, 0.0, 0.0)));
assert.ok (OV.CoordIsEqual3D (modelBounds.max, new OV.Coord3D (1.0, 1.0, 1.0)));
});
it ('Tetrahedron Topology Calculation', function () {
let tetrahedron = GetModelWithOneMesh (GetTetrahedronMesh ());
let topology = OV.GetTopology (tetrahedron);
assert.ok (OV.IsSolid (tetrahedron));
assert.strictEqual (topology.vertices.length, 4);
assert.strictEqual (topology.edges.length, 6);
assert.strictEqual (topology.triangleEdges.length, 4 * 3);
assert.strictEqual (topology.triangles.length, 4);
for (let i = 0; i < topology.vertices.length; i++) {
assert.strictEqual (topology.vertices[i].edges.length, 3);
assert.strictEqual (topology.vertices[i].triangles.length, 3);
}
for (let i = 0; i < topology.edges.length; i++) {
assert.strictEqual (topology.edges[i].triangles.length, 2);
}
});
it ('Cube Topology Calculation', function () {
let cube = GetModelWithOneMesh (OV.GenerateCuboid (null, 1.0, 1.0, 1.0));
assert.ok (OV.IsSolid (cube));
let topology = OV.GetTopology (cube);
assert.strictEqual (topology.vertices.length, 8);
assert.strictEqual (topology.edges.length, 12 + 6);
assert.strictEqual (topology.triangleEdges.length, 6 * 2 * 3);
assert.strictEqual (topology.triangles.length, 6 * 2);
let verticesWith4Triangles = 0;
let verticesWith5Triangles = 0;
let verticesWith4Edges = 0;
let verticesWith5Edges = 0;
for (let i = 0; i < topology.vertices.length; i++) {
if (topology.vertices[i].triangles.length == 4) {
verticesWith4Triangles += 1;
} else if (topology.vertices[i].triangles.length == 5) {
verticesWith5Triangles += 1;
}
if (topology.vertices[i].edges.length == 4) {
verticesWith4Edges += 1;
} else if (topology.vertices[i].edges.length == 5) {
verticesWith5Edges += 1;
}
}
assert.strictEqual (verticesWith4Triangles, 4);
assert.strictEqual (verticesWith5Triangles, 4);
assert.strictEqual (verticesWith4Edges, 4);
assert.strictEqual (verticesWith5Edges, 4);
for (let i = 0; i < topology.edges.length; i++) {
assert.strictEqual (topology.edges[i].triangles.length, 2);
}
});
it ('Two Cubes Connecting in One Vertex Topology Calculation', function () {
const model = GetTwoCubesConnectingInOneVertexModel ();
let topology = OV.GetTopology (model);
assert.strictEqual (topology.vertices.length, 15);
assert.ok (OV.IsSolid (model));
});
it ('Two Cubes Connecting in One Edge Topology Calculation', function () {
const model = GetTwoCubesConnectingInOneEdgeModel ();
let topology = OV.GetTopology (model);
assert.strictEqual (topology.vertices.length, 14);
assert.ok (OV.IsSolid (model));
});
it ('Two Cubes Connecting in One Face Topology Calculation', function () {
const model = GetTwoCubesConnectingInOneFaceModel ();
let topology = OV.GetTopology (model);
assert.strictEqual (topology.vertices.length, 12);
assert.ok (OV.IsSolid (model));
});
});
}

View File

@ -9,8 +9,8 @@ describe ('Quantities', function () {
it ('Cube Volume Calculation', function () {
const mesh = OV.GenerateCuboid (null, 1.0, 1.0, 1.0);
const model = GetModelWithOneMesh (mesh);
assert.ok (OV.IsSolid (mesh));
assert.ok (OV.IsSolid (model));
assert.ok (OV.IsTwoManifold (mesh));
assert.ok (OV.IsTwoManifold (model));
assert.ok (OV.IsEqual (OV.CalculateVolume (mesh), 1.0));
assert.ok (OV.IsEqual (OV.CalculateVolume (model), 1.0));
});
@ -18,24 +18,24 @@ describe ('Quantities', function () {
it ('Cube with Missing Face Volume Calculation', function () {
const mesh = GetCubeWithOneMissingFaceMesh ();
const model = GetModelWithOneMesh (mesh);
assert.ok (!OV.IsSolid (model));
assert.ok (!OV.IsTwoManifold (model));
});
it ('Two Cubes Connecting in One Vertex Volume Calculation', function () {
const model = GetTwoCubesConnectingInOneVertexModel ();
assert.ok (OV.IsSolid (model));
assert.ok (OV.IsTwoManifold (model));
assert.ok (OV.IsEqual (OV.CalculateVolume (model), 2.0));
});
it ('Two Cubes Connecting in One Edge Volume Calculation', function () {
const model = GetTwoCubesConnectingInOneEdgeModel ();
assert.ok (OV.IsSolid (model));
assert.ok (OV.IsTwoManifold (model));
assert.ok (OV.IsEqual (OV.CalculateVolume (model), 2.0));
});
it ('Two Cubes Connecting in One Face Volume Calculation', function () {
const model = GetTwoCubesConnectingInOneFaceModel ();
assert.ok (OV.IsSolid (model));
assert.ok (OV.IsTwoManifold (model));
assert.ok (OV.IsEqual (OV.CalculateVolume (model), 2.0));
});
@ -62,15 +62,15 @@ describe ('Quantities', function () {
mesh.AddTriangle (new OV.Triangle (4, 5, 6));
mesh.AddTriangle (new OV.Triangle (4, 7, 6));
const model = GetModelWithOneMesh (mesh);
assert.ok (!OV.IsSolid (mesh));
assert.ok (!OV.IsSolid (model));
assert.ok (!OV.IsTwoManifold (mesh));
assert.ok (!OV.IsTwoManifold (model));
});
it ('Cube Surface Area Calculation', function () {
const mesh = OV.GenerateCuboid (null, 1.0, 1.0, 1.0);
const model = GetModelWithOneMesh (mesh);
assert.ok (OV.IsSolid (mesh));
assert.ok (OV.IsSolid (model));
assert.ok (OV.IsTwoManifold (mesh));
assert.ok (OV.IsTwoManifold (model));
assert.ok (OV.IsEqual (OV.CalculateSurfaceArea (mesh), 6.0));
assert.ok (OV.IsEqual (OV.CalculateSurfaceArea (model), 6.0));
});
@ -78,8 +78,8 @@ describe ('Quantities', function () {
it ('Cube with Missing Face Surface Area Calculation', function () {
const mesh = GetCubeWithOneMissingFaceMesh ();
const model = GetModelWithOneMesh (mesh);
assert.ok (!OV.IsSolid (mesh));
assert.ok (!OV.IsSolid (model));
assert.ok (!OV.IsTwoManifold (mesh));
assert.ok (!OV.IsTwoManifold (model));
assert.ok (OV.IsEqual (OV.CalculateSurfaceArea (mesh), 5.0));
assert.ok (OV.IsEqual (OV.CalculateSurfaceArea (model), 5.0));
});
@ -88,8 +88,8 @@ describe ('Quantities', function () {
const edgeLength = OV.CoordDistance3D (new OV.Coord3D (1.0, 1.0, 1.0), new OV.Coord3D (-1.0, -1.0, 1.0));
const mesh = GetTetrahedronMesh ();
const model = GetModelWithOneMesh (mesh);
assert.ok (OV.IsSolid (mesh));
assert.ok (OV.IsSolid (model));
assert.ok (OV.IsTwoManifold (mesh));
assert.ok (OV.IsTwoManifold (model));
assert.ok (OV.IsEqual (OV.CalculateVolume (mesh), Math.pow (edgeLength, 3.0) / (6.0 * Math.sqrt (2))));
assert.ok (OV.IsEqual (OV.CalculateVolume (model), Math.pow (edgeLength, 3.0) / (6.0 * Math.sqrt (2))));
});
@ -98,8 +98,8 @@ describe ('Quantities', function () {
const edgeLength = OV.CoordDistance3D (new OV.Coord3D (1.0, 1.0, 1.0), new OV.Coord3D (-1.0, -1.0, 1.0));
const mesh = GetTetrahedronMesh ();
const model = GetModelWithOneMesh (mesh);
assert.ok (OV.IsSolid (mesh));
assert.ok (OV.IsSolid (model));
assert.ok (OV.IsTwoManifold (mesh));
assert.ok (OV.IsTwoManifold (model));
assert.ok (OV.IsEqual (OV.CalculateSurfaceArea (mesh), Math.sqrt (3) * Math.pow (edgeLength, 2.0)));
assert.ok (OV.IsEqual (OV.CalculateSurfaceArea (model), Math.sqrt (3) * Math.pow (edgeLength, 2.0)));
});
@ -110,7 +110,7 @@ describe ('Quantities', function () {
let node = new OV.Node ();
node.SetTransformation (transformation);
const meshInstance = new OV.MeshInstance (null, node, mesh);
assert.ok (OV.IsSolid (meshInstance));
assert.ok (OV.IsTwoManifold (meshInstance));
assert.ok (OV.IsEqual (OV.CalculateVolume (meshInstance), 8.0));
assert.ok (OV.IsEqual (OV.CalculateSurfaceArea (meshInstance), 24.0));
});

View File

@ -0,0 +1,84 @@
import * as assert from 'assert';
import * as OV from '../../source/engine/main.js';
import { GetModelWithOneMesh, GetTetrahedronMesh, GetTwoCubesConnectingInOneEdgeModel, GetTwoCubesConnectingInOneFaceModel, GetTwoCubesConnectingInOneVertexModel } from '../utils/testutils.js';
export default function suite ()
{
describe ('Topology', function () {
it ('Tetrahedron Topology Calculation', function () {
let tetrahedron = GetModelWithOneMesh (GetTetrahedronMesh ());
let topology = OV.GetTopology (tetrahedron);
assert.ok (OV.IsTwoManifold (tetrahedron));
assert.strictEqual (topology.vertices.length, 4);
assert.strictEqual (topology.edges.length, 6);
assert.strictEqual (topology.triangleEdges.length, 4 * 3);
assert.strictEqual (topology.triangles.length, 4);
for (let i = 0; i < topology.vertices.length; i++) {
assert.strictEqual (topology.vertices[i].edges.length, 3);
assert.strictEqual (topology.vertices[i].triangles.length, 3);
}
for (let i = 0; i < topology.edges.length; i++) {
assert.strictEqual (topology.edges[i].triangles.length, 2);
}
});
it ('Cube Topology Calculation', function () {
let cube = GetModelWithOneMesh (OV.GenerateCuboid (null, 1.0, 1.0, 1.0));
assert.ok (OV.IsTwoManifold (cube));
let topology = OV.GetTopology (cube);
assert.strictEqual (topology.vertices.length, 8);
assert.strictEqual (topology.edges.length, 12 + 6);
assert.strictEqual (topology.triangleEdges.length, 6 * 2 * 3);
assert.strictEqual (topology.triangles.length, 6 * 2);
let verticesWith4Triangles = 0;
let verticesWith5Triangles = 0;
let verticesWith4Edges = 0;
let verticesWith5Edges = 0;
for (let i = 0; i < topology.vertices.length; i++) {
if (topology.vertices[i].triangles.length == 4) {
verticesWith4Triangles += 1;
} else if (topology.vertices[i].triangles.length == 5) {
verticesWith5Triangles += 1;
}
if (topology.vertices[i].edges.length == 4) {
verticesWith4Edges += 1;
} else if (topology.vertices[i].edges.length == 5) {
verticesWith5Edges += 1;
}
}
assert.strictEqual (verticesWith4Triangles, 4);
assert.strictEqual (verticesWith5Triangles, 4);
assert.strictEqual (verticesWith4Edges, 4);
assert.strictEqual (verticesWith5Edges, 4);
for (let i = 0; i < topology.edges.length; i++) {
assert.strictEqual (topology.edges[i].triangles.length, 2);
}
});
it ('Two Cubes Connecting in One Vertex Topology Calculation', function () {
const model = GetTwoCubesConnectingInOneVertexModel ();
let topology = OV.GetTopology (model);
assert.strictEqual (topology.vertices.length, 15);
assert.ok (OV.IsTwoManifold (model));
});
it ('Two Cubes Connecting in One Edge Topology Calculation', function () {
const model = GetTwoCubesConnectingInOneEdgeModel ();
let topology = OV.GetTopology (model);
assert.strictEqual (topology.vertices.length, 14);
assert.ok (OV.IsTwoManifold (model));
});
it ('Two Cubes Connecting in One Face Topology Calculation', function () {
const model = GetTwoCubesConnectingInOneFaceModel ();
let topology = OV.GetTopology (model);
assert.strictEqual (topology.vertices.length, 12);
assert.ok (OV.IsTwoManifold (model));
});
});
}