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:
Vendored
+2
-2
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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/"><model-viewer></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/"><model-viewer></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) {
|
||||
|
||||
@@ -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 <model-viewer> can be found <a href="model-viewer.html">here</a>.
|
||||
</p>
|
||||
involving GLTF export and <model-viewer> 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;
|
||||
}
|
||||
|
||||
@@ -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)});
|
||||
}
|
||||
Vendored
+22
-13
@@ -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
|
||||
|
||||
@@ -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
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user