Compare commits

...
8 Commits
Author SHA1 Message Date
Emmett Lalish 67a5b7140d basic example working 2022-11-16 00:01:08 +01:00
Emmett Lalish 98a61df840 Fix editor first run (#281)
* fixed first run bug, plus minor cleanup

* added examples pointer
2022-11-14 00:02:43 +01:00
Emmett Lalish a9dc1550ba new JS Mesh class (#272)
* new JS Mesh class

* fix Mesh

* switch MeshGL to vectors

* new Mesh constructor

* export Mesh and updated types

* got scallop working again

* optional normals

* fixed Mesh constructor

* added halfedgeTangent

* cleanup
2022-11-10 23:24:46 +01:00
Emmett Lalish fd8c381179 Fix spelling (#270)
* fixed spelling

* forgot one

* JS code formatting

* updated CMakeLists for easier testing

* revert small change
2022-11-08 10:40:15 +01:00
Emmett LalishandStewart Allen af9377d6e7 Add GetMeshGL (#269)
* figure out wasm shared memory access, manifold data structure storage

* checkpoint multi-mode mesh direct access

* clean this up for a PR

* use size_t instead of int. fix pointer math for free() call. tab indent 2 spaces in js to match other code

* apply clang formatting. default to mode 1 (faster)

* eliminate new getMeshDirect() api call and fold it into getMesh() as an optional parameter object
eliminate _FreeMeshDirect and use Module._free() instead

* added optional ability to get normals array
restore access to mode 0 largely for testing

* remove mode options, simplify code

* this is quite a bit neater. fixes shorter header length

* attempt to do this the right way

* meshGL working

* revert JS API update

* added docs

Co-authored-by: Stewart Allen <stewart.allen@gmail.com>
2022-11-05 17:04:38 +01:00
Rafael 50fc5d87de Updated JS bindings (#268)
* Updated JS binding type definitions

* Ignore sublime text projects

* Ignore clangd cache

* Expose isManifold method

* Added wrapper for Manifold constructor, updated Mesh interface (...)
- Optional Mesh fields no longer error out with a 'missing property' error when they are missing
- Manifold constructor now uses the JS style Mesh instead of the serialized Mesh interface (MeshVec)
  - The old constructor was kept as a new static function (ManifoldFromMeshVec)
  - Error checking added to the constructor. If the status is not 0, an error with the correct message and code is thrown
- Updated type definitions

* Fix typo
whoops...

* Fix broken MeshRelation deserialization

* Revert "Expose isManifold method"

This reverts commit cedde70e1a28d721f58d886cea3ebd02fe0565af.

* Dummy commit to trigger CI
2022-11-05 00:23:54 +01:00
Jiří Hon ca81951355 WASM bindings: add missing smooth function to the ManifoldStatic interface (#267)
Update manifold.d.ts

Add missing smooth function to the ManifoldStatic interface.
2022-11-03 22:27:43 +01:00
Emmett Lalish c59130c754 Adding NodeJS tests for WASM module (#262)
* modularize wasm and add mocha tests

* add npm tests to CI

* fix mv command

* revert to build directory

* added rest of example tests

* fixed three.js test

* update web examples for module
2022-10-27 10:01:08 -07:00
41 changed files with 2766 additions and 615 deletions
+3
View File
@@ -97,6 +97,9 @@ jobs:
run: |
cd build/test
node ./manifold_test.js
cd ../bindings/wasm
npm install
npm test
- name: Upload WASM files
uses: actions/upload-artifact@v3
with:
+5 -1
View File
@@ -108,5 +108,9 @@ Temporary Items
build
buildWASM
docs/html
node_modules/
__pycache__
.vscode/c_cpp_properties.json
.vscode/c_cpp_properties.json
*.sublime-project
*.sublime-workspace
.cache
+9 -3
View File
@@ -92,15 +92,21 @@
"codecvt": "cpp",
"regex": "cpp",
"future": "cpp",
"shared_mutex": "cpp"
"shared_mutex": "cpp",
"__bits": "cpp",
"compare": "cpp",
"concepts": "cpp",
"queue": "cpp",
"stack": "cpp",
"__hash_table": "cpp"
},
"C_Cpp.clang_format_fallbackStyle": "google",
"editor.formatOnSave": true,
"cmake.configureArgs": [
"-DBUILD_SHARED_LIBS=ON",
"-DMANIFOLD_EXPORT=ON",
"-DMANIFOLD_EXPORT=OFF",
"-DMANIFOLD_DEBUG=ON",
"-DMANIFOLD_USE_CUDA=ON",
"-DMANIFOLD_USE_CUDA=OFF",
"-DMANIFOLD_PAR=NONE",
"-DCODE_COVERAGE=OFF",
"-DCMAKE_CXX_FLAGS=''" //'-fsanitize=address,undefined'"
+8 -8
View File
@@ -132,12 +132,12 @@ PYBIND11_MODULE(pymanifold, m) {
return self.Rotate(v_view(0), v_view(1), v_view(2));
},
py::arg("v"),
"Applys an Euler angle rotation to the manifold, first about the X "
"Applies an Euler angle rotation to the manifold, first about the X "
"axis, then\n"
"Y, then Z, in degrees. We use degrees so that we can minimize "
"rounding error,\n"
"and elimiate it completely for any multiples of 90 degrees. "
"Addtionally, more\n"
"and eliminate it completely for any multiples of 90 degrees. "
"Additionally, more\n"
"efficient code paths are used to update the manifold when the "
"transforms only\n"
"rotate by multiples of 90 degrees. This operation can be chained. "
@@ -153,12 +153,12 @@ PYBIND11_MODULE(pymanifold, m) {
},
py::arg("x_degrees") = 0.0f, py::arg("y_degrees") = 0.0f,
py::arg("z_degrees") = 0.0f,
"Applys an Euler angle rotation to the manifold, first about the X "
"Applies an Euler angle rotation to the manifold, first about the X "
"axis, then\n"
"Y, then Z, in degrees. We use degrees so that we can minimize "
"rounding error,\n"
"and elimiate it completely for any multiples of 90 degrees. "
"Addtionally, more\n"
"and eliminate it completely for any multiples of 90 degrees. "
"Additionally, more\n"
"efficient code paths are used to update the manifold when the "
"transforms only\n"
"rotate by multiples of 90 degrees. This operation can be chained. "
@@ -200,7 +200,7 @@ PYBIND11_MODULE(pymanifold, m) {
"Constructs a smooth version of the input mesh by creating "
"tangents; this\n"
"method will throw if you have supplied tangnets with your "
"method will throw if you have supplied tangents with your "
"mesh already. The\n"
"actual triangle resolution is unchanged; use the Refine() "
"method to\n"
@@ -375,7 +375,7 @@ PYBIND11_MODULE(pymanifold, m) {
":param height: Z-extent of extrusion.\n"
":param nDivisions: Number of extra copies of the crossSection to "
"insert into\n"
"the shape vertically; especially useful in combnation with "
"the shape vertically; especially useful in combination with "
"twistDegrees to\n"
"avoid interpolation artifacts. Default is none.\n"
":param twistDegrees: Amount to twist the top crossSection relative "
+11 -5
View File
@@ -21,12 +21,18 @@ set_source_files_properties(bindings.cpp OBJECT_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
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 -s ALLOW_TABLE_GROWTH=1
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction)
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)
target_compile_features(manifoldjs PUBLIC cxx_std_14)
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
file(GLOB_RECURSE WEB_FILES CONFIGURE_DEPENDS *.html *.ts)
file(COPY ${WEB_FILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${WEB_FILES})
file(COPY examples;test;. DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS examples;test)
add_custom_command(
TARGET manifoldjs POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
${CMAKE_CURRENT_BINARY_DIR}/examples/)
+46 -9
View File
@@ -13,6 +13,7 @@
// limitations under the License.
#include <emscripten/bind.h>
#include <emscripten/val.h>
using namespace emscripten;
@@ -55,6 +56,48 @@ std::vector<SimplePolygon> ToPolygon(
return simplePolygons;
}
val GetMeshJS(const Manifold& manifold) {
MeshGL mesh = manifold.GetMeshGL();
val meshJS = val::object();
meshJS.set("triVerts",
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
.call<val>("slice"));
meshJS.set("vertPos",
val(typed_memory_view(mesh.vertPos.size(), mesh.vertPos.data()))
.call<val>("slice"));
meshJS.set("vertNormal", val(typed_memory_view(mesh.vertNormal.size(),
mesh.vertNormal.data()))
.call<val>("slice"));
meshJS.set("halfedgeTangent",
val(typed_memory_view(mesh.halfedgeTangent.size(),
mesh.halfedgeTangent.data()))
.call<val>("slice"));
return meshJS;
}
MeshGL MeshJS2GL(const val& mesh) {
MeshGL out;
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
out.vertPos = convertJSArrayToNumberVector<float>(mesh["vertPos"]);
if (mesh["vertNormal"] != val::undefined()) {
out.vertNormal = convertJSArrayToNumberVector<float>(mesh["vertNormal"]);
}
if (mesh["halfedgeTangent"] != val::undefined()) {
out.halfedgeTangent =
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
}
return out;
}
Manifold FromMeshJS(const val& mesh) { return Manifold(MeshJS2GL(mesh)); }
Manifold Smooth(const val& mesh,
const std::vector<Smoothness>& sharpenedEdges = {}) {
return Manifold::Smooth(MeshJS2GL(mesh), sharpenedEdges);
}
Manifold Extrude(std::vector<std::vector<glm::vec2>>& polygons, float height,
int nDivisions, float twistDegrees, glm::vec2 scaleTop) {
return Manifold::Extrude(ToPolygon(polygons), height, nDivisions,
@@ -158,18 +201,12 @@ EMSCRIPTEN_BINDINGS(whatever) {
register_vector<BaryRef>("Vector_baryRef");
register_vector<glm::vec4>("Vector_vec4");
value_object<Mesh>("Mesh")
.field("vertPos", &Mesh::vertPos)
.field("triVerts", &Mesh::triVerts)
.field("vertNormal", &Mesh::vertNormal)
.field("halfedgeTangent", &Mesh::halfedgeTangent);
class_<Manifold>("Manifold")
.constructor<Mesh>()
.constructor(&FromMeshJS)
.function("add", &Union)
.function("subtract", &Difference)
.function("intersect", &Intersection)
.function("_GetMesh", &Manifold::GetMesh)
.function("_GetMeshJS", &GetMeshJS)
.function("refine", &Manifold::Refine)
.function("_Warp", &Warp)
.function("_Transform", &Transform)
@@ -195,7 +232,7 @@ EMSCRIPTEN_BINDINGS(whatever) {
function("_Cylinder", &Manifold::Cylinder);
function("_Sphere", &Manifold::Sphere);
function("tetrahedron", &Manifold::Tetrahedron);
function("_Smooth", &Manifold::Smooth);
function("_Smooth", &Smooth);
function("_Extrude", &Extrude);
function("_Revolve", &Revolve);
function("_LevelSet", &LevelSetJs);
+141 -93
View File
@@ -13,12 +13,12 @@
// limitations under the License.
var _ManifoldInitialized = false;
Module.setup = function () {
Module.setup = function() {
if (_ManifoldInitialized) return;
_ManifoldInitialized = true;
function toVec(vec, list, f = x => x) {
if (list != null) {
if (list) {
for (let x of list) {
vec.push_back(f(x));
}
@@ -29,8 +29,7 @@ Module.setup = function () {
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)));
for (let i = 0; i < size; i++) result.push(f(vec.get(i)));
return result;
}
@@ -39,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) {
@@ -51,38 +50,17 @@ Module.setup = function () {
polygonsVec.delete();
}
function mesh2vec(mesh) {
const vertPos = toVec(new Module.Vector_vec3, mesh.vertPos, p => {
return { x: p[0], y: p[1], z: p[2] }
});
const triVerts = toVec(new Module.Vector_ivec3, mesh.triVerts);
const vertNormal = toVec(new Module.Vector_vec3, mesh.vertNormal, p => {
return { x: p[0], y: p[1], z: p[2] }
});
const halfedgeTangent = toVec(new Module.Vector_vec4, mesh.halfedgeTangent, p => {
return { x: p[0], y: p[1], z: p[2], w: p[3] }
});
return { vertPos, triVerts, vertNormal, halfedgeTangent };
}
function disposeMesh(meshVec) {
meshVec.vertPos.delete();
meshVec.triVerts.delete();
meshVec.vertNormal.delete();
meshVec.halfedgeTangent.delete();
}
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');
@@ -98,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) {
@@ -110,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;
@@ -143,46 +121,72 @@ Module.setup = function () {
return result;
};
Module.Manifold.prototype.getMesh = function () {
const result = this._GetMesh();
const oldVertPos = result.vertPos;
const oldTriVerts = result.triVerts;
const oldVertNormal = result.vertNormal;
const oldHalfedgeTangent = result.halfedgeTangent;
const conversion1 = v => ['x', 'y', 'z'].map(f => v[f]);
const conversion2 = v => ['x', 'y', 'z', 'w'].map(f => v[f]);
result.vertPos = fromVec(oldVertPos, conversion1);
result.triVerts = fromVec(oldTriVerts);
result.vertNormal = fromVec(oldVertNormal, conversion1);
result.halfedgeTangent = fromVec(oldHalfedgeTangent, conversion2);
oldVertPos.delete();
oldTriVerts.delete();
oldVertNormal.delete();
oldHalfedgeTangent.delete();
return result;
class Mesh {
constructor({
triVerts = new Uint32Array(),
vertPos = new Float32Array(),
vertNormal,
halfedgeTangent
} = {}) {
this.triVerts = triVerts;
this.vertPos = vertPos;
this.vertNormal = vertNormal;
this.halfedgeTangent = halfedgeTangent;
}
get numTri() {
return this.triVerts.length / 3;
}
get numVert() {
return this.vertPos.length / 3;
}
verts(tri) {
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
}
position(vert) {
return this.vertPos.subarray(3 * vert, 3 * (vert + 1));
}
normal(vert) {
return this.vertNormal.subarray(3 * vert, 3 * (vert + 1));
}
tangent(halfedge) {
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
}
}
Module.Mesh = Mesh;
Module.Manifold.prototype.getMesh = function() {
return new Mesh(this._GetMeshJS());
};
Module.Manifold.prototype.getMeshRelation = function () {
Module.Manifold.prototype.getMeshRelation = function() {
const result = this._getMeshRelation();
const oldBarycentric = result.barycentric;
const oldTriBary = result.triBary;
const conversion1 = v => ['x', 'y', 'z'].map(f => v[f]);
const conversion2 = v => {
const conversion1 = v => [v.x, v.y, v.z];
const conversion2 = v => [v[0], v[1], v[2]];
const conversion3 = v => {
return {
meshID: v.meshID,
originalID: v.originalID,
tri: v.tri,
vertBary: conversion1(v.vertBary)
vertBary: conversion2(v.vertBary)
};
};
result.barycentric = fromVec(oldBarycentric, conversion1);
result.triBary = fromVec(oldTriBary, conversion2);
result.triBary = fromVec(oldTriBary, conversion3);
oldBarycentric.delete();
oldTriBary.delete();
return result;
};
Module.Manifold.prototype.boundingBox = function () {
Module.Manifold.prototype.boundingBox = function() {
const result = this._boundingBox();
return {
min: ['x', 'y', 'z'].map(f => result.min[f]),
@@ -190,58 +194,104 @@ Module.setup = function () {
};
};
Module.cube = function (...args) {
Module.ManifoldError = function ManifoldError(code, ...args) {
let message = 'Unknown error';
switch (code) {
case Module.status.NON_FINITE_VERTEX.value:
message = 'Non-finite vertex';
break;
case Module.status.NOT_MANIFOLD.value:
message = 'Not manifold';
break;
case Module.status.VERTEX_INDEX_OUT_OF_BOUNDS.value:
message = 'Vertex index out of bounds';
break;
case Module.status.PROPERTIES_WRONG_LENGTH.value:
message = 'Properties have wrong length';
break;
case Module.status.TRI_PROPERTIES_WRONG_LENGTH.value:
message = 'Tri properties have wrong length';
break;
case Module.status.TRI_PROPERTIES_OUT_OF_BOUNDS.value:
message = 'Tri properties out of bounds';
}
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}
});
const ManifoldCtor = Module.Manifold;
Module.Manifold = function(mesh) {
const manifold = new ManifoldCtor(mesh);
const status = manifold.status();
if (status.value !== 0) {
throw new Module.ManifoldError(status.value);
}
return manifold;
};
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
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 = []) {
const meshVec = mesh2vec(mesh);
Module.smooth = function(mesh, sharpenedEdges = []) {
const sharp = new Module.Vector_smoothness();
toVec(sharp, sharpenedEdges);
const result = Module._Smooth(meshVec, sharp);
const result = Module._Smooth(mesh, sharp);
sharp.delete();
disposeMesh(meshVec);
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);
@@ -249,12 +299,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');
@@ -264,15 +314,13 @@ Module.setup = function () {
const out = Module._LevelSet(wasmFuncPtr, bounds2, edgeLength, level);
removeFunction(wasmFuncPtr);
return out;
}
};
function batchbool(name) {
return function (...args) {
if (args.length == 1)
args = args[0];
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);
for (const m of args) v.push_back(m);
const result = Module['_' + name + 'N'](v);
v.delete();
return result;
+6 -6
View File
@@ -56,7 +56,7 @@ a:active {
}
button:disabled {
color:black;
color: black;
background-color: #eee !important;
cursor: auto;
}
@@ -87,9 +87,9 @@ p {
}
model-viewer {
position: relative;
flex: 3;
width: 100%;
position: relative;
flex: 3;
width: 100%;
background-color: #ddd;
}
@@ -180,14 +180,14 @@ model-viewer {
}
.cancel {
background-image: url(close.png);
background-image: url(icons/close.png);
background-size: 20px;
background-repeat: no-repeat;
background-position: center;
}
.icon:hover {
background-color: rgba(0,0,0,0.2);
background-color: rgba(0, 0, 0, 0.2);
}
.blue {
+45 -33
View File
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
const exampleFunctions = examples.functionBodies;
let editor = undefined;
// File UI ------------------------------------------------------------
@@ -20,11 +22,11 @@ const currentElement = document.querySelector('#current');
const arrow = document.querySelector('.uparrow');
const dropdown = document.querySelector('.dropdown');
const hideDropdown = function () {
const hideDropdown = function() {
dropdown.classList.remove('show');
arrow.classList.remove('down');
};
const toggleDropdown = function (event) {
const toggleDropdown = function(event) {
event.stopPropagation();
dropdown.classList.toggle('show');
arrow.classList.toggle('down');
@@ -53,7 +55,7 @@ function nthKey(n) {
function saveCurrent() {
if (editor) {
const currentName = currentElement.textContent;
if (!examples.get(currentName)) {
if (!exampleFunctions.get(currentName)) {
setScript(currentName, editor.getValue());
}
}
@@ -69,8 +71,9 @@ function switchTo(scriptName) {
switching = true;
currentElement.textContent = scriptName;
setScript('currentName', scriptName);
const code = examples.get(scriptName) ?? getScript(scriptName) ?? '';
isExample = examples.get(scriptName) != null;
const code =
exampleFunctions.get(scriptName) ?? getScript(scriptName) ?? '';
isExample = exampleFunctions.get(scriptName) != null;
editor.setValue(code);
}
}
@@ -86,7 +89,7 @@ function createDropdownItem(name) {
button.appendChild(label);
label.textContent = name;
button.onclick = function () {
button.onclick = function() {
saveCurrent();
switchTo(label.textContent);
};
@@ -103,7 +106,7 @@ function addIcon(button) {
function uniqueName(name) {
let num = 1;
let newName = name;
while (getScript(newName) != null || examples.get(newName) != null) {
while (getScript(newName) != null || exampleFunctions.get(newName) != null) {
newName = name + ' ' + num++;
}
return newName;
@@ -112,10 +115,10 @@ function uniqueName(name) {
function addEdit(button) {
const label = button.firstChild;
const edit = addIcon(button);
edit.style.backgroundImage = 'url(pencil.png)';
edit.style.backgroundImage = 'url(icons/pencil.png)';
edit.style.right = '30px';
edit.onclick = function (event) {
edit.onclick = function(event) {
event.stopPropagation();
const oldName = label.textContent;
const code = getScript(oldName);
@@ -142,29 +145,31 @@ function addEdit(button) {
}
form.onsubmit = rename;
inputElement.onclick = function (event) { event.stopPropagation(); };
inputElement.onclick = function(event) {
event.stopPropagation();
};
inputElement.onblur = function () {
inputElement.onblur = function() {
button.removeChild(form);
label.textContent = oldName;
};
};
const trash = addIcon(button);
trash.style.backgroundImage = 'url(trash.png)';
trash.style.backgroundImage = 'url(icons/trash.png)';
trash.style.right = '0px';
let lastClick = 0;
trash.onclick = function (event) {
trash.onclick = function(event) {
event.stopPropagation();
if (button.classList.contains('blue')) {
lastClick = performance.now();
button.classList.remove('blue');
button.classList.add('red');
document.body.addEventListener('click', function () {
document.body.addEventListener('click', function() {
button.classList.add('blue');
button.classList.remove('red');
}, { once: true });
}, {once: true});
} else if (performance.now() - lastClick > 500) {
removeScript(label.textContent);
if (currentElement.textContent == label.textContent) {
@@ -185,12 +190,14 @@ function newItem(code) {
addEdit(nextButton);
nextButton.click();
};
newButton.onclick = function () { newItem(''); };
newButton.onclick = function() {
newItem('');
};
const runButton = document.querySelector('#compile');
const poster = document.querySelector('#poster');
let manifoldInitialized = false;
let autoExecute = false;
let autoExecute = true;
function initializeRun() {
runButton.disabled = false;
@@ -203,18 +210,22 @@ function initializeRun() {
// Editor ------------------------------------------------------------
let tsWorker = undefined;
require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs' } });
require(['vs/editor/editor.main'], async function () {
const content = await fetch('manifold.d.ts').then(response => response.text());
require.config({
paths:
{vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs'}
});
require(['vs/editor/editor.main'], async function() {
const content =
await fetch('manifold.d.ts').then(response => response.text());
monaco.languages.typescript.typescriptDefaults.addExtraLib(content);
editor = monaco.editor.create(document.getElementById('editor'), {
language: 'typescript',
automaticLayout: true
});
editor = monaco.editor.create(
document.getElementById('editor'),
{language: 'typescript', automaticLayout: true});
const w = await monaco.languages.typescript.getTypeScriptWorker();
tsWorker = await w(editor.getModel().uri);
editor.getModel().updateOptions({tabSize: 2});
for (const [name] of examples) {
for (const [name] of exampleFunctions) {
const button = createDropdownItem(name);
dropdown.appendChild(button.parentElement);
}
@@ -261,7 +272,7 @@ require(['vs/editor/editor.main'], async function () {
// Execution ------------------------------------------------------------
const consoleElement = document.querySelector('#console');
const oldLog = console.log;
console.log = function (message) {
console.log = function(message) {
consoleElement.textContent += message + '\r\n';
consoleElement.scrollTop = consoleElement.scrollHeight;
oldLog(message);
@@ -288,8 +299,9 @@ function finishRun() {
const t1 = performance.now();
const log = consoleElement.textContent;
// Remove "Running..."
consoleElement.textContent = log.substring(log.indexOf("\n") + 1);
console.log(`Took ${(Math.round((t1 - t0) / 10) / 100).toLocaleString()} seconds`);
consoleElement.textContent = log.substring(log.indexOf('\n') + 1);
console.log(
`Took ${(Math.round((t1 - t0) / 10) / 100).toLocaleString()} seconds`);
}
const mv = document.querySelector('model-viewer');
@@ -298,7 +310,7 @@ let manifoldWorker = null;
function createWorker() {
manifoldWorker = new Worker('worker.js');
manifoldWorker.onmessage = function (e) {
manifoldWorker.onmessage = function(e) {
if (e.data == null) {
if (tsWorker != null && !manifoldInitialized) {
initializeRun();
@@ -347,7 +359,7 @@ function cancel() {
console.log('Run canceled');
}
runButton.onclick = function () {
runButton.onclick = function() {
if (runButton.classList.contains('cancel')) {
cancel();
} else {
@@ -356,9 +368,9 @@ runButton.onclick = function () {
};
const downloadButton = document.querySelector('#download');
downloadButton.onclick = function () {
const link = document.createElement("a");
link.download = "manifold.glb";
downloadButton.onclick = function() {
const link = document.createElement('a');
link.download = 'manifold.glb';
link.href = objectURL;
link.click();
};
+115 -97
View File
@@ -12,36 +12,41 @@
// See the License for the specific language governing permissions and
// limitations under the License.
const exampleFunctions = {
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.
(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.
const box = cube([100, 100, 100], true);
const ball = sphere(60, 100);
// You must name your final output "result".
const result = box.subtract(ball);
// All changes are automatically saved and restored between sessions.
// 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 () {
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 nDivisions = 50;// Number of divisions (both ways) in the screw surface.
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
const gap = 0.2; // Spacing between the two halves to allow sliding.
const nDivisions =
50; // Number of divisions (both ways) in the screw surface.
const scale = edgeLength / (2 * Math.sqrt(2));
@@ -54,28 +59,32 @@ const exampleFunctions = {
}
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 () {
// Demonstrates how at 90-degree intersections, the sphere and cylinder facets
// match up perfectly, for any choice of global resolution parameters.
RoundedFrame: function() {
// Demonstrates how at 90-degree intersections, the sphere and cylinder
// facets match up perfectly, for any choice of global resolution
// parameters.
function roundedFrame(edgeLength, radius, circularSegments = 0) {
const edge = cylinder(edgeLength, radius, -1, circularSegments);
const corner = sphere(radius, circularSegments);
let edge1 = union(corner, edge);
edge1 = edge1.rotate([-90, 0, 0]).translate([-edgeLength / 2, -edgeLength / 2, 0]);
edge1 = edge1.rotate([-90, 0, 0]).translate([
-edgeLength / 2, -edgeLength / 2, 0
]);
let edge2 = edge1.rotate([0, 0, 180]);
edge2 = union(edge2, edge1);
edge2 = union(edge2, edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
edge2 =
union(edge2, edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
let edge4 = edge2.rotate([0, 0, 90]);
edge4 = union(edge4, edge2);
@@ -92,7 +101,7 @@ const exampleFunctions = {
return result;
},
Heart: function () {
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
@@ -136,7 +145,7 @@ const exampleFunctions = {
return result;
},
Scallop: function () {
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
@@ -148,11 +157,9 @@ const exampleFunctions = {
const sharpness = 0.8;
const n = 50;
const scallop = {
vertPos: [],
triVerts: []
};
scallop.vertPos.push([-offset, 0, height], [-offset, 0, -height]);
const vertPos = [];
const triVerts = [];
vertPos.push(-offset, 0, height, -offset, 0, -height);
const sharpenedEdges = [];
const delta = 3.14159 / wiggles;
@@ -160,45 +167,51 @@ const exampleFunctions = {
const theta = (i - wiggles) * delta;
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
scallop.vertPos.push([radius * Math.cos(theta),
radius * Math.sin(theta),
amp * (i % 2 == 0 ? 1 : -1)]);
vertPos.push(
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 = 3 * scallop.triVerts.length + 1;
sharpenedEdges.push({ halfedge, smoothness });
scallop.triVerts.push([0, 2 + i, 2 + j]);
let halfedge = triVerts.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triVerts.push(0, 2 + i, 2 + j);
halfedge = 3 * scallop.triVerts.length + 1;
sharpenedEdges.push({ halfedge, smoothness });
scallop.triVerts.push([1, 2 + j, 2 + i]);
halfedge = triVerts.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triVerts.push(1, 2 + j, 2 + i);
}
const scallop = new Mesh();
scallop.triVerts = Uint32Array.from(triVerts);
scallop.vertPos = Float32Array.from(vertPos);
const result = smooth(scallop, sharpenedEdges).refine(n);
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.
// 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.
// @param majorRadius Radius of the interior of the imaginary donut.
// @param minorRadius Radius of the small cross-section of the imaginary donut.
// @param threadRadius Radius of the small cross-section of the actual object.
// @param circularSegments Number of linear segments making up the threadRadius
// circle. Default is getCircularSegments(threadRadius).
// @param minorRadius Radius of the small cross-section of the imaginary
// donut.
// @param threadRadius Radius of the small cross-section of the actual
// object.
// @param circularSegments Number of linear segments making up the
// threadRadius circle. Default is getCircularSegments(threadRadius).
// @param linearSegments Number of segments along the length of the knot.
// Default makes roughly square facets.
function torusKnot(p, q, majorRadius, minorRadius,
threadRadius, circularSegments = 0,
linearSegments = 0) {
function torusKnot(
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
linearSegments = 0) {
function gcd(a, b) {
return b == 0 ? a : gcd(b, a % b);
}
@@ -207,9 +220,9 @@ const exampleFunctions = {
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;
@@ -221,27 +234,30 @@ const exampleFunctions = {
const point = new THREE.Vector3();
const axis = new THREE.Vector3();
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 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;
}
let knot = revolve(circle, m).warp(func);
if (kLoops > 1) {
const knots = []
const knots = [];
for (let k = 0; k < kLoops; ++k) {
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
}
@@ -258,7 +274,7 @@ const exampleFunctions = {
return result;
},
MengerSponge: function () {
MengerSponge: function() {
// This example demonstrates how symbolic perturbation correctly creates
// holes even though the subtracted objects are exactly coplanar.
@@ -269,10 +285,11 @@ const exampleFunctions = {
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]
[-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);
@@ -296,14 +313,13 @@ const exampleFunctions = {
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),
@@ -321,9 +337,9 @@ const exampleFunctions = {
]);
}
let decor =
extrude(circle, width, nDivision, 180).scale([1, 0.5, 1]).translate([
0, radius, 0
]);
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 = [];
@@ -343,9 +359,8 @@ const exampleFunctions = {
}
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;
@@ -354,20 +369,21 @@ const exampleFunctions = {
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.
@@ -380,9 +396,8 @@ const exampleFunctions = {
const x = p[0] - pi / 4;
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);
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
Math.cos(z) * Math.sin(x);
}
function gyroidOffset(level) {
@@ -396,7 +411,8 @@ const exampleFunctions = {
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]));
const result =
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
return result.intersect(box.rotate([0, 0, 45]));
}
@@ -407,16 +423,18 @@ const exampleFunctions = {
}
};
const examples = new Map();
exports.functionBodies = new Map();
for (const [func, code] of Object.entries(exampleFunctions)) {
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
lines.splice(0, 1); // remove first line
lines.splice(-2, 2); // remove last two lines
// remove first four leading spaces
const body = '\n' + lines.map(l => l.slice(4)).join('\n');
const name = func.replace(/([a-z])([A-Z])/g, '$1 $2');// Add spaces between words
examples.set(name, body);
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);

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 539 B

Before

Width:  |  Height:  |  Size: 375 B

After

Width:  |  Height:  |  Size: 375 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

+34 -74
View File
@@ -48,39 +48,38 @@
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
</body>
<script src="https://unpkg.com/three@0.144.0/build/three.js"></script>
<script src="https://unpkg.com/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
<script src="https://unpkg.com/three@0.144.0/examples/js/exporters/GLTFExporter.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
<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="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/exporters/GLTFExporter.js"></script>
<script src="manifold.js"></script>
<script>
var Module = {
onRuntimeInitialized: function () {
Module.setup();
const mesh = new THREE.Mesh(undefined, new THREE.MeshStandardMaterial({
color: 'yellow',
metalness: 1,
roughness: 0.2
}));
let wasm;
Module().then(function (tmp) {
wasm = tmp;
wasm.setup();
const mesh = new THREE.Mesh(undefined, new THREE.MeshStandardMaterial({
color: 'yellow',
metalness: 1,
roughness: 0.2
}));
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const manifold_1 = Module.cube(0.2, 0.2, 0.2, true);
// const manifold_1 = Module
// .revolve([[[0.1, 0.1], [0.2, 0.1], [0.1, 0.2]]], 64)
// .translate(0, 0, -0.1);
const manifold_2 = new Module.Manifold(geometry2mesh(icosahedron));
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(Module[operation](manifold_1, manifold_2).getMesh());
push2MV(mesh);
};
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
push2MV(mesh);
};
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;
@@ -102,57 +101,20 @@
// functions to convert between three.js and wasm
function geometry2mesh(geometry) {
const mesh = {
vertPos: new Module.Vector_vec3(),
vertNormal: new Module.Vector_vec3(),
triVerts: new Module.Vector_ivec3(),
halfedgeTangent: new Module.Vector_vec4()
};
const temp = new THREE.Vector3();
const p = geometry.attributes.position;
// const n = geometry.attributes.normal;
const x = geometry.index;
for (let i = 0; i < p.count; i++) {
temp.fromBufferAttribute(p, i);
mesh.vertPos.push_back(temp);
// temp.fromBufferAttribute(n, i);
// mesh.vertNormal.push_back(temp);
}
for (let i = 0; i < x.count; i += 3) {
mesh.triVerts.push_back(x.array.subarray(i, i + 3));
}
return mesh;
const vertPos = geometry.attributes.position.array;
const vertNormal = geometry.attributes.normal.array;
const triVerts = geometry.index.array;
return new wasm.Mesh({vertPos, vertNormal, triVerts});
}
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
const numVert = mesh.vertPos.length;
const vert = new Float32Array(3 * numVert);
for (let i = 0; i < numVert; i++) {
const v = mesh.vertPos[i];
const idx = 3 * i;
vert[idx] = v[0];
vert[idx + 1] = v[1];
vert[idx + 2] = v[2];
}
const numTri = mesh.triVerts.length;
const tri = new Uint32Array(3 * numTri);
for (let i = 0; i < numTri; i++) {
const v = mesh.triVerts[i];
const idx = 3 * i;
tri[idx] = v[0];
tri[idx + 1] = v[1];
tri[idx + 2] = v[2];
}
geometry.setAttribute('position', new THREE.BufferAttribute(vert, 3));
geometry.setIndex(new THREE.BufferAttribute(tri, 1));
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
// most of three.js geometries arent manifolds, so...
// most of three.js geometries aren't manifolds, so...
function simplify(geometry) {
delete geometry.attributes.normal;
delete geometry.attributes.uv;
@@ -161,7 +123,5 @@
return simplified;
}
</script>
<script src="manifold.js"></script>
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
</html>
+119
View File
@@ -0,0 +1,119 @@
<!doctype html>
<html lang="en">
<head>
<title>Manifold - three.js texture example</title>
<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;
background-color: #f7f7f7;
font-family: 'Rubik', sans-serif;
font-size: 16px;
line-height: 24px;
color: rgba(0, 0, 0, .87);
font-weight: 400;
-webkit-font-smoothing: antialiased;
}
p {
max-width: 700px;
margin: 1em;
text-align: left;
}
canvas {
display: block;
width: 100vw;
height: 100vw;
max-width: 600px;
max-height: 600px;
}
</style>
</head>
<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>
<input type="checkbox"/><br />
<canvas id="output"></canvas>
</body>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
<script src="manifold.js"></script>
<script>
Module().then(function (wasm) {
wasm.setup();
const cube = new wasm.Mesh();
cube.vertPos = new Float32Array(24);
cube.triVerts = new Uint32Array(36);
cube.vertPos.set([
-1, -1, -1,
1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1]);
cube.triVerts.set([
0, 2, 1, 0, 3, 2,
4, 5, 6, 4, 6, 7,
0, 1, 5, 0, 5, 4,
1, 2, 6, 1, 6, 5,
2, 3, 7, 2, 7, 6,
3, 0, 4, 3, 4, 7]);
const manifold = wasm.Manifold(cube);
const result = manifold.add(manifold.rotate([90, 0, 90]).translate([1, 1, 0]));
// we have a manifold, let's do some three.js
const camera = new THREE.PerspectiveCamera(30, 1, 1, 100);
camera.position.z = 10;
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(mesh2geometry(result.getMesh()),
new THREE.MeshNormalMaterial({
flatShading: true
}));
const wireframe = new THREE.WireframeGeometry(mesh.geometry);
const lines = new THREE.LineSegments(wireframe);
scene.add(mesh);
scene.add(lines);
lines.visible = false;
const checkbox = document.querySelector('input');
checkbox.onchange = function (event) {
mesh.visible = !checkbox.checked;
lines.visible = checkbox.checked;
};
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) {
scene.rotation.x = time / 2000;
scene.rotation.y = time / 1000;
renderer.render(scene, camera);
});
});
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
</script>
</html>
+44 -90
View File
@@ -47,113 +47,68 @@
<canvas id="output"></canvas>
</body>
<script src="https://unpkg.com/three@0.142.0/build/three.js"></script>
<script src="https://unpkg.com/three@0.142.0/examples/js/utils/BufferGeometryUtils.js"></script>
<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>
var Module = {
onRuntimeInitialized: function () {
// we have manifold module, let's do some three.js
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
camera.position.z = 1;
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;
Module.setup();
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
flatShading: true
}));
scene.add(mesh);
wasm.setup();
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
flatShading: true
}));
scene.add(mesh);
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const manifold_1 = Module.cube(0.2, 0.2, 0.2, true);
// const manifold_1 = Module
// .revolve([[[0.1, 0.1], [0.2, 0.1], [0.1, 0.2]]], 64)
// .translate(0, 0, -0.1);
const manifold_2 = new Module.Manifold(geometry2mesh(icosahedron));
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(Module[operation](manifold_1, manifold_2).getMesh());
};
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
};
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 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);
});
}
};
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
function geometry2mesh(geometry) {
const mesh = {
vertPos: new Module.Vector_vec3(),
vertNormal: new Module.Vector_vec3(),
triVerts: new Module.Vector_ivec3(),
halfedgeTangent: new Module.Vector_vec4()
};
const temp = new THREE.Vector3();
const p = geometry.attributes.position;
const n = geometry.attributes.normal;
const x = geometry.index;
for (let i = 0; i < p.count; i++) {
temp.fromBufferAttribute(p, i);
mesh.vertPos.push_back(temp);
temp.fromBufferAttribute(n, i);
mesh.vertNormal.push_back(temp);
}
for (let i = 0; i < x.count; i += 3) {
mesh.triVerts.push_back(x.array.subarray(i, i + 3));
}
return mesh;
const vertPos = geometry.attributes.position.array;
const vertNormal = geometry.attributes.normal.array;
const triVerts = geometry.index.array;
return new wasm.Mesh({vertPos, vertNormal, triVerts});
}
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
const numVert = mesh.vertPos.length;
const vert = new Float32Array(3 * numVert);
const norm = new Float32Array(3 * numVert);
for (let i = 0; i < numVert; i++) {
const v = mesh.vertPos[i];
const n = mesh.vertNormal[i];
const idx = 3 * i;
vert[idx] = v[0];
vert[idx + 1] = v[1];
vert[idx + 2] = v[2];
norm[idx] = n[0];
norm[idx + 1] = n[1];
norm[idx + 2] = n[2];
}
const numTri = mesh.triVerts.length;
const tri = new Uint32Array(3 * numTri);
for (let i = 0; i < numTri; i++) {
const v = mesh.triVerts[i];
const idx = 3 * i;
tri[idx] = v[0];
tri[idx + 1] = v[1];
tri[idx + 2] = v[2];
}
geometry.setAttribute('position', new THREE.BufferAttribute(vert, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(norm, 3));
geometry.setIndex(new THREE.BufferAttribute(tri, 1));
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
// most of three.js geometries arent manifolds, so...
// most of three.js geometries aren't manifolds, so...
function simplify(geometry) {
delete geometry.attributes.normal;
delete geometry.attributes.uv;
@@ -162,6 +117,5 @@
return simplified;
}
</script>
<script src="manifold.js"></script>
</html>
+86 -107
View File
@@ -12,6 +12,11 @@
// 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');
// manifold member functions that returns a new manifold
const memberFunctions = [
'add', 'subtract', 'intersect', 'refine', 'transform', 'translate', 'rotate',
@@ -24,98 +29,92 @@ const constructors = [
];
const utils = [
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
'getCircularSegments'
'getCircularSegments', 'Mesh'
];
const exposedFunctions = constructors.concat(utils);
var Module = {
onRuntimeInitialized: function () {
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.
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.
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 = Module[name];
Module[name] = function (...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
}
}
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 = [];
}
postMessage(null);
}
};
const threePath = 'https://cdn.jsdelivr.net/npm/three@0.144.0/';
importScripts('manifold.js', threePath + 'build/three.js', threePath + 'examples/js/exporters/GLTFExporter.js');
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);
});
const oldLog = console.log;
console.log = function (...args) {
console.log = function(...args) {
let message = '';
for (const arg of args) {
if (typeof arg == "object") {
if (typeof arg == 'object') {
message += JSON.stringify(arg, null, 4);
} else {
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 => Module[name]));
} catch (error) {
console.log(error.toString());
postMessage({ objectURL: null });
} finally {
Module.cleanup();
}
}
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();
}
}
// Export & Rendering ------------------------------------------------------------
const mesh = new THREE.Mesh(undefined, new THREE.MeshStandardMaterial({
color: 'yellow',
metalness: 1,
roughness: 0.2
}));
// Export & Rendering
// ------------------------------------------------------------
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
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 exporter = new THREE.GLTFExporter();
@@ -126,50 +125,30 @@ function exportGLB(manifold) {
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 }
);
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});
}
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
const numVert = mesh.vertPos.length;
const vert = new Float32Array(3 * numVert);
for (let i = 0; i < numVert; i++) {
const v = mesh.vertPos[i];
const idx = 3 * i;
vert[idx] = v[0];
vert[idx + 1] = v[1];
vert[idx + 2] = v[2];
}
const numTri = mesh.triVerts.length;
const tri = new Uint32Array(3 * numTri);
for (let i = 0; i < numTri; i++) {
const v = mesh.triVerts[i];
const idx = 3 * i;
tri[idx] = v[0];
tri[idx + 1] = v[1];
tri[idx + 2] = v[2];
}
geometry.setAttribute('position', new THREE.BufferAttribute(vert, 3));
geometry.setIndex(new THREE.BufferAttribute(tri, 1));
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
+45 -22
View File
@@ -17,13 +17,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 Mesh = {
vertPos: Vec3[],
triVerts: Vec3[],
vertNormal?: Vec3[],
halfedgeTangent?: Vec4[]
};
type Polygons = SimplePolygon|SimplePolygon[];
type Box = {
min: Vec3,
max: Vec3
@@ -55,7 +49,24 @@ type MeshRelation = {
triBary: BaryRef[],
};
declare class Mesh {
vertPos: Float32Array;
triVerts: Uint32Array;
vertNormal?: Float32Array;
halfedgeTangent?: Float32Array;
get numTri(): number;
get numVert(): number;
verts(tri: number): Uint32Array<3>;
position(vert: number): Float32Array<3>;
normal(vert: number): Float32Array<3>;
tangent(halfedge: number): Float32Array<4>;
}
declare class Manifold {
/**
* Create a Manifold from a Mesh object.
*/
constructor(mesh: Mesh);
/**
* Transform this Manifold in space. The first three columns form a 3x3 matrix
* transform and the last is a translation vector. This operation can be
@@ -77,9 +88,9 @@ declare class Manifold {
* Applies an Euler angle rotation to the manifold, first about the X axis,
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
* error, and eliminate it completely for any multiples of 90 degrees.
* Additionally, more efficient code paths are used to update the manifold when
* the transforms only rotate by multiples of 90 degrees. This operation can
* be chained. Transforms are combined and applied lazily.
* Additionally, more efficient code paths are used to update the manifold
* when the transforms only rotate by multiples of 90 degrees. This operation
* can be chained. Transforms are combined and applied lazily.
*
* @param v [X, Y, Z] rotation in degrees.
*/
@@ -91,7 +102,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
@@ -201,8 +212,8 @@ 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.
* This returns a Mesh of simple vectors of vertices and triangles suitable
* for saving or other operations outside of the context of this library.
*/
getMesh(): Mesh;
@@ -253,7 +264,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
@@ -269,8 +280,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.
@@ -337,8 +348,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
@@ -352,7 +363,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;
@@ -389,8 +400,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
@@ -408,10 +419,19 @@ declare function setCircularSegments(segments: number): void;
declare function getCircularSegments(radius: number): number;
///@}
/**
* Create a Manifold from a serialized Mesh object (MeshVec). Unlike the
* constructor, this method does not dispose the Mesh after using it.
*
* @param meshVec The serialized Mesh object to convert into a Manifold.
*/
declare function ManifoldFromMeshVec(meshVec: MeshVec): Manifold;
declare interface ManifoldStatic {
cube: typeof cube;
cylinder: typeof cylinder;
sphere: typeof sphere;
smooth: typeof smooth;
tetrahedron: typeof tetrahedron;
extrude: typeof extrude;
revolve: typeof revolve;
@@ -424,6 +444,9 @@ declare interface ManifoldStatic {
setMinCircularEdgeLength: typeof setMinCircularEdgeLength;
setCircularSegments: typeof setCircularSegments;
getCircularSegments: typeof getCircularSegments;
ManifoldFromMeshVec: typeof ManifoldFromMeshVec;
Manifold: typeof Manifold;
setup: () => void;
}
declare const Module: ManifoldStatic;
declare function Module(): Promise<ManifoldStatic>;
+1705
View File
File diff suppressed because it is too large Load Diff
+12 -2
View File
@@ -6,7 +6,9 @@
"files": [
"manifold.js",
"manifold.wasm",
"manifold.d.ts"
"manifold.d.ts",
"examples/examples.js",
"test/test.js"
],
"typings": "manifold.d.ts",
"types": "manifold.d.ts",
@@ -32,5 +34,13 @@
"bugs": {
"url": "https://github.com/elalish/manifold/issues"
},
"homepage": "https://github.com/elalish/manifold#readme"
"homepage": "https://github.com/elalish/manifold#readme",
"scripts": {
"test": "mocha -u tdd -t 30000"
},
"devDependencies": {
"chai": "*",
"mocha": "*",
"three": "^0.146.0"
}
}
+153
View File
@@ -0,0 +1,153 @@
// 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.
const expect = require('chai').expect;
const wasm = require('../manifold.js');
const examples = require('../examples/examples');
const THREE = require('three');
// manifold member functions that returns a new manifold
const memberFunctions = [
'add', 'subtract', 'intersect', 'refine', 'transform', 'translate', 'rotate',
'scale', 'asOriginal', 'decompose'
];
// top level functions that constructs a new manifold
const constructors = [
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'union',
'difference', 'intersection', 'compose', 'levelSet', 'smooth'
];
const utils = [
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
'getCircularSegments', 'Mesh'
];
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.
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;
}
}
for (const name of constructors) {
const originalFn = Module[name];
Module[name] = function(...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
}
}
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 = [];
}
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();
}
}
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('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('Rounded Frame', () => {
const result = runExample('Rounded Frame');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(353706, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
});
test('Heart', () => {
const result = runExample('Heart');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(282743, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22187, 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('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('Menger Sponge', () => {
const result = runExample('Menger Sponge');
expect(result.genus).to.equal(1409, 'Genus');
expect(result.volume).to.be.closeTo(406457, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(247590, 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('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');
});
});
});
+1 -1
View File
@@ -22,7 +22,7 @@ int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
namespace manifold {
/**
* Creates a classic torus knot, defined as a string wrapping peroidically
* 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 Manifold.Warp() method, thus avoiding any handling of triangles.
+1 -1
View File
@@ -23,7 +23,7 @@ namespace manifold {
*
* @param edgeLength Distance between the corners.
* @param radius Radius of the frame members.
* @param circularSegments Number of segements in the cylinders and spheres.
* @param circularSegments Number of segments in the cylinders and spheres.
* Defaults to Manifold.GetCircularSegments().
*/
Manifold RoundedFrame(float edgeLength, float radius, int circularSegments) {
+2 -2
View File
@@ -27,10 +27,10 @@ class Collider {
// Aborts and returns false if transform is not axis aligned.
bool Transform(glm::mat4x3);
void UpdateBoxes(const VecDH<Box>& leafBB);
// Collisions returns a sparse result, where i is the querry index and j is
// Collisions returns a sparse result, where i is the query index and j is
// the leaf index where their bounding boxes overlap.
template <typename T>
SparseIndices Collisions(const VecDH<T>& querriesIn) const;
SparseIndices Collisions(const VecDH<T>& queriesIn) const;
private:
VecDH<Box> nodeBBox_;
+15 -15
View File
@@ -159,7 +159,7 @@ struct CreateRadixTree {
template <typename T, const bool allocateOnly>
struct FindCollisions {
thrust::pair<int*, int*> querryTri_;
thrust::pair<int*, int*> queryTri_;
int* counts;
const Box* nodeBBox_;
const thrust::pair<int, int>* internalChildren_;
@@ -176,8 +176,8 @@ struct FindCollisions {
count++;
} else {
int pos = count++;
querryTri_.first[pos] = queryIdx;
querryTri_.second[pos] = Node2Leaf(node);
queryTri_.first[pos] = queryIdx;
queryTri_.second[pos] = Node2Leaf(node);
}
}
return overlaps && IsInternal(node); // Should traverse into node
@@ -264,32 +264,32 @@ Collider::Collider(const VecDH<Box>& leafBB,
}
/**
* For a vector of querry objects, this returns a sparse array of overlaps
* between the querries and the bounding boxes of the collider. Querries are
* For a vector of query objects, this returns a sparse array of overlaps
* between the queries and the bounding boxes of the collider. Queries are
* normally axis-aligned bounding boxes. Points can also be used, and this case
* overlaps are defined as lying in the XY projection of the bounding box.
*/
template <typename T>
SparseIndices Collider::Collisions(const VecDH<T>& querriesIn) const {
SparseIndices Collider::Collisions(const VecDH<T>& queriesIn) const {
// note that the length is 1 larger than the number of queries so the last
// element can store the sum when using exclusive scan
VecDH<int> counts(querriesIn.size() + 1, 0);
auto policy = autoPolicy(querriesIn.size());
VecDH<int> counts(queriesIn.size() + 1, 0);
auto policy = autoPolicy(queriesIn.size());
// compute the number of collisions to determine the size for allocation and
// offset, this avoids the need for atomic
for_each_n(policy, zip(querriesIn.cbegin(), countAt(0)), querriesIn.size(),
for_each_n(policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
FindCollisions<T, true>(
{thrust::pair<int*, int*>(nullptr, nullptr), counts.ptrD(),
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
// compute start index for each query and total count
exclusive_scan(policy, counts.begin(), counts.end(), counts.begin());
SparseIndices querryTri(counts.back());
SparseIndices queryTri(counts.back());
// actually recording collisions
for_each_n(
policy, zip(querriesIn.cbegin(), countAt(0)), querriesIn.size(),
FindCollisions<T, false>({querryTri.ptrDpq(), counts.ptrD(),
policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
FindCollisions<T, false>({queryTri.ptrDpq(), counts.ptrD(),
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
return querryTri;
return queryTri;
}
/**
@@ -299,13 +299,13 @@ SparseIndices Collider::Collisions(const VecDH<T>& querriesIn) const {
void Collider::UpdateBoxes(const VecDH<Box>& leafBB) {
ASSERT(leafBB.size() == NumLeaves(), userErr,
"must have the same number of updated boxes as original");
// copy in leaf node Boxs
// copy in leaf node Boxes
strided_range<VecDH<Box>::Iter> leaves(nodeBBox_.begin(), nodeBBox_.end(), 2);
auto policy = autoPolicy(NumInternal());
copy(policy, leafBB.cbegin(), leafBB.cend(), leaves.begin());
// create global counters
VecDH<int> counter(NumInternal(), 0);
// kernel over leaves to save internal Boxs
// kernel over leaves to save internal Boxes
for_each_n(
policy, countAt(0), NumLeaves(),
BuildInternalBoxes({nodeBBox_.ptrD(), counter.ptrD(), nodeParent_.ptrD(),
+2 -1
View File
@@ -83,7 +83,7 @@ class Manifold {
* default on construction. If circularSegments is specified, it takes
* precedence. If it is zero, then instead the minimum is used of the segments
* calculated based on edge length and angle, rounded up to the nearest
* multiple of four. To get numbers not divisible by four, circularSegements
* multiple of four. To get numbers not divisible by four, circularSegments
* must be specified.
*/
///@{
@@ -98,6 +98,7 @@ class Manifold {
*/
///@{
Mesh GetMesh() const;
MeshGL GetMeshGL() const;
bool IsEmpty() const;
enum class Error {
NO_ERROR,
+1 -1
View File
@@ -38,7 +38,7 @@
*
* Note many functions are designed to work symmetrically, for instance for both
* p2q1 and p1q2. Inside of these functions P and Q are marked as though the
* funtion is forwards, but it may include a Boolean "reverse" that indicates P
* function is forwards, but it may include a Boolean "reverse" that indicates P
* and Q have been swapped.
*/
+1 -1
View File
@@ -212,7 +212,7 @@ void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
const VecDH<int>::IterC faceP2R, bool forward) {
// Each edge in the map is partially retained; for each of these, look up
// their original verts and include them based on their winding number (i03),
// while remaping them to the output using vP2R. Use the verts position
// while remapping them to the output using vP2R. Use the verts position
// projected along the edge vector to pair them up, then distribute these
// edges to their faces.
VecDH<Halfedge> &halfedgeR = outR.halfedge_;
+2 -2
View File
@@ -52,7 +52,7 @@ struct RemoveFace {
namespace manifold {
/**
* Constructs a smooth version of the input mesh by creating tangents; this
* method will throw if you have supplied tangnets with your mesh already. The
* method will throw if you have supplied tangents with your mesh already. The
* actual triangle resolution is unchanged; use the Refine() method to
* interpolate to a higher-resolution curve.
*
@@ -172,7 +172,7 @@ Manifold Manifold::Sphere(float radius, int circularSegments) {
* @param crossSection A set of non-overlapping polygons to extrude.
* @param height Z-extent of extrusion.
* @param nDivisions Number of extra copies of the crossSection to insert into
* the shape vertically; especially useful in combnation with twistDegrees to
* the shape vertically; especially useful in combination with twistDegrees to
* avoid interpolation artifacts. Default is none.
* @param twistDegrees Amount to twist the top crossSection relative to the
* bottom, interpolated linearly for the divisions in between.
+2 -2
View File
@@ -230,7 +230,7 @@ CsgOpNode::CsgOpNode(const std::vector<std::shared_ptr<CsgNode>> &children,
: impl_(std::make_shared<Impl>()) {
impl_->children_ = children;
SetOp(op);
// opportunisticly flatten the tree without costly evaluation
// opportunistically flatten the tree without costly evaluation
GetChildren(false);
}
@@ -239,7 +239,7 @@ CsgOpNode::CsgOpNode(std::vector<std::shared_ptr<CsgNode>> &&children,
: impl_(std::make_shared<Impl>()) {
impl_->children_ = children;
SetOp(op);
// opportunisticly flatten the tree without costly evaluation
// opportunistically flatten the tree without costly evaluation
GetChildren(false);
}
+1 -1
View File
@@ -186,7 +186,7 @@ Polygons Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
int vert = halfedge_[thisEdge].startVert;
polys.back().push_back({projection * vertPos_[vert], vert});
const auto result = vert_edge.find(halfedge_[thisEdge].endVert);
ASSERT(result != vert_edge.end(), topologyErr, "nonmanifold edge");
ASSERT(result != vert_edge.end(), topologyErr, "non-manifold edge");
thisEdge = result->second;
vert_edge.erase(result);
}
+2 -2
View File
@@ -332,8 +332,8 @@ Manifold::Impl::Impl(const Mesh& mesh,
}
/**
* Create eiter a unit tetrahedron, cube or octahedron. The cube is in the first
* octant, while the others are symmetric about the origin.
* Create either a unit tetrahedron, cube or octahedron. The cube is in the
* first octant, while the others are symmetric about the origin.
*/
Manifold::Impl::Impl(Shape shape) {
std::vector<glm::vec3> vertPos;
+45 -9
View File
@@ -100,7 +100,7 @@ CsgLeafNode& Manifold::GetCsgLeafNode() const {
* properties in different triangles.
* @param properties A vector whose length is the largest index in
* triProperties times the length of propertyTolerance (the number of
* properties). Think of it as a propery matrix indexed as [index *
* properties). Think of it as a property matrix indexed as [index *
* numProperties + propertyNum].
* @param propertyTolerance A vector of precision values for each property.
* This is the amount of interpolation error allowed before two neighboring
@@ -138,6 +138,42 @@ Mesh Manifold::GetMesh() const {
return result;
}
MeshGL Manifold::GetMeshGL() const {
const Impl& impl = *GetCsgLeafNode().GetImpl();
const int numVert = NumVert();
const int numTri = NumTri();
MeshGL out;
out.vertPos.resize(3 * numVert);
out.vertNormal.resize(3 * numVert);
out.triVerts.resize(3 * numTri);
for (int i = 0; i < numVert; ++i) {
const glm::vec3 v = impl.vertPos_[i];
out.vertPos[3 * i] = v.x;
out.vertPos[3 * i + 1] = v.y;
out.vertPos[3 * i + 2] = v.z;
const glm::vec3 n = impl.vertNormal_[i];
out.vertNormal[3 * i] = n.x;
out.vertNormal[3 * i + 1] = n.y;
out.vertNormal[3 * i + 2] = n.z;
}
for (int i = 0; i < numTri * 3; ++i) {
out.triVerts[i] = impl.halfedge_[i].startVert;
}
const int numHalfedge = impl.halfedgeTangent_.size();
out.halfedgeTangent.resize(4 * numHalfedge);
for (int i = 0; i < numHalfedge; ++i) {
const glm::vec4 t = impl.halfedgeTangent_[i];
out.halfedgeTangent[4 * i] = t.x;
out.halfedgeTangent[4 * i + 1] = t.y;
out.halfedgeTangent[4 * i + 2] = t.z;
out.halfedgeTangent[4 * i + 3] = t.w;
}
return out;
}
int Manifold::circularSegments_ = 0;
float Manifold::circularAngle_ = 10.0f;
float Manifold::circularEdgeLength_ = 1.0f;
@@ -172,7 +208,7 @@ void Manifold::SetMinCircularEdgeLength(float length) {
/**
* Sets the default number of circular segments for the
* Cylinder(), Sphere(), and Revolve() constructors. Overrides the edge length
* and angle constraints and sets the number of segements to exactly this value.
* and angle constraints and sets the number of segments to exactly this value.
*
* @param number Number of circular segments. Default is 0, meaning no
* constraint is applied.
@@ -275,7 +311,7 @@ Curvature Manifold::GetCurvature() const {
}
/**
* Gets the relationship to the previous meshes, for the purpose of assinging
* Gets the relationship to the previous meshes, for the purpose of assigning
* properties like texture coordinates. The triBary vector is the same length as
* Mesh.triVerts: BaryRef.originalID indicates the source mesh and BaryRef.tri
* is that mesh's triangle index to which these barycentric coordinates refer.
@@ -390,12 +426,12 @@ Manifold Manifold::Scale(glm::vec3 v) const {
}
/**
* Applys an Euler angle rotation to the manifold, first about the X axis, then
* Applies an Euler angle rotation to the manifold, first about the X axis, then
* Y, then Z, in degrees. We use degrees so that we can minimize rounding error,
* and elimiate it completely for any multiples of 90 degrees. Addtionally, more
* efficient code paths are used to update the manifold when the transforms only
* rotate by multiples of 90 degrees. This operation can be chained. Transforms
* are combined and applied lazily.
* and eliminate it completely for any multiples of 90 degrees. Additionally,
* more efficient code paths are used to update the manifold when the transforms
* only rotate by multiples of 90 degrees. This operation can be chained.
* Transforms are combined and applied lazily.
*
* @param xDegrees First rotation, degrees about the X-axis.
* @param yDegrees Second rotation, degrees about the Y-axis.
@@ -549,7 +585,7 @@ std::pair<Manifold, Manifold> Manifold::Split(const Manifold& cutter) const {
}
/**
* Convient version of Split() for a half-space.
* Convenient version of Split() for a half-space.
*
* @param normal This vector is normal to the cutting plane and its length does
* not matter. The first result is in the direction of this vector, the second
+1 -1
View File
@@ -126,7 +126,7 @@ struct Halfedge {
};
/**
* This is a temporary edge strcture which only stores edges forward and
* This is a temporary edge structure which only stores edges forward and
* references the halfedge it was created from.
*/
struct TmpEdge {
+1 -1
View File
@@ -493,7 +493,7 @@ Manifold::Impl::MeshRelationD Manifold::Impl::Subdivide(int n) {
meshRelation_.triBary.ptrD(),
oldMeshRelation.barycentric.cptrD(), triVertStart, n,
halfedge_.ptrD()}));
// Create subtriangles
// Create sub-triangles
VecDH<glm::ivec3> triVerts(n * n * numTri);
for_each_n(policy, countAt(0), numTri,
SplitTris({triVerts.ptrD(), halfedge_.cptrD(), half2Edge.cptrD(),
+1 -1
View File
@@ -171,7 +171,7 @@ void Manifold::Impl::Finish() {
CalculateBBox();
SetPrecision(precision_);
if (!bBox_.IsFinite()) {
// Decimated out of existance - early out.
// Decimated out of existence - early out.
MarkFailure(Error::NO_ERROR);
return;
}
+4 -4
View File
@@ -49,7 +49,7 @@ bool OverlapAssert(bool condition, const char *file, int line,
* the asserted condition is false, it implies the monotone subdivision has
* failed. This is most likely due to the input polygons being overlapped by
* more than the input precision, but if not, then it indicates a bug. Either
* way subdivision processing stops: if params.processOvelaps is false, then an
* way subdivision processing stops: if params.processOverlaps is false, then an
* exception is thrown. Otherwise this returns true from the sweep function,
* causing polygons to be left in their original state.
*
@@ -319,7 +319,7 @@ class Monotones {
/**
* This is the data structure of the polygons themselves. They are stored as a
* list in sweep-line order. The left and right pointers form the polygons,
* while the mesh_idx describes the input indices that will be tranfered to
* while the mesh_idx describes the input indices that will be transferred to
* the output triangulation. The edgeRight value represents an extra contraint
* from the mesh Boolean algorithm.
*/
@@ -772,8 +772,8 @@ class Monotones {
/**
* This function sweeps forward (South to North) keeping track of the
* monotones and reordering degenerates (monotone ordering in the x-direction
* and sweep line ordering in the y-direction). The input polygons (montones_)
* is not changed during this process.
* and sweep line ordering in the y-direction). The input polygons
* (monotones_) is not changed during this process.
*/
bool SweepForward() {
// Reversed so that minimum element is at queue.top() / vector.back().
+5 -5
View File
@@ -107,7 +107,7 @@ __host__ __device__ Uint64 SpreadBits3(Uint64 v) {
return v;
}
__host__ __device__ Uint64 SqeezeBits3(Uint64 v) {
__host__ __device__ Uint64 SqueezeBits3(Uint64 v) {
v = v & 0x1249249249249249;
v = (v ^ v >> 2) & 0x10c30c30c30c30c3;
v = (v ^ v >> 4) & 0x100f00f00f00f00f;
@@ -127,9 +127,9 @@ __host__ __device__ Uint64 MortonCode(const glm::ivec4& index) {
__host__ __device__ glm::ivec4 DecodeMorton(Uint64 code) {
glm::ivec4 index;
index.x = SqeezeBits3(code >> 1);
index.y = SqeezeBits3(code >> 2);
index.z = SqeezeBits3(code >> 3);
index.x = SqueezeBits3(code >> 1);
index.y = SqueezeBits3(code >> 2);
index.z = SqueezeBits3(code >> 3);
index.w = code & 0x1u;
return index;
}
@@ -385,7 +385,7 @@ namespace manifold {
* it with a negative value.
* @return Mesh This class does not depend on Manifold, so it just returns a
* Mesh, but it is guaranteed to be manifold and so can always be used as
* input to the Manifold contructor for further operations.
* input to the Manifold constructor for further operations.
*/
template <typename Func>
inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
+72 -13
View File
@@ -20,6 +20,7 @@
#include <glm/gtx/compatibility.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include <limits>
#include <memory>
#include <unordered_map>
#include <vector>
@@ -147,6 +148,22 @@ using SimplePolygon = std::vector<PolyVert>;
using Polygons = std::vector<SimplePolygon>;
/** @} */
/**
* An alternative to Mesh for output suitable for pushing into graphics
* libraries directly.
*/
struct MeshGL {
/// Number of vertices
int NumVert() const { return this->vertPos.size() / 3; };
/// Number of triangles
int NumTri() const { return this->triVerts.size() / 3; };
std::vector<float> vertPos;
std::vector<float> vertNormal;
std::vector<uint32_t> triVerts;
std::vector<float> halfedgeTangent;
};
/**
* The triangle-mesh input and output of this library.
*/
@@ -165,6 +182,47 @@ struct Mesh {
/// as 3 * tri + i, representing the tangent from Mesh.triVerts[tri][i] along
/// the CCW edge. If empty, mesh is faceted.
std::vector<glm::vec4> halfedgeTangent;
Mesh() = default;
Mesh(const std::vector<glm::vec3>& vertPos_,
const std::vector<glm::ivec3>& triVerts_,
const std::vector<glm::vec3>& vertNormal_ = {},
const std::vector<glm::vec4>& halfedgeTangent_ = {})
: vertPos(vertPos_),
triVerts(triVerts_),
vertNormal(vertNormal_),
halfedgeTangent(halfedgeTangent_) {}
Mesh(const MeshGL& in) {
const int numTri = in.NumTri();
const int numVert = in.NumVert();
triVerts.resize(numTri);
vertPos.resize(numVert);
if (!in.vertNormal.empty()) {
vertNormal.resize(numVert);
}
for (int i = 0; i < numVert; ++i) {
vertPos[i] = {in.vertPos[3 * i], in.vertPos[3 * i + 1],
in.vertPos[3 * i + 2]};
if (!in.vertNormal.empty()) {
vertNormal[i] = {in.vertNormal[3 * i], in.vertNormal[3 * i + 1],
in.vertNormal[3 * i + 2]};
}
}
for (int i = 0; i < numTri; ++i) {
triVerts[i] = {in.triVerts[3 * i], in.triVerts[3 * i + 1],
in.triVerts[3 * i + 2]};
if (!in.halfedgeTangent.empty()) {
for (const int j : {0, 1, 2})
halfedgeTangent[3 * i + j] = {
in.halfedgeTangent[4 * (3 * i + j)],
in.halfedgeTangent[4 * (3 * i + j) + 1],
in.halfedgeTangent[4 * (3 * i + j) + 2],
in.halfedgeTangent[4 * (3 * i + j) + 3]};
}
}
}
};
/**
@@ -452,19 +510,6 @@ struct ExecutionParams {
};
#ifdef MANIFOLD_DEBUG
/**
* Print the contents of this vector to standard output. Only exists if compiled
* with MANIFOLD_DEBUG flag.
*/
template <typename T>
void Dump(const std::vector<T>& vec) {
std::cout << "Vec = " << std::endl;
for (int i = 0; i < vec.size(); ++i) {
std::cout << i << ", " << vec[i] << ", " << std::endl;
}
std::cout << std::endl;
}
/** @} */
inline std::ostream& operator<<(std::ostream& stream, const Box& box) {
return stream << "min: " << box.min.x << ", " << box.min.y << ", "
@@ -501,6 +546,20 @@ inline std::ostream& operator<<(std::ostream& stream, const BaryRef& ref) {
<< ", originalID: " << ref.originalID << ", tri: " << ref.tri
<< ", uvw idx: " << ref.vertBary;
}
/**
* Print the contents of this vector to standard output. Only exists if compiled
* with MANIFOLD_DEBUG flag.
*/
template <typename T>
void Dump(const std::vector<T>& vec) {
std::cout << "Vec = " << std::endl;
for (int i = 0; i < vec.size(); ++i) {
std::cout << i << ", " << vec[i] << ", " << std::endl;
}
std::cout << std::endl;
}
/** @} */
#endif
} // namespace manifold
+1 -1
View File
@@ -29,7 +29,7 @@ namespace manifold {
// Vector implementation optimized for managed memory, will perform memory
// prefetching to minimize page faults and use parallel/GPU copy/fill depending
// on data size. This will also handle builds without CUDA or builds with CUDA
// but runned without CUDA GPU properly.
// but run without CUDA GPU properly.
//
// Note that the constructor and resize function will not perform initialization
// if the parameter val is not set. Also, this implementation is a toy
+19 -1
View File
@@ -188,6 +188,24 @@ TEST(Manifold, GetMesh) {
Identical(mesh_out, mesh_out2);
}
TEST(Manifold, GetMeshGL) {
Manifold manifold = Manifold::Sphere(1);
Mesh mesh_out = manifold.GetMesh();
MeshGL meshGL_out = manifold.GetMeshGL();
ASSERT_EQ(meshGL_out.NumVert(), mesh_out.vertPos.size());
ASSERT_EQ(meshGL_out.NumTri(), mesh_out.triVerts.size());
for (int i = 0; i < meshGL_out.NumVert(); ++i) {
for (const int j : {0, 1, 2}) {
ASSERT_EQ(meshGL_out.vertPos[3 * i + j], mesh_out.vertPos[i][j]);
ASSERT_EQ(meshGL_out.vertNormal[3 * i + j], mesh_out.vertNormal[i][j]);
}
}
for (int i = 0; i < meshGL_out.NumTri(); ++i) {
for (const int j : {0, 1, 2})
ASSERT_EQ(meshGL_out.triVerts[3 * i + j], mesh_out.triVerts[i][j]);
}
}
TEST(Manifold, Empty) {
Mesh emptyMesh;
Manifold empty(emptyMesh);
@@ -513,7 +531,7 @@ TEST(Manifold, Precision2) {
* positive is convex and negative is concave. There are two orthogonal
* principal curvatures at any point on a manifold, with one maximum and the
* other minimum. Gaussian curvature is their product, while mean
* curvature is their sum. Here we check our discrete appoximations calculated
* curvature is their sum. Here we check our discrete approximations calculated
* at each vertex against the constant expected values of spheres of different
* radii and at different mesh resolutions.
*/