Files
manifold/bindings/wasm/bindings.js
T
Emmett Lalish 7b33277245 Remove duplicate Smoothing APIs (#1814)
* almost updated scallop

* scallop working

* fixed scallop

* sharpened scallop

* removed some smoothing APIs

* fixed some tests

* tests passing

* updated bindings examples

* fix formatting

* fix more formatting
2026-09-07 12:44:38 +12:00

799 lines
23 KiB
JavaScript

// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
var _ManifoldInitialized = false;
Module.setup = function() {
if (_ManifoldInitialized) return;
_ManifoldInitialized = true;
// warmup tbb for emscripten, according to
// https://github.com/oneapi-src/oneTBB/blob/master/WASM_Support.md#limitations
Module.initTBB();
// conversion utilities
function toVec(vec, list, f = x => x) {
if (list) {
for (let x of list) {
vec.push_back(f(x));
}
}
return vec;
}
function fromVec(vec, f = x => x) {
const result = [];
const size = vec.size();
for (let i = 0; i < size; i++) result.push(f(vec.get(i)));
return result;
}
function vec2polygons(vec, f = x => x) {
const result = [];
const nPoly = vec.size();
for (let i = 0; i < nPoly; i++) {
const v = vec.get(i);
const nPts = v.size();
const poly = [];
for (let j = 0; j < nPts; j++) {
poly.push(f(v.get(j)));
}
result.push(poly);
}
return result;
}
function polygons2vec(polygons) {
if (polygons[0].length < 3) {
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;
}));
}
function disposePolygons(polygonsVec) {
for (let i = 0; i < polygonsVec.size(); i++) polygonsVec.get(i).delete();
polygonsVec.delete();
}
function vararg2vec2(vec) {
if (vec[0] instanceof Array) return {x: vec[0][0], y: vec[0][1]};
if (typeof (vec[0]) == 'number')
// default to 0
return {x: vec[0] || 0, y: vec[1] || 0};
return vec[0];
}
function vararg2vec3(vec) {
if (vec[0] instanceof Array)
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 vec[0];
}
function joinTypeToInt(joinType) {
return joinType == 'Round' ? 1 :
joinType == 'Miter' ? 2 :
joinType == 'Bevel' ? 3 :
/* Square */ 0;
}
// CrossSection methods
const CrossSectionCtor = Module.CrossSection;
function cross(polygons) {
if (polygons instanceof CrossSectionCtor) {
return polygons;
} else {
const polygonsVec = polygons2vec(polygons);
const cs = new CrossSectionCtor(polygonsVec);
disposePolygons(polygonsVec);
return cs;
}
};
Module.CrossSection.prototype.translate = function(...vec) {
return this._Translate(vararg2vec2(vec));
};
Module.CrossSection.prototype.scale = function(vec) {
// if only one factor provided, scale both x and y with it
if (typeof vec == 'number') {
return this._Scale({x: vec, y: vec});
}
return this._Scale(vararg2vec2([vec]));
};
Module.CrossSection.prototype.mirror = function(vec) {
return this._Mirror(vararg2vec2([vec]));
};
Module.CrossSection.prototype.warp = function(func) {
const wasmFuncPtr = addFunction(function(vec2Ptr) {
const x = getValue(vec2Ptr, 'double');
const y = getValue(vec2Ptr + 8, 'double');
const vert = [x, y];
func(vert);
setValue(vec2Ptr, vert[0], 'double');
setValue(vec2Ptr + 8, vert[1], 'double');
}, 'vi');
const out = this._Warp(wasmFuncPtr);
removeFunction(wasmFuncPtr);
return out;
};
Module.CrossSection.prototype.decompose = function() {
const vec = this._Decompose();
const result = fromVec(vec);
vec.delete();
return result;
};
Module.CrossSection.prototype.bounds = function() {
const result = this._Bounds();
return {
min: ['x', 'y'].map(f => result.min[f]),
max: ['x', 'y'].map(f => result.max[f]),
};
};
Module.CrossSection.prototype.offset = function(
delta, joinType = 'Round', miterLimit = 2.0, circularSegments = 0) {
return this._Offset(
delta, joinTypeToInt(joinType), miterLimit, circularSegments);
};
Module.CrossSection.prototype.simplify = function(tolerance = 0) {
return this._Simplify(tolerance);
};
Module.CrossSection.prototype.extrude = function(
height, nDivisions = 0, twistDegrees = 0.0, scaleTop = [1.0, 1.0],
center = false) {
scaleTop = vararg2vec2([scaleTop]);
const man = Module._Extrude(
this._ToPolygons(), height, nDivisions, twistDegrees, scaleTop);
return (center ? man.translate([0., 0., -height / 2.]) : man);
};
Module.CrossSection.prototype.revolve = function(
circularSegments = 0, revolveDegrees = 360.0) {
return Module._Revolve(
this._ToPolygons(), circularSegments, revolveDegrees);
};
Module.CrossSection.prototype.add = function(other) {
return this._add(cross(other));
};
Module.CrossSection.prototype.subtract = function(other) {
return this._subtract(cross(other));
};
Module.CrossSection.prototype.intersect = function(other) {
return this._intersect(cross(other));
};
Module.CrossSection.prototype.toPolygons = function() {
const vec = this._ToPolygons();
const result = vec2polygons(vec, v => [v.x, v.y]);
vec.delete();
return result;
};
// Manifold methods
Module.Manifold.prototype.warp = function(func) {
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'double');
const y = getValue(vec3Ptr + 8, 'double');
const z = getValue(vec3Ptr + 16, 'double');
const vert = [x, y, z];
func(vert);
setValue(vec3Ptr, vert[0], 'double');
setValue(vec3Ptr + 8, vert[1], 'double');
setValue(vec3Ptr + 16, vert[2], 'double');
}, 'vi');
const out = this._Warp(wasmFuncPtr);
removeFunction(wasmFuncPtr);
const status = out.status();
if (status !== 'NoError') {
throw new Module.ManifoldError(status);
}
return out;
};
Module.Manifold.prototype.warpBatch = function(func) {
const wasmFuncPtr = addFunction(function(ptr, count) {
const heapF64 = Module.HEAPF64 ?? HEAPF64;
if (!heapF64) {
throw new Error('WASM heap is not initialized (HEAPF64 unavailable)');
}
const verts = new Float64Array(heapF64.buffer, ptr, count * 3);
func(verts, count);
}, 'vii');
const out = this._WarpBatch(wasmFuncPtr);
removeFunction(wasmFuncPtr);
const status = out.status();
if (status !== 'NoError') {
throw new Module.ManifoldError(status);
}
return out;
};
Module.Manifold.prototype.calculateNormals = function(
normalIdx = 0, minSharpAngle = 52.5) {
return this._CalculateNormals(normalIdx, minSharpAngle);
};
Module.Manifold.prototype.smoothByNormals = function(normalIdx = 0) {
return this._SmoothByNormals(normalIdx);
};
Module.Manifold.prototype.setProperties = function(numProp, func) {
const oldNumProp = this.numProp();
const wasmFuncPtr = addFunction(function(newPtr, vec3Ptr, oldPtr) {
const newProp = [];
for (let i = 0; i < numProp; ++i) {
newProp[i] = getValue(newPtr + 8 * i, 'double');
}
const pos = [];
for (let i = 0; i < 3; ++i) {
pos[i] = getValue(vec3Ptr + 8 * i, 'double');
}
const oldProp = [];
for (let i = 0; i < oldNumProp; ++i) {
oldProp[i] = getValue(oldPtr + 8 * i, 'double');
}
func(newProp, pos, oldProp);
for (let i = 0; i < numProp; ++i) {
setValue(newPtr + 8 * i, newProp[i], 'double');
}
}, 'viii');
const out = this._SetProperties(numProp, wasmFuncPtr);
removeFunction(wasmFuncPtr);
return out;
};
Module.Manifold.prototype.translate = function(...vec) {
return this._Translate(vararg2vec3(vec));
};
Module.Manifold.prototype.rotate = function(xOrVec, y, z) {
if (Array.isArray(xOrVec)) {
return this._Rotate(...xOrVec);
} else {
return this._Rotate(xOrVec, y || 0, z || 0);
}
};
Module.Manifold.prototype.scale = function(vec) {
// if only one factor provided, scale all three dimensions (xyz) with it
if (typeof vec == 'number') {
return this._Scale({x: vec, y: vec, z: vec});
}
return this._Scale(vararg2vec3([vec]));
};
Module.Manifold.prototype.mirror = function(vec) {
return this._Mirror(vararg2vec3([vec]));
};
Module.Manifold.prototype.trimByPlane = function(normal, offset = 0.) {
return this._TrimByPlane(vararg2vec3([normal]), offset);
};
Module.Manifold.prototype.slice = function(height = 0.) {
const polygonsVec = this._Slice(height);
const result = new CrossSectionCtor(polygonsVec);
disposePolygons(polygonsVec);
return result;
};
Module.Manifold.prototype.project = function() {
const polygonsVec = this._Project();
const result = new CrossSectionCtor(polygonsVec);
disposePolygons(polygonsVec);
return result;
};
Module.Manifold.prototype.windingNumber = function(points) {
const inVec = new Module.Vector_vec3();
for (const p of points) inVec.push_back(vararg2vec3([p]));
const outVec = this._WindingNumber(inVec);
inVec.delete();
const result = fromVec(outVec);
outVec.delete();
return result;
};
Module.Manifold.prototype.rayCast = function(origin, endpoint) {
const vec = this._RayCast(vararg2vec3([origin]), vararg2vec3([endpoint]));
const result =
fromVec(vec, hit => ({
faceID: hit.faceID,
distance: hit.distance,
position: ['x', 'y', 'z'].map(f => hit.position[f]),
normal: ['x', 'y', 'z'].map(f => hit.normal[f]),
}));
vec.delete();
return result;
};
Module.Manifold.prototype.split = function(manifold) {
const vec = this._Split(manifold);
const result = fromVec(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.splitByPlane = function(normal, offset = 0.) {
const vec = this._SplitByPlane(vararg2vec3([normal]), offset);
const result = fromVec(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.decompose = function() {
const vec = this._Decompose();
const result = fromVec(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.boundingBox = function() {
const result = this._boundingBox();
return {
min: ['x', 'y', 'z'].map(f => result.min[f]),
max: ['x', 'y', 'z'].map(f => result.max[f]),
};
};
Module.Manifold.prototype.simplify = function(tolerance = 0) {
return this._Simplify(tolerance);
};
class Mesh {
constructor({
numProp = 3,
triVerts = new Uint32Array(),
vertProperties = new Float32Array(),
mergeFromVert,
mergeToVert,
runIndex,
runOriginalID,
faceID,
halfedgeTangent,
runTransform,
runFlags,
tolerance = 0
} = {}) {
this.numProp = numProp;
this.triVerts = triVerts;
this.vertProperties = vertProperties;
this.mergeFromVert = mergeFromVert;
this.mergeToVert = mergeToVert;
this.runIndex = runIndex;
this.runOriginalID = runOriginalID;
this.faceID = faceID;
this.halfedgeTangent = halfedgeTangent;
this.runTransform = runTransform;
this.runFlags = runFlags;
this.tolerance = tolerance;
}
get numTri() {
return this.triVerts.length / 3;
}
get numVert() {
return this.vertProperties.length / this.numProp;
}
get numRun() {
return this.runOriginalID.length;
}
merge() {
const {changed, mesh} = Module._Merge(this);
Object.assign(this, {...mesh});
return changed;
}
verts(tri) {
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
}
position(vert) {
return this.vertProperties.subarray(
this.numProp * vert, this.numProp * vert + 3);
}
extras(vert) {
return this.vertProperties.subarray(
this.numProp * vert + 3, this.numProp * (vert + 1));
}
tangent(halfedge) {
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
}
transform(run) {
const mat4 = new Array(16);
for (const col of [0, 1, 2, 3]) {
for (const row of [0, 1, 2]) {
mat4[4 * col + row] = this.runTransform[12 * run + 3 * col + row];
}
}
mat4[15] = 1;
return mat4;
}
backside(run) {
return this.runFlags != null && run < this.runFlags.length &&
(this.runFlags[run] & 1) !== 0;
}
hasNormals(run) {
return this.runFlags != null && run < this.runFlags.length &&
(this.runFlags[run] & 2) !== 0;
}
}
Module.Mesh = Mesh;
Module.Manifold.prototype.getMesh = function(normalIdx = -1) {
return new Mesh(this._GetMeshJS(normalIdx));
};
Module.ManifoldError = function ManifoldError(code, ...args) {
let message = 'Unknown error';
switch (code) {
case 'NonFiniteVertex':
message = 'Non-finite vertex';
break;
case 'NotManifold':
message = 'Not manifold';
break;
case 'VertexOutOfBounds':
message = 'Vertex index out of bounds';
break;
case 'PropertiesWrongLength':
message = 'Properties have wrong length';
break;
case 'MissingPositionProperties':
message = 'Less than three properties';
break;
case 'MergeVectorsDifferentLengths':
message = 'Merge vectors have different lengths';
break;
case 'MergeIndexOutOfBounds':
message = 'Merge index out of bounds';
break;
case 'TransformWrongLength':
message = 'Transform vector has wrong length';
break;
case 'RunIndexWrongLength':
message = 'Run index vector has wrong length';
break;
case 'FaceIDWrongLength':
message = 'Face ID vector has wrong length';
break;
case 'InvalidConstruction':
message = 'Manifold constructed with invalid parameters';
break;
case 'ResultTooLarge':
message = 'Result exceeds maximum size';
break;
case 'InvalidTangents':
message = 'Invalid halfedge tangents';
break;
}
const base = Error.apply(this, [message, ...args]);
base.name = this.name = 'ManifoldError';
this.message = base.message;
this.stack = base.stack;
this.code = code;
};
Module.ManifoldError.prototype = Object.create(Error.prototype, {
constructor:
{value: Module.ManifoldError, writable: true, configurable: true}
});
// CrossSection Constructors
Module.CrossSection = function(polygons) {
const polygonsVec = polygons2vec(polygons);
const cs = new CrossSectionCtor(polygonsVec);
disposePolygons(polygonsVec);
return cs;
};
Module.CrossSection.ofPolygons = function(polygons) {
return new Module.CrossSection(polygons);
};
Module.CrossSection.evenOdd = function(polygons) {
const polygonsVec = polygons2vec(polygons);
const cs = Module._EvenOdd(polygonsVec);
disposePolygons(polygonsVec);
return cs;
};
Module.CrossSection.square = function(...args) {
let size = undefined;
if (args.length == 0)
size = {x: 1, y: 1};
else if (typeof args[0] == 'number')
size = {x: args[0], y: args[0]};
else
size = vararg2vec2(args);
const center = args[1] || false;
return Module._Square(size, center);
};
Module.CrossSection.circle = function(radius, circularSegments = 0) {
return Module._Circle(radius, circularSegments);
};
// allows args to be either CrossSection or polygons (constructed with
// Positive fill)
function crossSectionBatchbool(name) {
return function(...args) {
if (args.length == 1) args = args[0];
const v = new Module.Vector_crossSection();
for (const cs of args) v.push_back(cross(cs));
const result = Module['_crossSection' + name](v);
v.delete();
return result;
};
}
Module.CrossSection.union = crossSectionBatchbool('UnionN');
Module.CrossSection.difference = crossSectionBatchbool('DifferenceN');
Module.CrossSection.intersection = crossSectionBatchbool('IntersectionN');
function pushVec2(vec, ps) {
toVec(vec, ps, p => {
if (p instanceof Array) return {x: p[0], y: p[1]};
return p;
})
}
Module.CrossSection.hull = function(...args) {
if (args.length == 1) args = args[0];
let pts = new Module.Vector_vec2();
for (const cs of args) {
if (cs instanceof CrossSectionCtor) {
Module._crossSectionCollectVertices(pts, cs);
} else if (
cs instanceof Array && cs.length == 2 && typeof cs[0] == 'number') {
pts.push_back({x: cs[0], y: cs[1]});
} else if (cs.x) {
pts.push_back(cs);
} else {
const wrap =
((cs[0].length == 2 && typeof cs[0][0] == 'number') || cs[0].x);
const polys = wrap ? [cs] : cs;
for (const poly of polys) pushVec2(pts, poly);
}
}
const result = Module._crossSectionHullPoints(pts);
pts.delete();
return result;
};
Module.CrossSection.prototype = Object.create(CrossSectionCtor.prototype);
// Because the constructor and prototype are being replaced, instanceof will
// not work as desired unless we refer back to the original like this
Object.defineProperty(Module.CrossSection, Symbol.hasInstance, {
get: () => (t) => {
return (t instanceof CrossSectionCtor);
}
});
// Manifold Constructors
const ManifoldCtor = Module.Manifold;
Module.Manifold = function(mesh) {
const manifold = new ManifoldCtor(mesh);
const status = manifold.status();
if (status !== 'NoError') {
throw new Module.ManifoldError(status);
}
return manifold;
};
Module.Manifold.ofMesh = function(mesh) {
return new Module.Manifold(mesh);
};
Module.Manifold.tetrahedron = function() {
return Module._Tetrahedron();
};
Module.Manifold.cube = function(...args) {
let size = undefined;
if (args.length == 0)
size = {x: 1, y: 1, z: 1};
else if (typeof args[0] == 'number')
size = {x: args[0], y: args[0], z: args[0]};
else
size = vararg2vec3(args);
const center = args[1] || false;
return Module._Cube(size, center);
};
Module.Manifold.cylinder = function(
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
center = false) {
return Module._Cylinder(
height, radiusLow, radiusHigh, circularSegments, center);
};
Module.Manifold.sphere = function(radius, circularSegments = 0) {
return Module._Sphere(radius, circularSegments);
};
Module.Manifold.extrude = function(
polygons, height, nDivisions = 0, twistDegrees = 0.0,
scaleTop = [1.0, 1.0], center = false) {
const cs = (polygons instanceof CrossSectionCtor) ?
polygons :
Module.CrossSection(polygons);
return cs.extrude(height, nDivisions, twistDegrees, scaleTop, center);
};
Module.Manifold.revolve = function(
polygons, circularSegments = 0, revolveDegrees = 360.0) {
const cs = (polygons instanceof CrossSectionCtor) ?
polygons :
Module.CrossSection(polygons);
return cs.revolve(circularSegments, revolveDegrees);
};
Module.Manifold.reserveIDs = function(n) {
return Module._ReserveIDs(n);
};
function manifoldBatchbool(name) {
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);
const result = Module['_manifold' + name + 'N'](v);
v.delete();
return result;
};
}
Module.Manifold.union = manifoldBatchbool('Union');
// Aliasing compose to union.
// Native compose has some issues, and is deprecated.
Module.Manifold.compose = Module.Manifold.union;
Module.Manifold.difference = manifoldBatchbool('Difference');
Module.Manifold.intersection = manifoldBatchbool('Intersection');
Module.Manifold.levelSet = function(
sdf, bounds, edgeLength, level = 0, tolerance = -1) {
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]},
};
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'double');
const y = getValue(vec3Ptr + 8, 'double');
const z = getValue(vec3Ptr + 16, 'double');
const vert = [x, y, z];
return sdf(vert);
}, 'di');
const out =
Module._LevelSet(wasmFuncPtr, bounds2, edgeLength, level, tolerance);
removeFunction(wasmFuncPtr);
return out;
};
// ctx-aware static factories: mirror Manifold.ofMesh / levelSet but
// run under this ExecutionContext so progress/cancellation are observed.
Module.ExecutionContext.prototype.fromMesh = function(mesh) {
return this._FromMesh(mesh);
};
Module.ExecutionContext.prototype.levelSet = function(
sdf, bounds, edgeLength, level = 0, tolerance = -1) {
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]},
};
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'double');
const y = getValue(vec3Ptr + 8, 'double');
const z = getValue(vec3Ptr + 16, 'double');
const vert = [x, y, z];
return sdf(vert);
}, 'di');
const out =
this._LevelSet(wasmFuncPtr, bounds2, edgeLength, level, tolerance);
removeFunction(wasmFuncPtr);
return out;
};
function pushVec3(vec, ps) {
toVec(vec, ps, p => {
if (p instanceof Array) return {x: p[0], y: p[1], z: p[2]};
return p;
})
}
Module.Manifold.hull = function(...args) {
if (args.length == 1) args = args[0];
let pts = new Module.Vector_vec3();
for (const m of args) {
if (m instanceof ManifoldCtor) {
Module._manifoldCollectVertices(pts, m);
} else if (
m instanceof Array && m.length == 3 && typeof m[0] == 'number') {
pts.push_back({x: m[0], y: m[1], z: m[2]});
} else if (m.x) {
pts.push_back(m);
} else {
pushVec3(pts, m);
}
}
const result = Module._manifoldHullPoints(pts);
pts.delete();
return result;
};
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
// Because the constructor and prototype are being replaced, instanceof will
// not work as desired unless we refer back to the original like this
Object.defineProperty(Module.Manifold, Symbol.hasInstance, {
get: () => (t) => {
return (t instanceof ManifoldCtor);
}
});
// Top-level functions
Module.triangulate = function(polygons, epsilon = -1, allowConvex = true) {
const polygonsVec = polygons2vec(polygons);
const result = fromVec(
Module._Triangulate(polygonsVec, epsilon, allowConvex),
(x) => [x[0], x[1], x[2]]);
disposePolygons(polygonsVec);
return result;
};
};