Compare commits
8
Commits
v2.0.0
...
textureExample
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67a5b7140d | ||
|
|
98a61df840 | ||
|
|
a9dc1550ba | ||
|
|
fd8c381179 | ||
|
|
af9377d6e7 | ||
|
|
50fc5d87de | ||
|
|
ca81951355 | ||
|
|
c59130c754 |
@@ -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
@@ -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
|
||||
Vendored
+9
-3
@@ -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'"
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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 <model-viewer> 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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
Vendored
+45
-22
@@ -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>;
|
||||
Generated
+1705
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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().
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user