convert to ES6 module (#322)

* es6 tests work

* fixed model-viewer example

* fixed three example

* worker not quite working

* removed three.js dependency

* switched to top-level await

* added normalIdx binding

* fix binding
This commit is contained in:
Emmett Lalish
2023-02-16 08:09:31 -08:00
committed by GitHub
parent 171fe5466e
commit 02affd5247
13 changed files with 469 additions and 435 deletions
+2 -2
View File
@@ -104,8 +104,8 @@
"editor.formatOnSave": true,
"cmake.configureArgs": [
"-DBUILD_SHARED_LIBS=ON",
"-DMANIFOLD_EXPORT=ON",
"-DMANIFOLD_DEBUG=ON",
"-DMANIFOLD_EXPORT=OFF",
"-DMANIFOLD_DEBUG=OFF",
"-DMANIFOLD_USE_CUDA=OFF",
"-DMANIFOLD_PAR=NONE",
"-DCODE_COVERAGE=OFF",
+1 -1
View File
@@ -22,7 +22,7 @@ set_source_files_properties(bindings.cpp OBJECT_DEPENDS
target_link_libraries(manifoldjs manifold sdf)
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS} -fexceptions)
target_link_options(manifoldjs PUBLIC --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js --bind -sALLOW_TABLE_GROWTH=1
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1)
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1 -sEXPORT_ES6=1)
target_compile_features(manifoldjs PUBLIC cxx_std_17)
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
+2 -2
View File
@@ -58,8 +58,8 @@ std::vector<SimplePolygon> ToPolygon(
return simplePolygons;
}
val GetMeshJS(const Manifold& manifold) {
MeshGL mesh = manifold.GetMeshGL();
val GetMeshJS(const Manifold& manifold, const glm::ivec3& normalIdx) {
MeshGL mesh = manifold.GetMeshGL(normalIdx);
val meshJS = val::object();
meshJS.set("numProp", mesh.numProp);
+47 -44
View File
@@ -13,7 +13,7 @@
// limitations under the License.
var _ManifoldInitialized = false;
Module.setup = function () {
Module.setup = function() {
if (_ManifoldInitialized) return;
_ManifoldInitialized = true;
@@ -38,11 +38,11 @@ Module.setup = function () {
polygons = [polygons];
}
return toVec(
new Module.Vector2_vec2(), polygons,
poly => toVec(new Module.Vector_vec2(), poly, p => {
if (p instanceof Array) return { x: p[0], y: p[1] };
return p;
}));
new Module.Vector2_vec2(), polygons,
poly => toVec(new Module.Vector_vec2(), poly, p => {
if (p instanceof Array) return {x: p[0], y: p[1]};
return p;
}));
}
function disposePolygons(polygonsVec) {
@@ -52,15 +52,15 @@ Module.setup = function () {
function vararg2vec(vec) {
if (vec[0] instanceof Array)
return { x: vec[0][0], y: vec[0][1], z: vec[0][2] };
return {x: vec[0][0], y: vec[0][1], z: vec[0][2]};
if (typeof (vec[0]) == 'number')
// default to 0
return { x: vec[0] || 0, y: vec[1] || 0, z: vec[2] || 0 };
return {x: vec[0] || 0, y: vec[1] || 0, z: vec[2] || 0};
return vec[0];
}
Module.Manifold.prototype.warp = function (func) {
const wasmFuncPtr = addFunction(function (vec3Ptr) {
Module.Manifold.prototype.warp = function(func) {
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'float');
const y = getValue(vec3Ptr + 4, 'float');
const z = getValue(vec3Ptr + 8, 'float');
@@ -76,7 +76,7 @@ Module.setup = function () {
};
// note that the matrix is using column major (same as glm)
Module.Manifold.prototype.transform = function (mat) {
Module.Manifold.prototype.transform = function(mat) {
console.assert(mat.length == 4, 'expects a 3x4 matrix');
const vec = new Module.Vector_f32();
for (let col of mat) {
@@ -88,29 +88,29 @@ Module.setup = function () {
return result;
};
Module.Manifold.prototype.translate = function (...vec) {
Module.Manifold.prototype.translate = function(...vec) {
return this._Translate(vararg2vec(vec));
};
Module.Manifold.prototype.rotate = function (vec) {
Module.Manifold.prototype.rotate = function(vec) {
return this._Rotate(...vec);
};
Module.Manifold.prototype.scale = function (vec) {
Module.Manifold.prototype.scale = function(vec) {
if (typeof vec == 'number') {
return this._Scale({ x: vec, y: vec, z: vec });
return this._Scale({x: vec, y: vec, z: vec});
}
return this._Scale(vararg2vec([vec]));
};
Module.Manifold.prototype.decompose = function () {
Module.Manifold.prototype.decompose = function() {
const vec = this._Decompose();
const result = fromVec(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.getCurvature = function () {
Module.Manifold.prototype.getCurvature = function() {
const result = this._getCurvature();
const oldMeanCurvature = result.vertMeanCurvature;
const oldGaussianCurvature = result.vertGaussianCurvature;
@@ -167,7 +167,8 @@ Module.setup = function () {
}
extras(vert) {
return this.vertProperties.subarray(numProp * vert + 3, numProp * (vert + 1));
return this.vertProperties.subarray(
numProp * vert + 3, numProp * (vert + 1));
}
tangent(halfedge) {
@@ -181,11 +182,13 @@ Module.setup = function () {
Module.Mesh = Mesh;
Module.Manifold.prototype.getMesh = function () {
return new Mesh(this._GetMeshJS());
Module.Manifold.prototype.getMesh = function(normalIdx = [0, 0, 0]) {
if (normalIdx instanceof Array)
normalIdx = {0: normalIdx[0], 1: normalIdx[1], 2: normalIdx[2]};
return new Mesh(this._GetMeshJS(normalIdx));
};
Module.Manifold.prototype.boundingBox = function () {
Module.Manifold.prototype.boundingBox = function() {
const result = this._boundingBox();
return {
min: ['x', 'y', 'z'].map(f => result.min[f]),
@@ -236,11 +239,11 @@ Module.setup = function () {
Module.ManifoldError.prototype = Object.create(Error.prototype, {
constructor:
{ value: Module.ManifoldError, writable: true, configurable: true }
{value: Module.ManifoldError, writable: true, configurable: true}
});
const ManifoldCtor = Module.Manifold;
Module.Manifold = function (mesh) {
Module.Manifold = function(mesh) {
const manifold = new ManifoldCtor(mesh);
const status = manifold.status();
@@ -253,30 +256,30 @@ Module.setup = function () {
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
Module.cube = function (...args) {
Module.cube = function(...args) {
let size = undefined;
if (args.length == 0)
size = { x: 1, y: 1, z: 1 };
size = {x: 1, y: 1, z: 1};
else if (typeof args[0] == 'number')
size = { x: args[0], y: args[0], z: args[0] };
size = {x: args[0], y: args[0], z: args[0]};
else
size = vararg2vec(args);
const center = args[1] || false;
return Module._Cube(size, center);
};
Module.cylinder = function (
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
center = false) {
Module.cylinder = function(
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
center = false) {
return Module._Cylinder(
height, radiusLow, radiusHigh, circularSegments, center);
height, radiusLow, radiusHigh, circularSegments, center);
};
Module.sphere = function (radius, circularSegments = 0) {
Module.sphere = function(radius, circularSegments = 0) {
return Module._Sphere(radius, circularSegments);
};
Module.smooth = function (mesh, sharpenedEdges = []) {
Module.smooth = function(mesh, sharpenedEdges = []) {
const sharp = new Module.Vector_smoothness();
toVec(sharp, sharpenedEdges);
const result = Module._Smooth(mesh, sharp);
@@ -284,25 +287,25 @@ Module.setup = function () {
return result;
};
Module.extrude = function (
polygons, height, nDivisions = 0, twistDegrees = 0.0,
scaleTop = [1.0, 1.0]) {
if (scaleTop instanceof Array) scaleTop = { x: scaleTop[0], y: scaleTop[1] };
Module.extrude = function(
polygons, height, nDivisions = 0, twistDegrees = 0.0,
scaleTop = [1.0, 1.0]) {
if (scaleTop instanceof Array) scaleTop = {x: scaleTop[0], y: scaleTop[1]};
const polygonsVec = polygons2vec(polygons);
const result = Module._Extrude(
polygonsVec, height, nDivisions, twistDegrees, scaleTop);
polygonsVec, height, nDivisions, twistDegrees, scaleTop);
disposePolygons(polygonsVec);
return result;
};
Module.revolve = function (polygons, circularSegments = 0) {
Module.revolve = function(polygons, circularSegments = 0) {
const polygonsVec = polygons2vec(polygons);
const result = Module._Revolve(polygonsVec, circularSegments);
disposePolygons(polygonsVec);
return result;
};
Module.compose = function (manifolds) {
Module.compose = function(manifolds) {
const vec = new Module.Vector_manifold();
toVec(vec, manifolds);
const result = Module._Compose(vec);
@@ -310,12 +313,12 @@ Module.setup = function () {
return result;
};
Module.levelSet = function (sdf, bounds, edgeLength, level = 0) {
Module.levelSet = function(sdf, bounds, edgeLength, level = 0) {
const bounds2 = {
min: { x: bounds.min[0], y: bounds.min[1], z: bounds.min[2] },
max: { x: bounds.max[0], y: bounds.max[1], z: bounds.max[2] },
min: {x: bounds.min[0], y: bounds.min[1], z: bounds.min[2]},
max: {x: bounds.max[0], y: bounds.max[1], z: bounds.max[2]},
};
const wasmFuncPtr = addFunction(function (vec3Ptr) {
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'float');
const y = getValue(vec3Ptr + 4, 'float');
const z = getValue(vec3Ptr + 8, 'float');
@@ -328,7 +331,7 @@ Module.setup = function () {
};
function batchbool(name) {
return function (...args) {
return function(...args) {
if (args.length == 1) args = args[0];
const v = new Module.Vector_manifold();
for (const m of args) v.push_back(m);
+2 -1
View File
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {examples} from './examples.js';
const exampleFunctions = examples.functionBodies;
let editor = undefined;
@@ -309,7 +310,7 @@ let objectURL = null;
let manifoldWorker = null;
function createWorker() {
manifoldWorker = new Worker('worker.js');
manifoldWorker = new Worker('worker.js', {type: 'module'});
manifoldWorker.onmessage = function(e) {
if (e.data == null) {
if (tsWorker != null && !manifoldInitialized) {
+117 -99
View File
@@ -12,14 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
(function (exports) {
exports.functions = {
Intro: function () {
// Write code in JavaScript or TypeScript and this editor will show the API
// docs. Type e.g. "box." to see the Manifold API. Type "Module." to see the
// static API - these functions can also be used bare. Use console.log() to
// print output (lower-right). This editor defines Z as up and units of mm.
export const examples = {
functions: {
Intro: function() {
// Write code in JavaScript or TypeScript and this editor will show the
// API docs. Type e.g. "box." to see the Manifold API. Type "Module." to
// see the static API - these functions can also be used bare. Use
// console.log() to print output (lower-right). This editor defines Z as
// up and units of mm.
const box = cube([100, 100, 100], true);
const ball = sphere(60, 100);
@@ -29,24 +29,24 @@
// All changes are automatically saved and restored between sessions.
// This app is purely local - there is no server communication.
// This means it will work equally well offline once loaded.
// To share your code with another browser/device, simply copy the text to a
// file.
// To share your code with another browser/device, simply copy the text to
// a file.
// See the script drop-down above ("Intro") for usage examples.
return result;
},
TetrahedronPuzzle: function () {
// A tetrahedron cut into two identical halves that can screw together as a
// puzzle. This only outputs one of the halves. This demonstrates how
TetrahedronPuzzle: function() {
// A tetrahedron cut into two identical halves that can screw together as
// a puzzle. This only outputs one of the halves. This demonstrates how
// redundant points along a polygon can be used to make twisted extrusions
// smoother. Based on the screw puzzle by George Hart:
// https://www.thingiverse.com/thing:186372
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
const gap = 0.2; // Spacing between the two halves to allow sliding.
const gap = 0.2; // Spacing between the two halves to allow sliding.
const nDivisions =
50; // Number of divisions (both ways) in the screw surface.
50; // Number of divisions (both ways) in the screw surface.
const scale = edgeLength / (2 * Math.sqrt(2));
@@ -59,15 +59,15 @@
}
const screw = extrude(box, 2, nDivisions, 270)
.rotate([0, 0, -45])
.translate([0, 0, -1])
.scale(scale);
.rotate([0, 0, -45])
.translate([0, 0, -1])
.scale(scale);
const result = tet.intersect(screw);
return result;
},
RoundedFrame: function () {
RoundedFrame: function() {
// Demonstrates how at 90-degree intersections, the sphere and cylinder
// facets match up perfectly, for any choice of global resolution
// parameters.
@@ -84,7 +84,7 @@
let edge2 = edge1.rotate([0, 0, 180]);
edge2 = union(edge2, edge1);
edge2 =
union(edge2, edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
union(edge2, edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
let edge4 = edge2.rotate([0, 0, 90]);
edge4 = union(edge4, edge2);
@@ -101,9 +101,9 @@
return result;
},
Heart: function () {
// Smooth, complex manifolds can be created using the warp() function. This
// example recreates the Exploitable Heart by Emmett Lalish:
Heart: function() {
// Smooth, complex manifolds can be created using the warp() function.
// This example recreates the Exploitable Heart by Emmett Lalish:
// https://www.thingiverse.com/thing:6190
const func = (v) => {
@@ -122,7 +122,8 @@
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
// Derivative
const df = 6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
const df =
6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
return f / df;
};
// Newton's method for root finding
@@ -145,9 +146,9 @@
return result;
},
Scallop: function () {
// A smoothed manifold demonstrating selective edge sharpening with smooth()
// and refine(), see more details at:
Scallop: function() {
// A smoothed manifold demonstrating selective edge sharpening with
// smooth() and refine(), see more details at:
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
const height = 10;
@@ -168,18 +169,18 @@
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
vertPos.push(
radius * Math.cos(theta), radius * Math.sin(theta),
amp * (i % 2 == 0 ? 1 : -1));
radius * Math.cos(theta), radius * Math.sin(theta),
amp * (i % 2 == 0 ? 1 : -1));
let j = i + 1;
if (j == 2 * wiggles) j = 0;
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
let halfedge = triVerts.length + 1;
sharpenedEdges.push({ halfedge, smoothness });
sharpenedEdges.push({halfedge, smoothness});
triVerts.push(0, 2 + i, 2 + j);
halfedge = triVerts.length + 1;
sharpenedEdges.push({ halfedge, smoothness });
sharpenedEdges.push({halfedge, smoothness});
triVerts.push(1, 2 + j, 2 + i);
}
@@ -190,12 +191,12 @@
return result;
},
TorusKnot: function () {
TorusKnot: function() {
// Creates a classic torus knot, defined as a string wrapping periodically
// around the surface of an imaginary donut. If p and q have a common factor
// then you will get multiple separate, interwoven knots. This is an example
// of using the warp() method, thus avoiding any direct handling of
// triangles.
// around the surface of an imaginary donut. If p and q have a common
// factor then you will get multiple separate, interwoven knots. This is
// an example of using the warp() method, thus avoiding any direct
// handling of triangles.
// @param p The number of times the thread passes through the donut hole.
// @param q The number of times the thread circles the donut.
@@ -210,8 +211,8 @@
// Default makes roughly square facets.
function torusKnot(
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
linearSegments = 0) {
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
linearSegments = 0) {
function gcd(a, b) {
return b == 0 ? a : gcd(b, a % b);
}
@@ -220,9 +221,9 @@
p /= kLoops;
q /= kLoops;
const n = circularSegments > 2 ? circularSegments :
getCircularSegments(threadRadius);
getCircularSegments(threadRadius);
const m = linearSegments > 2 ? linearSegments :
n * q * majorRadius / threadRadius;
n * q * majorRadius / threadRadius;
const circle = [];
const dPhi = 2 * 3.14159 / n;
@@ -231,28 +232,43 @@
circle.push([Math.cos(dPhi * i) + offset, Math.sin(dPhi * i)]);
}
const point = new THREE.Vector3();
const axis = new THREE.Vector3();
function rotateVector(v, axis, angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const c = (1 - cos) * (v.x * axis.x + v.y * axis.y + v.z * axis.z);
const func =
(v) => {
const psi = q * Math.atan2(v[0], v[1]);
const theta = psi * p / q;
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
const phi = Math.atan2(x1 - offset, v[2]);
point.set(Math.cos(phi), 0, Math.sin(phi))
.multiplyScalar(threadRadius);
const r = majorRadius + minorRadius * Math.cos(theta);
point.applyAxisAngle(
axis.set(1, 0, 0), -Math.atan2(p * minorRadius, q * r));
point.x += minorRadius;
point.applyAxisAngle(axis.set(0, 1, 0), theta);
point.x += majorRadius;
point.applyAxisAngle(axis.set(0, 0, 1), psi);
v[0] = point.x;
v[1] = point.y;
v[2] = point.z;
const x =
cos * v.x + sin * (axis.y * v.z - axis.z * v.y) + c * axis.x;
const y =
cos * v.y + sin * (axis.z * v.x - axis.x * v.z) + c * axis.y;
const z =
cos * v.z + sin * (axis.x * v.y - axis.y * v.x) + c * axis.z;
v.x = x;
v.y = y;
v.z = z;
}
const func = (v) => {
const psi = q * Math.atan2(v[0], v[1]);
const theta = psi * p / q;
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
const phi = Math.atan2(x1 - offset, v[2]);
const point = {
x: threadRadius * Math.cos(phi),
y: 0,
z: threadRadius * Math.sin(phi)
};
const r = majorRadius + minorRadius * Math.cos(theta);
rotateVector(
point, {x: 1, y: 0, z: 0}, -Math.atan2(p * minorRadius, q * r));
point.x += minorRadius;
rotateVector(point, {x: 0, y: 1, z: 0}, theta);
point.x += majorRadius;
rotateVector(point, {x: 0, y: 0, z: 1}, psi);
v[0] = point.x;
v[1] = point.y;
v[2] = point.z;
};
let knot = revolve(circle, m).warp(func);
@@ -274,7 +290,7 @@
return result;
},
MengerSponge: function () {
MengerSponge: function() {
// This example demonstrates how symbolic perturbation correctly creates
// holes even though the subtracted objects are exactly coplanar.
@@ -285,14 +301,15 @@
function fractal(holes, hole, w, position, depth, maxDepth) {
w /= 3;
holes.push(
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
if (depth == maxDepth) return;
const offsets = [
[-w, -w], [-w, 0.0], [-w, w], [0.0, w], [w, w], [w, 0.0], [w, -w],
[0.0, -w]
];
for (let offset of offsets)
fractal(holes, hole, w, vec2add(position, offset), depth + 1, maxDepth);
fractal(
holes, hole, w, vec2add(position, offset), depth + 1, maxDepth);
}
function mengerSponge(n) {
@@ -313,13 +330,13 @@
return result;
},
StretchyBracelet: function () {
StretchyBracelet: function() {
// Recreates Stretchy Bracelet by Emmett Lalish:
// https://www.thingiverse.com/thing:13505
function base(
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
outerRadius, cut, nCut, nDivision) {
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
outerRadius, cut, nCut, nDivision) {
function rotate(v, theta) {
return [
v[0] * Math.cos(theta) - v[1] * Math.sin(theta),
@@ -336,10 +353,9 @@
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
]);
}
let decor =
extrude(circle, width, nDivision, 180).scale([1, 0.5, 1]).translate([
0, radius, 0
]);
let decor = extrude(circle, width, nDivision, 180)
.scale([1, 0.5, 1])
.translate([0, radius, 0]);
for (let i = 0; i < nDecor; i++)
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
const stretch = [];
@@ -359,8 +375,8 @@
}
function stretchyBracelet(
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
nCut = 27, nDivision = 30) {
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
nCut = 27, nDivision = 30) {
const twistRadius = Math.PI * radius / nDecor;
const decorRadius = twistRadius * 1.5;
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
@@ -369,24 +385,25 @@
const adjThickness = 0.5 * thickness * height / cut;
return difference(
base(
width, radius, decorRadius, twistRadius, nDecor,
innerRadius + thickness, outerRadius + adjThickness,
cut - adjThickness, nCut, nDivision),
base(
width, radius - thickness, decorRadius, twistRadius, nDecor,
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
nDivision));
base(
width, radius, decorRadius, twistRadius, nDecor,
innerRadius + thickness, outerRadius + adjThickness,
cut - adjThickness, nCut, nDivision),
base(
width, radius - thickness, decorRadius, twistRadius, nDecor,
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
nDivision));
}
const result = stretchyBracelet();
return result;
},
GyroidModule: function () {
GyroidModule: function() {
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
// https://www.thingiverse.com/thing:25477. This sample demonstrates the use
// of a Signed Distance Function (SDF) to create smooth, complex manifolds.
// https://www.thingiverse.com/thing:25477. This sample demonstrates the
// use of a Signed Distance Function (SDF) to create smooth, complex
// manifolds.
const size = 20;
const n = 20;
@@ -397,7 +414,7 @@
const y = p[1] - pi / 4;
const z = p[2] - pi / 4;
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
Math.cos(z) * Math.sin(x);
Math.cos(z) * Math.sin(x);
}
function gyroidOffset(level) {
@@ -412,29 +429,30 @@
function rhombicDodecahedron() {
const box = cube([1, 1, 2], true).scale(size * Math.sqrt(2));
const result =
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
return result.intersect(box.rotate([0, 0, 45]));
}
let result = rhombicDodecahedron().intersect(gyroidOffset(-0.4));
result = result.subtract(gyroidOffset(0.4));
result = result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
result =
result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
return result;
}
};
},
exports.functionBodies = new Map();
functionBodies: new Map()
};
for (const [func, code] of Object.entries(exports.functions)) {
const whole = code.toString();
const lines = whole.split('\n');
lines.splice(0, 1); // remove first line
lines.splice(-2, 2); // remove last two lines
// remove first six leading spaces
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
for (const [func, code] of Object.entries(examples.functions)) {
const whole = code.toString();
const lines = whole.split('\n');
lines.splice(0, 1); // remove first line
lines.splice(-2, 2); // remove last two lines
// remove first six leading spaces
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
const name =
const name =
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
exports.functionBodies.set(name, body);
};
})(typeof exports == 'undefined' ? this['examples'] = {} : exports);
examples.functionBodies.set(name, body);
};
+1 -2
View File
@@ -49,8 +49,7 @@
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="examples.js"></script>
<script src="editor.js"></script>
<script type="module" src="editor.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
</html>
+28 -30
View File
@@ -6,7 +6,6 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<style>
body {
margin: 0;
@@ -37,12 +36,10 @@
<body>
<p>This example demonstrates feeding Manifold's output into <a
href="https://modelviewer.dev/">&lt;model-viewer&gt;</a>
via <a href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a>.
It also shows how to pass mesh properties like UV coordinates through Manifold and
how to re-associate textures after operations.
A more basic three.js example is <a href="three.html">here</a>.
</p>
href="https://modelviewer.dev/">&lt;model-viewer&gt;</a> via <a
href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a>. It also shows how to pass mesh
properties like UV coordinates through Manifold and how to re-associate textures after operations. A more basic
three.js example is <a href="three.html">here</a>. </p>
<select>
<option value="difference" selected>Difference</option>
<option value="intersection">Intersection</option>
@@ -50,11 +47,10 @@
</select>
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
</body>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
<script src="manifold.js"></script>
<script type="module">
import { Accessor, Document, WebIO } from 'https://cdn.skypack.dev/@gltf-transform/core';
import Module from '../manifold.js';
const io = new WebIO();
const doc = new Document();
@@ -92,26 +88,24 @@
const texturesLoad = loadTextures();
const materialMap = new Map();
let wasm;
Module().then(function (tmp) {
wasm = tmp;
wasm.setup();
const wasm = await Module();
wasm.setup();
const space = cubeUV().scale(50);
const moon = sphereUV().scale(60);
materialMap.set(space.originalID(), spaceIndices);
materialMap.set(moon.originalID(), moonIndices);
const firstID = wasm.reserveIDs(2);
const space = cubeUV(firstID).scale(50);
const moon = sphereUV(firstID + 1).scale(60);
materialMap.set(firstID, spaceIndices);
materialMap.set(firstID + 1, moonIndices);
const csg = function (operation) {
push2MV(wasm[operation](space, moon));
};
const csg = function (operation) {
push2MV(wasm[operation](space, moon));
};
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
csg('difference');
});
csg('difference');
const mv = document.querySelector('model-viewer');
let objectURL = null;
@@ -120,7 +114,7 @@
// From Z-up to Y-up (glTF)
const mesh = manifold.rotate([-90, 0, 0]).getMesh();
for (const [i, id] of mesh.originalID.entries()) {
for (const [i, id] of mesh.runOriginalID.entries()) {
const indices = materialMap.get(id);
indices.setArray(mesh.triVerts.subarray(mesh.runIndex[i], mesh.runIndex[i + 1]));
}
@@ -149,7 +143,7 @@
mv.src = objectURL;
}
function cubeUV() {
function cubeUV(id) {
const numProp = 5;
const numTri = 12;
const numVert = 8;
@@ -176,7 +170,9 @@
const vertProperties = new Float32Array(numProp * numPropVert);
const mergeFromVert = new Uint32Array(numPropVert - numVert);
const mergeToVert = new Uint32Array(numPropVert - numVert);
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert });
const runOriginalID = new Uint32Array(1);
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert, runOriginalID });
runOriginalID[0] = id;
const vert2vert = [-1, -1, -1, -1, -1, -1, -1, -1];
let idx = 0;
for (let face = 0; face < 6; ++face) {
@@ -203,7 +199,7 @@
return wasm.Manifold(meshUV);
}
function sphereUV() {
function sphereUV(id) {
const n = 100;
const tol = 1 / (2 * n);
const mesh = wasm.sphere(1, n).getMesh();
@@ -216,7 +212,9 @@
const vertProperties = new Float32Array(numProp * (numVert + nMerge));
const mergeFromVert = new Uint32Array(nMerge);
const mergeToVert = new Uint32Array(nMerge);
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert });
const runOriginalID = new Uint32Array(1);
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert, runOriginalID });
runOriginalID[0] = id;
const vert2vert = new Uint32Array(numVert);
let idx = 0;
for (let i = 0; i < numVert; ++i) {
+43 -40
View File
@@ -6,7 +6,6 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<style>
body {
margin: 0;
@@ -37,8 +36,7 @@
<body>
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
involving GLTF export and &lt;model-viewer&gt; can be found <a href="model-viewer.html">here</a>.
</p>
involving GLTF export and &lt;model-viewer&gt; can be found <a href="model-viewer.html">here</a>. </p>
<select>
<option value="difference" selected>Difference</option>
<option value="intersection">Intersection</option>
@@ -46,50 +44,55 @@
</select><br />
<canvas id="output"></canvas>
</body>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.149.0/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.149.0/build/three.module.js';
import { mergeVertices } from 'https://cdn.jsdelivr.net/npm/three@0.149.0/examples/jsm/utils/BufferGeometryUtils.js';
import Module from '../manifold.js';
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
<script src="manifold.js"></script>
<script>
let wasm;
Module().then(function (tmp) {
wasm = tmp;
// we have manifold module, let's do some three.js
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
camera.position.z = 1;
const wasm = await Module();
wasm.setup();
wasm.setup();
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
flatShading: true
}));
scene.add(mesh);
// we have manifold module, let's do some three.js
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
camera.position.z = 1;
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
flatShading: true
}));
scene.add(mesh);
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
};
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
};
csg('difference');
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
const output = document.querySelector("#output");
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
const dim = output.getBoundingClientRect();
renderer.setSize(dim.width, dim.height);
renderer.setAnimationLoop(function (time) {
mesh.rotation.x = time / 2000;
mesh.rotation.y = time / 1000;
renderer.render(scene, camera);
});
csg('difference');
const output = document.querySelector("#output");
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
const dim = output.getBoundingClientRect();
renderer.setSize(dim.width, dim.height);
renderer.setAnimationLoop(function (time) {
mesh.rotation.x = time / 2000;
mesh.rotation.y = time / 1000;
renderer.render(scene, camera);
});
// functions to convert between three.js and wasm
@@ -110,7 +113,7 @@
function simplify(geometry) {
delete geometry.attributes.normal;
delete geometry.attributes.uv;
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
const simplified = mergeVertices(geometry);
simplified.computeVertexNormals();
return simplified;
}
+97 -94
View File
@@ -12,10 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
const threePath = 'https://cdn.jsdelivr.net/npm/three@0.144.0/';
importScripts(
'manifold.js', threePath + 'build/three.js',
threePath + 'examples/js/exporters/GLTFExporter.js');
import {Accessor, Document, WebIO} from 'https://cdn.skypack.dev/@gltf-transform/core';
import Module from '../manifold.js';
const wasm = await Module();
wasm.setup();
// manifold member functions that returns a new manifold
const memberFunctions = [
@@ -33,52 +35,47 @@ const utils = [
];
const exposedFunctions = constructors.concat(utils);
let wasm;
Module().then(function (tmp) {
wasm = tmp;
wasm.setup();
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
// Note that this only fixes memory leak across different runs: the memory
// will only be freed when the compilation finishes.
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
// Note that this only fixes memory leak across different runs: the memory
// will only be freed when the compilation finishes.
let manifoldRegistry = [];
for (const name of memberFunctions) {
const originalFn = wasm.Manifold.prototype[name];
wasm.Manifold.prototype['_' + name] = originalFn;
wasm.Manifold.prototype[name] = function (...args) {
const result = this['_' + name](...args);
manifoldRegistry.push(result);
return result;
};
let manifoldRegistry = [];
for (const name of memberFunctions) {
const originalFn = wasm.Manifold.prototype[name];
wasm.Manifold.prototype['_' + name] = originalFn;
wasm.Manifold.prototype[name] = function(...args) {
const result = this['_' + name](...args);
manifoldRegistry.push(result);
return result;
};
}
for (const name of constructors) {
const originalFn = wasm[name];
wasm[name] = function(...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
};
}
wasm.cleanup = function() {
for (const obj of manifoldRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
manifoldRegistry = [];
};
for (const name of constructors) {
const originalFn = wasm[name];
wasm[name] = function (...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
};
}
wasm.cleanup =
function () {
for (const obj of manifoldRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
manifoldRegistry = [];
};
postMessage(null);
});
// Setup complete
postMessage(null);
const oldLog = console.log;
console.log = function (...args) {
console.log = function(...args) {
let message = '';
for (const arg of args) {
if (typeof arg == 'object') {
@@ -87,69 +84,75 @@ console.log = function (...args) {
message += arg.toString();
}
}
postMessage({ log: message });
postMessage({log: message});
oldLog(...args);
};
onmessage =
(e) => {
const content = e.data + '\nexportGLB(result);\n';
try {
const f = new Function(...exposedFunctions, content);
f(...exposedFunctions.map(name => wasm[name]));
} catch (error) {
console.log(error.toString());
postMessage({ objectURL: null });
} finally {
wasm.cleanup();
}
};
onmessage = (e) => {
const content = e.data + '\nexportGLB(result);\n';
try {
const f = new Function('exportGLB', ...exposedFunctions, content);
f(exportGLB, ...exposedFunctions.map(name => wasm[name]));
} catch (error) {
console.log(error.toString());
postMessage({objectURL: null});
} finally {
wasm.cleanup();
}
};
// Export & Rendering
// Export
// ------------------------------------------------------------
const mesh = new THREE.Mesh(
undefined,
new THREE.MeshStandardMaterial(
{ color: 'yellow', metalness: 1, roughness: 0.2 }));
const rotation = new THREE.Matrix4();
rotation.set(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
mesh.setRotationFromMatrix(rotation); // Z-up -> Y-up
mesh.scale.setScalar(0.001); // mm -> m
const io = new WebIO();
const doc = new Document();
const exporter = new THREE.GLTFExporter();
const buffer = doc.createBuffer();
const position =
doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.VEC3);
const indices =
doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.SCALAR);
const material = doc.createMaterial()
.setBaseColorFactor([1, 1, 0, 1])
.setMetallicFactor(1)
.setRoughnessFactor(0.2);
const primitive = doc.createPrimitive()
.setMaterial(material)
.setIndices(indices)
.setAttribute('POSITION', position);
const mesh = doc.createMesh('result').addPrimitive(primitive);
const node = doc.createNode().setMesh(mesh);
doc.createScene().addChild(node);
function exportGLB(manifold) {
async function exportGLB(manifold) {
console.log(`Triangles: ${manifold.numTri().toLocaleString()}`);
const box = manifold.boundingBox();
const size = [0, 0, 0];
for (let i = 0; i < 3; i++) {
size[i] = Math.round((box.max[i] - box.min[i]) * 10) / 10;
}
console.log(`Bounding Box: X = ${size[0].toLocaleString()
} mm, Y = ${size[1].toLocaleString()
} mm, Z = ${size[2].toLocaleString()} mm`);
console.log(`Bounding Box: X = ${size[0].toLocaleString()} mm, Y = ${
size[1].toLocaleString()} mm, Z = ${size[2].toLocaleString()} mm`);
const volume = Math.round(manifold.getProperties().volume / 10);
console.log(`Genus: ${manifold.genus().toLocaleString()
}, Volume: ${(volume / 100).toLocaleString()} cm^3`);
console.log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
(volume / 100).toLocaleString()} cm^3`);
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(manifold.getMesh());
exporter.parse(
mesh,
(gltf) => {
const blob = new Blob([gltf], { type: 'application/octet-stream' });
postMessage({ objectURL: URL.createObjectURL(blob) });
},
() => {
console.log('glTF export failed!');
postMessage({ objectURL: null });
},
{ binary: true });
}
// From Z-up to Y-up (glTF)
const mesh = manifold.rotate([-90, 0, 0]).getMesh();
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertProperties, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
indices.setArray(mesh.triVerts);
const numVert = mesh.numVert;
const numProp = mesh.numProp;
const posArray = new Float32Array(3 * numVert);
for (let i = 0; i < numVert; ++i) {
posArray[3 * i] = mesh.vertProperties[numProp * i];
posArray[3 * i + 1] = mesh.vertProperties[numProp * i + 1];
posArray[3 * i + 2] = mesh.vertProperties[numProp * i + 2];
}
position.setArray(posArray);
const glb = await io.writeBinary(doc);
const blob = new Blob([glb], {type: 'application/octet-stream'});
postMessage({objectURL: URL.createObjectURL(blob)});
}
+22 -13
View File
@@ -25,7 +25,7 @@ type Vec3 = [number, number, number];
type Vec4 = [number, number, number, number];
type Matrix3x4 = [Vec3, Vec3, Vec3, Vec3];
type SimplePolygon = Vec2[];
type Polygons = SimplePolygon | SimplePolygon[];
type Polygons = SimplePolygon|SimplePolygon[];
type Box = {
min: Vec3,
max: Vec3
@@ -120,7 +120,7 @@ declare class Manifold {
*
* @param v The vector to multiply every vertex by per component.
*/
scale(v: Vec3 | number): Manifold;
scale(v: Vec3|number): Manifold;
/**
* Boolean union
@@ -230,10 +230,19 @@ declare class Manifold {
getCurvature(): Curvature;
/**
* This returns a Mesh of simple vectors of vertices and triangles suitable
* for saving or other operations outside of the context of this library.
* Returns a Mesh that is designed to easily push into a renderer, including
* all interleaved vertex properties that may have been input. It also
* includes relations to all the input meshes that form a part of this result
* and the transforms applied to each.
*
* @param normalIdx If the original Mesh inputs that formed this manifold had
* properties corresponding to normal vectors, you can specify which property
* channels these are (x, y, z), which will cause this output Mesh to
* automatically update these normals according to the applied transforms and
* front/back side. Each channel must be >= 3 and < numProp, and all original
* Meshes must use the same channels for their normals.
*/
getMesh(): Mesh;
getMesh(normalIdx?: Vec3): Mesh;
/**
* If you copy a manifold, but you want this new copy to have new properties
@@ -265,7 +274,7 @@ declare class Manifold {
* @param size The X, Y, and Z dimensions of the box.
* @param center Set to true to shift the center to the origin.
*/
declare function cube(size?: Vec3 | number, center?: boolean): Manifold;
declare function cube(size?: Vec3|number, center?: boolean): Manifold;
/**
* A convenience constructor for the common case of extruding a circle. Can also
@@ -281,8 +290,8 @@ declare function cube(size?: Vec3 | number, center?: boolean): Manifold;
* origin at the bottom.
*/
declare function cylinder(
height: number, radiusLow: number, radiusHigh?: number,
circularSegments?: number, center?: boolean): Manifold;
height: number, radiusLow: number, radiusHigh?: number,
circularSegments?: number, center?: boolean): Manifold;
/**
* Constructs a geodesic sphere of a given radius.
@@ -349,8 +358,8 @@ declare function tetrahedron(): Manifold;
* Default {1, 1}.
*/
declare function extrude(
crossSection: Polygons, height: number, nDivisions?: number,
twistDegrees?: number, scaleTop?: Vec2): Manifold;
crossSection: Polygons, height: number, nDivisions?: number,
twistDegrees?: number, scaleTop?: Vec2): Manifold;
/**
* Constructs a manifold from a set of polygons by revolving this cross-section
@@ -364,7 +373,7 @@ declare function extrude(
* calculated by the static Defaults.
*/
declare function revolve(
crossSection: Polygons, circularSegments?: number): Manifold;
crossSection: Polygons, circularSegments?: number): Manifold;
declare function union(a: Manifold, b: Manifold): Manifold;
declare function difference(a: Manifold, b: Manifold): Manifold;
@@ -401,8 +410,8 @@ declare function compose(manifolds: Manifold[]): Manifold;
* it with a negative value.
*/
declare function levelSet(
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
level?: number): Manifold;
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
level?: number): Manifold;
/**
* @name Defaults
+1
View File
@@ -12,6 +12,7 @@
],
"typings": "manifold.d.ts",
"types": "manifold.d.ts",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/elalish/manifold.git"
+106 -107
View File
@@ -12,10 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
const expect = require('chai').expect;
const wasm = require('../manifold.js');
const examples = require('../examples/examples');
const THREE = require('three');
import {expect} from 'chai';
import {examples} from '../examples/examples.js';
import Module from '../manifold.js';
const wasm = await Module();
wasm.setup();
// manifold member functions that returns a new manifold
const memberFunctions = [
@@ -33,121 +36,117 @@ const utils = [
];
const exposedFunctions = constructors.concat(utils);
wasm().then(function (Module) {
Module.setup();
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
// Note that this only fixes memory leak across different runs: the memory
// will only be freed when the compilation finishes.
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
// Note that this only fixes memory leak across different runs: the memory
// will only be freed when the compilation finishes.
let manifoldRegistry = [];
for (const name of memberFunctions) {
const originalFn = Module.Manifold.prototype[name];
Module.Manifold.prototype['_' + name] = originalFn;
Module.Manifold.prototype[name] = function (...args) {
const result = this['_' + name](...args);
manifoldRegistry.push(result);
return result;
};
let manifoldRegistry = [];
for (const name of memberFunctions) {
const originalFn = wasm.Manifold.prototype[name];
wasm.Manifold.prototype['_' + name] = originalFn;
wasm.Manifold.prototype[name] = function(...args) {
const result = this['_' + name](...args);
manifoldRegistry.push(result);
return result;
};
}
for (const name of constructors) {
const originalFn = wasm[name];
wasm[name] = function(...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
};
}
wasm.cleanup = function() {
for (const obj of manifoldRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
manifoldRegistry = [];
};
for (const name of constructors) {
const originalFn = Module[name];
Module[name] = function (...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
};
function runExample(name) {
try {
const content = examples.functionBodies.get(name) + '\nreturn result;\n';
;
const f = new Function(...exposedFunctions, content);
const manifold = f(...exposedFunctions.map(name => wasm[name]));
const prop = manifold.getProperties();
const genus = manifold.genus();
return {...prop, genus};
} finally {
wasm.cleanup();
}
}
Module.cleanup =
function () {
for (const obj of manifoldRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
manifoldRegistry = [];
};
suite('Examples', () => {
test('Intro', () => {
const result = runExample('Intro');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
});
function runExample(name) {
try {
const content = examples.functionBodies.get(name) + '\nreturn result;\n';
;
const f = new Function(...exposedFunctions, 'THREE', content);
const manifold = f(...exposedFunctions.map(name => Module[name]), THREE);
const prop = manifold.getProperties();
const genus = manifold.genus();
return { ...prop, genus };
} finally {
Module.cleanup();
}
}
test('Tetrahedron Puzzle', () => {
const result = runExample('Tetrahedron Puzzle');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(7297, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(3303, 1, 'Surface Area');
});
suite('Examples', () => {
test('Intro', () => {
const result = runExample('Intro');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
});
test('Rounded Frame', () => {
const result = runExample('Rounded Frame');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(353706, 10, 'Volume');
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
});
test('Tetrahedron Puzzle', () => {
const result = runExample('Tetrahedron Puzzle');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(7297, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(3303, 1, 'Surface Area');
});
test('Heart', () => {
const result = runExample('Heart');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(282743, 10, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
});
test('Rounded Frame', () => {
const result = runExample('Rounded Frame');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(353706, 10, 'Volume');
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
});
test('Scallop', () => {
const result = runExample('Scallop');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
});
test('Heart', () => {
const result = runExample('Heart');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(282743, 10, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
});
test('Torus Knot', () => {
const result = runExample('Torus Knot');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(20960, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(11202, 1, 'Surface Area');
});
test('Scallop', () => {
const result = runExample('Scallop');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
});
test('Menger Sponge', () => {
const result = runExample('Menger Sponge');
expect(result.genus).to.equal(1409, 'Genus');
expect(result.volume).to.be.closeTo(406457, 10, 'Volume');
expect(result.surfaceArea).to.be.closeTo(247590, 10, 'Surface Area');
});
test('Torus Knot', () => {
const result = runExample('Torus Knot');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(20960, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(11202, 1, 'Surface Area');
});
test('Stretchy Bracelet', () => {
const result = runExample('Stretchy Bracelet');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
});
test('Menger Sponge', () => {
const result = runExample('Menger Sponge');
expect(result.genus).to.equal(1409, 'Genus');
expect(result.volume).to.be.closeTo(406457, 10, 'Volume');
expect(result.surfaceArea).to.be.closeTo(247590, 10, 'Surface Area');
});
test('Stretchy Bracelet', () => {
const result = runExample('Stretchy Bracelet');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
});
test('Gyroid Module', () => {
const result = runExample('Gyroid Module');
expect(result.genus).to.equal(15, 'Genus');
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
});
test('Gyroid Module', () => {
const result = runExample('Gyroid Module');
expect(result.genus).to.equal(15, 'Genus');
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
});
});