Compare commits

..
1 Commits
Author SHA1 Message Date
pca006132 324ff3f065 push to pypi 2023-10-30 17:57:34 +08:00
54 changed files with 3340 additions and 4335 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
with:
path: dist/*.tar.gz
pypi-publish:
pypi-publish-beta:
name: Upload release to PyPI
needs: [build_wheels, make_sdist]
runs-on: ubuntu-latest
+1 -4
View File
@@ -18,7 +18,4 @@ jobs:
exclude: '*/third_party'
extensions: 'h,cpp,js,ts,html'
clangFormatVersion: 12
- uses: psf/black@stable
with:
options: "--check --verbose"
src: "./bindings/python/examples"
+3 -3
View File
@@ -22,7 +22,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get -y update
DEBIAN_FRONTEND=noninteractive sudo apt install -y libglm-dev libgtest-dev libassimp-dev git libtbb-dev libthrust-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
DEBIAN_FRONTEND=noninteractive sudo apt install -y libglm-dev libgtest-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
pip install trimesh
- uses: actions/checkout@v3
with:
@@ -105,8 +105,8 @@ jobs:
# setup emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install 3.1.45
./emsdk activate 3.1.45
./emsdk install latest
./emsdk activate latest
- uses: jwlawson/actions-setup-cmake@v1.12
- name: Build WASM
run: |
-25
View File
@@ -1,25 +0,0 @@
name: publish_npm
on: [release, workflow_dispatch]
jobs:
deploy_github_pages:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Download built examples
uses: dawidd6/action-download-artifact@v2
with:
workflow: manifold.yml
workflow_conclusion: completed
name: wasm
path: ./bindings/wasm/
- name: Publish to npm
run: |
cd ./bindings/wasm/
npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
+5 -7
View File
@@ -1,12 +1,14 @@
[![codecov](https://codecov.io/github/elalish/manifold/branch/master/graph/badge.svg?token=IIA8G5HVS7)](https://codecov.io/github/elalish/manifold)
[![PyPI version](https://badge.fury.io/py/manifold3d.svg)](https://badge.fury.io/py/manifold3d)
[![npm version](https://badge.fury.io/js/manifold-3d.svg)](https://badge.fury.io/js/manifold-3d)
[![twitter](https://img.shields.io/twitter/follow/manifoldcad?style=social&logo=twitter)](https://twitter.com/intent/follow?screen_name=manifoldcad)
## Users
[OpenSCAD](https://openscad.org/), [IFCjs](https://ifcjs.github.io/info/), [Grid.Space](https://grid.space/), and [OCADml](https://github.com/OCADml/OManifold) have all integrated our Manifold geometry kernel! Why? Because its reliability is guaranteed and it's 1,000 times faster than other libraries. See our [usage](https://github.com/elalish/manifold/discussions/340) and [performance](https://github.com/elalish/manifold/discussions/383) discussions for all the latest and to add your own projects & analyses.
For example, here is a log-log plot of Manifold's performance vs. earlier OpenSCAD geometry backends:
<img src="https://elalish.github.io/manifold/samples/models/perfSpheres.png" width="350px"/>
## [ManifoldCAD.org](https://manifoldcad.org)
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app. Our WASM is not multithreaded yet, but it's still quite fast and a good way to test out our Manifold library.
@@ -27,7 +29,7 @@ of this repository may solve the problem.
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm), Python ([manifold3d](https://pypi.org/project/manifold3d/)), and C to make this library more portable and easy to use.
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm), Python, and C to make this library more portable and easy to use.
System Dependencies (note that we will automatically download the dependency if there is no such package on the system):
- [`GLM`](https://github.com/g-truc/glm/): A compact header-only vector library.
@@ -85,10 +87,6 @@ Offline building:
The build instructions used by our CI are in [manifold.yml](https://github.com/elalish/manifold/blob/master/.github/workflows/manifold.yml), which is a good source to check if something goes wrong and for instructions specific to other platforms, like Windows.
### WASM
> Note that we have only tested emscripten version 3.1.45. It is known that
3.1.48 has some issues compiling manifold.
To build the JS WASM library, first install NodeJS and set up emscripten:
(on Mac):
+1 -1
View File
@@ -19,7 +19,7 @@ nanobind_add_module(
manifold3d
NB_STATIC STABLE_ABI LTO
manifold3d.cpp)
target_link_libraries(manifold3d PRIVATE manifold sdf)
target_link_libraries(manifold3d PRIVATE manifold)
target_compile_options(manifold3d PRIVATE ${MANIFOLD_FLAGS} -DMODULE_NAME=manifold3d)
target_compile_features(manifold3d PUBLIC cxx_std_17)
set_target_properties(manifold3d PROPERTIES OUTPUT_NAME "manifold3d")
+26 -42
View File
@@ -20,11 +20,11 @@ from manifold3d import Manifold
INCHES = 25.4
brick_depth = (3 + 5 / 8) * INCHES
brick_height = (2 + 1 / 4) * INCHES
brick_length = (7 + 5 / 8) * INCHES
brick_depth = (3 + 5/8) * INCHES
brick_height = (2 + 1/4) * INCHES
brick_length = (7 + 5/8) * INCHES
mortar_gap = (3 / 8) * INCHES
mortar_gap = (3/8) * INCHES
def brick():
@@ -32,64 +32,48 @@ def brick():
def halfbrick():
return Manifold.cube((brick_length - mortar_gap) / 2, brick_depth, brick_height)
return Manifold.cube((brick_length - mortar_gap)/2, brick_depth, brick_height)
def row(length):
bricks = [
brick().translate((brick_length + mortar_gap) * x, 0, 0) for x in range(length)
]
bricks = [brick().translate((brick_length + mortar_gap)*x, 0, 0)
for x in range(length)]
return Manifold(bricks)
def wall(length, height, alternate=0):
bricks = [
row(length).translate(
((z + alternate) % 2) * (brick_depth + mortar_gap),
0,
(brick_height + mortar_gap) * z,
)
for z in range(height)
]
bricks = [row(length).translate(
((z+alternate) % 2)*(brick_depth+mortar_gap), 0,
(brick_height+mortar_gap)*z) for z in range(height)]
return Manifold(bricks)
def walls(length, width, height):
return Manifold(
[
wall(length, height),
wall(width, height, 1).rotate(0, 0, 90).translate(brick_depth, 0, 0),
wall(length, height, 1).translate(
0, (width) * (brick_length + mortar_gap), 0
),
wall(width, height)
.rotate(0, 0, 90)
.translate((length + 0.5) * (brick_length + mortar_gap) - mortar_gap, 0, 0),
]
)
return Manifold([
wall(length, height),
wall(width, height, 1).rotate(0, 0, 90).translate(brick_depth, 0, 0),
wall(length, height, 1).translate(
0, (width)*(brick_length+mortar_gap), 0),
wall(width, height).rotate(0, 0, 90)
.translate((length+0.5)*(brick_length+mortar_gap) - mortar_gap, 0, 0),
])
def floor(length, width):
results = [walls(length, width, 1)]
if length > 1 and width > 1:
results.append(
floor(length - 1, width - 1).translate(
brick_depth + mortar_gap, brick_depth + mortar_gap, 0
)
)
results.append(floor(length-1, width-1).translate(brick_depth +
mortar_gap, brick_depth+mortar_gap, 0))
if length == 1 and width > 1:
results.append(row(width - 1).rotate(0, 0, 90))
results.append(row(width-1).rotate(0, 0, 90))
if width == 1 and length > 1:
results.append(
row(length - 1).translate(
2 * (brick_depth + mortar_gap), brick_depth + mortar_gap, 0
)
)
results.append(
halfbrick().translate(brick_depth + mortar_gap, brick_depth + mortar_gap, 0)
)
results.append(row(length-1).translate(2*(brick_depth +
mortar_gap), brick_depth+mortar_gap, 0))
results.append(halfbrick().translate(
brick_depth+mortar_gap, brick_depth+mortar_gap, 0))
return Manifold(results)
def run(width=10, length=10, height=10):
return walls(length, width, height) + floor(length, width)
+4 -7
View File
@@ -13,21 +13,18 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
from manifold3d import Manifold
from functools import reduce
# https://gist.github.com/ochafik/2db96400e3c1f73558fcede990b8a355#file-cube-with-half-spheres-dents-scad
def run(n=5, overlap=True):
a = Manifold.cube(n, n, 0.5).translate(-0.5, -0.5, -0.5)
spheres = [
Manifold.sphere(0.45 if overlap else 0.55, 50).translate(i, j, 0)
for i in range(n)
for j in range(n)
]
spheres = [Manifold.sphere(0.45 if overlap else 0.55, 50).translate(i, j, 0)
for i in range(n) for j in range(n)]
spheres = reduce(lambda a, b: a + b, spheres)
return a - spheres
+4 -12
View File
@@ -11,9 +11,7 @@ def run():
polygons = cross_section.to_polygons()
polygon = polygons[0]
if set(polygon) != set(polygon_points):
raise Exception(
f"polygon={polygon} differs from polygon_points={polygon_points}"
)
raise Exception(f"polygon={polygon} differs from polygon_points={polygon_points}")
# extrude a polygon to create a manifold
extruded_polygon = cross_section.extrude(10.0)
@@ -21,23 +19,17 @@ def run():
observed_volume = extruded_polygon.get_volume()
expected_volume = 10.0
if abs(observed_volume - expected_volume) > eps:
raise Exception(
f"observed_volume={observed_volume} differs from expected_volume={expected_volume}"
)
raise Exception(f"observed_volume={observed_volume} differs from expected_volume={expected_volume}")
observed_surface_area = extruded_polygon.get_surface_area()
expected_surface_area = 42.0
if abs(observed_surface_area - expected_surface_area) > eps:
raise Exception(
f"observed_surface_area={observed_surface_area} differs from expected_surface_area={expected_surface_area}"
)
raise Exception(f"observed_surface_area={observed_surface_area} differs from expected_surface_area={expected_surface_area}")
# get bounding box from manifold
observed_bbox = extruded_polygon.bounding_box
expected_bbox = (0.0, 0.0, 0.0, 1.0, 1.0, 10.0)
if observed_bbox != expected_bbox:
raise Exception(
f"observed_bbox={observed_bbox} differs from expected_bbox={expected_bbox}"
)
raise Exception(f"observed_bbox={observed_bbox} differs from expected_bbox={expected_bbox}")
return extruded_polygon
-59
View File
@@ -1,59 +0,0 @@
"""
Copyright 2023 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
https://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.
"""
import math
import numpy as np
from manifold3d import Mesh, Manifold
def gyroid(x, y, z):
xi = x - math.pi / 4.0
yi = y - math.pi / 4.0
zi = z - math.pi / 4.0
return (
math.cos(xi) * math.sin(yi)
+ math.cos(yi) * math.sin(zi)
+ math.cos(zi) * math.sin(xi)
)
def gyroid_levelset(level, period, size, n):
return Manifold.from_mesh(
Mesh.level_set(
gyroid,
[-period, -period, -period, period, period, period],
period / n,
level,
)
).scale(size / period)
def rhombic_dodecahedron(size):
box = Manifold.cube(size * math.sqrt(2.0) * np.array([1, 1, 2]), True)
result = box.rotate(90, 45) ^ box.rotate(90, 45, 90)
return result ^ box.rotate(0, 0, 45)
def gyroid_module(size=20, n=15):
period = math.pi * 2.0
result = (
gyroid_levelset(-0.4, period, size, n) ^ rhombic_dodecahedron(size)
) - gyroid_levelset(0.4, period, size, n)
return result.rotate(-45, 0, 90).translate(0, 0, size / math.sqrt(2.0))
def run(size=20, n=15):
return gyroid_module(size, n)
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -24,9 +24,10 @@ from time import time
if __name__ == "__main__":
current_file = pathlib.Path(__file__)
current_dir = current_file.parent
files = [f.parts[-1][:-3] for f in current_dir.glob("*.py") if f != current_file]
files = [f.parts[-1][:-3]
for f in current_dir.glob('*.py') if f != current_file]
export_models = len(sys.argv) == 2 and sys.argv[-1] == "-e"
export_models = len(sys.argv) == 2 and sys.argv[-1] == '-e'
for f in files:
module = importlib.import_module(f)
@@ -40,10 +41,9 @@ if __name__ == "__main__":
else:
vertices = mesh.vert_properties
colors = None
meshOut = trimesh.Trimesh(
vertices=vertices, faces=mesh.tri_verts, vertex_colors=colors
)
trimesh.exchange.export.export_mesh(meshOut, f"{f}.glb", "glb")
print(f"Exported model to {f}.glb")
meshOut = trimesh.Trimesh(vertices=vertices, faces=mesh.tri_verts,
vertex_colors=colors)
trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb')
print(f'Exported model to {f}.glb')
t1 = time()
print(f"Took {(t1-t0)*1000:.1f}ms for {f}")
print(f'Took {(t1-t0)*1000:.1f}ms for {f}')
-80
View File
@@ -1,80 +0,0 @@
"""
Copyright 2023 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
https://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.
"""
from manifold3d import Manifold, Mesh
import numpy as np
def run():
# 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
height = 10
radius = 30
offset = 20
wiggles = 12
sharpness = 0.8
n = 50
triangles = []
positions = [[-offset, 0, height], [-offset, 0, -height]]
sharpenedEdges = []
delta = np.pi / wiggles
for i in range(2 * wiggles):
theta = (i - wiggles) * delta
amp = 0.5 * height * max(np.cos(0.8 * theta), 0)
positions.append(
[
radius * np.cos(theta),
radius * np.sin(theta),
amp * (1 if i % 2 == 0 else -1),
]
)
j = i + 1
if j == 2 * wiggles:
j = 0
smoothness = 1 - sharpness * np.cos((theta + delta / 2) / 2)
halfedge = 3 * len(triangles) + 1
sharpenedEdges.append((halfedge, smoothness))
triangles.append([0, 2 + i, 2 + j])
halfedge = 3 * len(triangles) + 1
sharpenedEdges.append((halfedge, smoothness))
triangles.append([1, 2 + j, 2 + i])
scallop = Mesh(
tri_verts=np.array(triangles, np.int32),
vert_properties=np.array(positions, np.float32),
)
def colorCurvature(_pos, oldProp):
a = max(0, min(1, oldProp[0] / 3 + 0.5))
b = a * a * (3 - 2 * a)
red = [1, 0, 0]
blue = [0, 0, 1]
return [(1 - b) * blue[i] + b * red[i] for i in range(3)]
return (
Manifold.smooth(scallop, sharpenedEdges)
.refine(n)
.calculate_curvature(-1, 0)
.set_properties(3, colorCurvature)
)
-23
View File
@@ -1,23 +0,0 @@
"""
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
https://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.
"""
from manifold3d import Manifold
def run():
a = Manifold.cube(1.0, 1.0, 1.0)
b = Manifold.cube(1.0, 1.0, 1.0).rotate(45.0, 45.0, 45.0)
return a.split(b)[0]
+12 -13
View File
@@ -4,21 +4,20 @@ import numpy as np
def fractal(holes, hole, w, position, depth, maxDepth):
w /= 3
holes.append(hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]))
holes.append(hole.scale([w, w, 1.0]).translate(
[position[0], position[1], 0.0]))
if depth == maxDepth:
return
offsets = np.array(
[
[-w, -w],
[-w, 0.0],
[-w, w],
[0.0, w],
[w, w],
[w, 0.0],
[w, -w],
[0.0, -w],
]
)
offsets = np.array([
[-w, -w],
[-w, 0.0],
[-w, w],
[0.0, w],
[w, w],
[w, 0.0],
[w, -w],
[0.0, -w],
])
for offset in offsets:
fractal(holes, hole, w, position + offset, depth + 1, maxDepth)
+1 -2
View File
@@ -1,8 +1,7 @@
from manifold3d import Manifold
def run():
# for some reason this causes collider error
obj = Manifold.cube()
obj += Manifold([Manifold.cube()]).rotate(0, 0, 45 + 180)
obj += Manifold([Manifold.cube()]).rotate(0, 0, 45+180)
return obj
+10 -90
View File
@@ -24,7 +24,6 @@
#include "nanobind/stl/optional.h"
#include "nanobind/stl/tuple.h"
#include "nanobind/stl/vector.h"
#include "sdf.h"
template <>
struct nanobind::detail::type_caster<glm::vec3> {
@@ -277,7 +276,7 @@ NB_MODULE(manifold3d, m) {
auto result =
f(std::make_tuple(v.x, v.y, v.z),
nb::ndarray<nb::numpy, const float, nb::c_contig>(
oldProps, {static_cast<unsigned long>(oldNumProp)}));
&oldProps, {static_cast<unsigned long>(oldNumProp)}));
nb::ndarray<float, nb::shape<nb::any>> array;
std::vector<float> vec;
if (nb::try_cast(result, array)) {
@@ -304,28 +303,6 @@ NB_MODULE(manifold3d, m) {
":param numProp: The new number of properties per vertex."
":param propFunc: A function that modifies the properties of a given "
"vertex.")
.def(
"calculate_curvature",
[](Manifold &self, int gaussianIdx, int meanIdx) {
return self.CalculateCurvature(gaussianIdx, meanIdx);
},
nb::arg("gaussian_idx"), nb::arg("mean_idx"),
"Curvature is the inverse of the radius of curvature, and signed "
"such that 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. This approximates them "
"for every vertex and assigns them as vertex properties on the given "
"channels."
"\n\n"
":param gaussianIdx: The property channel index in which to store "
"the Gaussian curvature. An index < 0 will be ignored (stores "
"nothing). The property set will be automatically expanded to "
"include the channel index specified."
":param meanIdx: The property channel index in which to store the "
"mean curvature. An index < 0 will be ignored (stores nothing). The "
"property set will be automatically expanded to include the channel "
"index specified.")
.def(
"refine", [](Manifold &self, int n) { return self.Refine(n); },
nb::arg("n"),
@@ -335,8 +312,8 @@ NB_MODULE(manifold3d, m) {
"immediately collapsed) unless the Mesh/Manifold has "
"halfedgeTangents specified (e.g. from the Smooth() constructor), "
"in which case the new vertices will be moved to the interpolated "
"surface according to their barycentric coordinates."
"\n\n"
"surface according to their barycentric coordinates.\n"
"\n"
":param n: The number of pieces to split every edge into. Must be > "
"1.")
.def(
@@ -420,25 +397,16 @@ NB_MODULE(manifold3d, m) {
"topologically disconnected. If everything is connected, the vector "
"is length one, containing a copy of the original. It is the inverse "
"operation of Compose().")
.def(
"split",
[](Manifold &self, Manifold &cutter) {
auto p = self.Split(cutter);
return nb::make_tuple(p.first, p.second);
},
nb::arg("cutter"),
"Split cuts this manifold in two using the cutter manifold. The "
"first result is the intersection, second is the difference. This "
"is more efficient than doing them separately."
"\n\n"
":param cutter: This is the manifold to cut by.\n")
.def("split", &Manifold::Split,
"Split cuts this manifold in two using the cutter manifold. The "
"first result is the intersection, second is the difference. This "
"is more efficient than doing them separately.")
.def(
"split_by_plane",
[](Manifold &self, Float3 normal, float originOffset) {
auto p = self.SplitByPlane(
return self.SplitByPlane(
{std::get<0>(normal), std::get<1>(normal), std::get<2>(normal)},
originOffset);
return nb::make_tuple(p.first, p.second);
},
nb::arg("normal"), nb::arg("origin_offset"),
"Convenient version of Split() for a half-space."
@@ -483,17 +451,7 @@ NB_MODULE(manifold3d, m) {
"Gets the manifold bounding box as a tuple "
"(xmin, ymin, zmin, xmax, ymax, zmax).")
.def_static(
"smooth",
[](const MeshGL &mesh,
const std::vector<std::tuple<int, float>> &sharpenedEdges = {}) {
std::vector<Smoothness> vec(sharpenedEdges.size());
for (int i = 0; i < sharpenedEdges.size(); i++) {
vec[i] = {std::get<0>(sharpenedEdges[i]),
std::get<1>(sharpenedEdges[i])};
}
return Manifold::Smooth(mesh, vec);
},
nb::arg("mesh"), nb::arg("sharpened_edges"),
"smooth", [](const Mesh &mesh) { return Manifold::Smooth(mesh); },
"Constructs a smooth version of the input mesh by creating tangents; "
"this method will throw if you have supplied tangents with your "
"mesh already. The actual triangle resolution is unchanged; use the "
@@ -727,45 +685,7 @@ NB_MODULE(manifold3d, m) {
.def_ro("merge_to_vert", &MeshGL::mergeToVert)
.def_ro("run_index", &MeshGL::runIndex)
.def_ro("run_original_id", &MeshGL::runOriginalID)
.def_ro("face_id", &MeshGL::faceID)
.def_static(
"level_set",
[](const std::function<float(float, float, float)> &f,
std::vector<float> bounds, float edgeLength, float level = 0.0) {
// Same format as Manifold.bounding_box
Box bound = {glm::vec3(bounds[0], bounds[1], bounds[2]),
glm::vec3(bounds[3], bounds[4], bounds[5])};
std::function<float(glm::vec3)> cppToPython =
[&f](glm::vec3 v) { return f(v.x, v.y, v.z); };
Mesh result =
LevelSet(cppToPython, bound,
edgeLength, level, false);
return MeshGL(result);
},
nb::arg("f"), nb::arg("bounds"), nb::arg("edgeLength"),
nb::arg("level") = 0.0,
"Constructs a level-set Mesh from the input Signed-Distance Function "
"(SDF) This uses a form of Marching Tetrahedra (akin to Marching "
"Cubes, but better for manifoldness). Instead of using a cubic grid, "
"it uses a body-centered cubic grid (two shifted cubic grids). This "
"means if your function's interior exceeds the given bounds, you "
"will see a kind of egg-crate shape closing off the manifold, which "
"is due to the underlying grid."
"\n\n"
":param f: The signed-distance functor, containing this function "
"signature: `def sdf(xyz : tuple) -> float:`, which returns the "
"signed distance of a given point in R^3. Positive values are "
"inside, negative outside."
":param bounds: An axis-aligned box that defines the extent of the "
"grid."
":param edgeLength: Approximate maximum edge length of the triangles "
"in the final result. This affects grid spacing, and hence has a "
"strong effect on performance."
":param level: You can inset your Mesh by using a positive value, or "
"outset it with a negative value."
":return Mesh: This mesh is guaranteed to be manifold."
"Use Manifold.from_mesh(mesh) to create a Manifold");
.def_ro("face_id", &MeshGL::faceID);
nb::enum_<Manifold::Error>(m, "Error")
.value("NoError", Manifold::Error::NoError)
+1 -9
View File
@@ -32,21 +32,13 @@ add_custom_target(js_deps ALL
DEPENDS manifoldjs
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts)
# copy WASM build back here for publishing to npm
add_custom_command(
TARGET js_deps POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
${CMAKE_CURRENT_SOURCE_DIR})
# copy WASM build and TS declarations for Vite to package into ManifoldCAD.org
add_custom_command(
TARGET js_deps POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/manifold*
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
${CMAKE_CURRENT_SOURCE_DIR}/examples/built/)
# copy TS declarations to public so they can be accessed by our editor
add_custom_command(
TARGET js_deps POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
+285
View File
@@ -0,0 +1,285 @@
// Copyright 2023 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.
import {Accessor} from '@gltf-transform/core';
import {EXTManifold} from './manifold-gltf.js';
export const attributeDefs = {
'POSITION': {type: Accessor.Type.VEC3, components: 3},
'NORMAL': {type: Accessor.Type.VEC3, components: 3},
'TANGENT': {type: Accessor.Type.VEC4, components: 4},
'TEXCOORD_0': {type: Accessor.Type.VEC2, components: 2},
'TEXCOORD_1': {type: Accessor.Type.VEC2, components: 2},
'COLOR_0': {type: Accessor.Type.VEC3, components: 3},
'JOINTS_0': {type: Accessor.Type.VEC4, components: 4},
'WEIGHTS_0': {type: Accessor.Type.VEC4, components: 4},
'SKIP_1': {type: null, components: 1},
'SKIP_2': {type: null, components: 2},
'SKIP_3': {type: null, components: 3},
'SKIP_4': {type: null, components: 4},
};
export function setupIO(io) {
return io.registerExtensions([EXTManifold]);
}
function readPrimitive(primitive, numProp, attributes) {
const position = primitive.getAttribute('POSITION');
const numVert = position.getCount();
const vertProperties = [];
let offset = 0;
for (const attribute of attributes) {
const size = attributeDefs[attribute].components;
if (attribute === 'SKIP') {
offset += size;
continue;
}
const accessor = primitive.getAttribute(attribute);
if (accessor) {
const array = accessor.getArray();
for (let i = 0; i < numVert; ++i) {
for (let j = 0; j < size; ++j) {
vertProperties[numProp * i + offset + j] = array[i * size + j];
}
}
} else {
for (let i = 0; i < numVert; ++i) {
for (let j = 0; j < size; ++j) {
vertProperties[numProp * i + offset + j] = 0;
}
}
}
offset += size;
}
return vertProperties;
}
export function readMesh(mesh, attributes, materials) {
const primitives = mesh.listPrimitives();
if (primitives.length === 0) {
return {};
}
if (attributes.length === 0) {
const attributeSet = new Set();
for (const primitive of primitives) {
const semantics = primitive.listSemantics();
for (const semantic of semantics) {
attributeSet.add(semantic);
}
}
for (const semantic in attributeDefs) {
if (attributeSet.has(semantic)) {
attributes.push(semantic);
attributeSet.delete(semantic);
}
}
for (const semantic of attributeSet.keys()) {
attributes.push(semantic);
}
}
if (attributes.length < 1 || attributes[0] !== 'POSITION')
throw new Error('First attribute must be "POSITION".');
const numProp = attributes.map((def) => attributeDefs[def].components)
.reduce((a, b) => a + b);
const manifoldPrimitive = mesh.getExtension('EXT_manifold');
let vertPropArray = [];
let triVertArray = [];
const runIndexArray = [0];
const mergeFromVert = [];
const mergeToVert = [];
if (manifoldPrimitive != null) {
vertPropArray = readPrimitive(primitives[0], numProp, attributes);
for (const primitive of primitives) {
triVertArray = [...triVertArray, ...primitive.getIndices().getArray()];
runIndexArray.push(triVertArray.length);
materials.push(primitive.getMaterial());
}
const mergeTriVert = manifoldPrimitive.getMergeIndices()?.getArray() ?? [];
const mergeTo = manifoldPrimitive.getMergeValues()?.getArray() ?? [];
const vert2merge = new Map();
for (const [i, idx] of mergeTriVert.entries()) {
vert2merge.set(triVertArray[idx], mergeTo[i]);
}
for (const [from, to] of vert2merge.entries()) {
mergeFromVert.push(from);
mergeToVert.push(to);
}
} else {
for (const primitive of primitives) {
const numVert = vertPropArray.length / numProp;
vertPropArray =
[...vertPropArray, ...readPrimitive(primitive, numProp, attributes)];
triVertArray = [
...triVertArray,
...primitive.getIndices().getArray().map((i) => i + numVert)
];
runIndexArray.push(triVertArray.length);
materials.push(primitive.getMaterial());
}
}
const vertProperties = new Float32Array(vertPropArray);
const triVerts = new Uint32Array(triVertArray);
const runIndex = new Uint32Array(runIndexArray);
return {
numProp,
triVerts,
vertProperties,
runIndex,
mergeFromVert,
mergeToVert
};
}
export function writeMesh(doc, manifoldMesh, attributes, materials) {
if (doc.getRoot().listBuffers().length === 0) {
doc.createBuffer();
}
const buffer = doc.getRoot().listBuffers()[0];
const manifoldExtension = doc.createExtension(EXTManifold);
const attributeUnion = [];
for (const matAttributes of attributes) {
matAttributes.forEach((attribute, i) => {
if (i >= attributeUnion.length) {
attributeUnion.push(attribute);
} else {
const size = attributeDefs[attribute].components;
const unionSize = attributeDefs[attributeUnion[i]].components;
if (size != unionSize) {
throw new Error(
'Attribute sizes do not correspond: ', attribute, ' and ',
attributeUnion[i]);
}
if (attributeDefs[attributeUnion[i]].type == null) {
attributeUnion[i] = attribute;
}
}
});
}
if (attributeUnion.length < 1 || attributeUnion[0] !== 'POSITION')
throw new Error('First attribute must be "POSITION".');
const mesh = doc.createMesh();
const numPrimitive = manifoldMesh.runIndex.length - 1;
for (let run = 0; run < numPrimitive; ++run) {
const indices = doc.createAccessor('indices')
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array());
const primitive = doc.createPrimitive().setIndices(indices);
const material = materials[run];
if (material) {
primitive.setMaterial(material);
}
mesh.addPrimitive(primitive);
}
const numVert = manifoldMesh.numVert;
const numProp = manifoldMesh.numProp;
let offset = 0;
attributeUnion.forEach((attribute, aIdx) => {
if (attribute === 'SKIP') {
++offset;
return;
}
const def = attributeDefs[attribute];
if (def == null)
throw new Error(attribute + ' is not a recognized attribute.');
const n = def.components;
if (offset + n > numProp) throw new Error('Too many attribute channels.');
const array = new Float32Array(n * numVert);
for (let v = 0; v < numVert; ++v) {
for (let i = 0; i < n; ++i) {
array[n * v + i] =
manifoldMesh.vertProperties[numProp * v + offset + i];
}
}
const accessor =
doc.createAccessor().setBuffer(buffer).setType(def.type).setArray(
array);
mesh.listPrimitives().forEach((primitive, pIdx) => {
if (attributes[pIdx].length > aIdx &&
attributeDefs[attributes[pIdx][aIdx]].type != null) {
primitive.setAttribute(attribute, accessor);
}
});
offset += n;
});
const manifoldPrimitive = manifoldExtension.createManifoldPrimitive();
mesh.setExtension('EXT_manifold', manifoldPrimitive);
const indices = doc.createAccessor('indices')
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(manifoldMesh.triVerts);
manifoldPrimitive.setIndices(indices);
manifoldPrimitive.setRunIndex(manifoldMesh.runIndex);
const vert2merge = [...Array(manifoldMesh.numVert).keys()];
for (const [i, from] of manifoldMesh.mergeFromVert.entries()) {
vert2merge[from] = manifoldMesh.mergeToVert[i];
}
const ind = [];
const val = [];
for (const [i, vert] of manifoldMesh.triVerts.entries()) {
const newVert = vert2merge[vert];
if (vert !== newVert) {
ind.push(i);
val.push(newVert);
}
}
const indicesAccessor = doc.createAccessor()
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array(ind));
const valuesAccessor = doc.createAccessor()
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array(val));
manifoldPrimitive.setMerge(indicesAccessor, valuesAccessor);
return mesh;
}
export function disposeMesh(mesh) {
if (!mesh) return;
const primitives = mesh.listPrimitives();
for (const primitive of primitives) {
primitive.getIndices()?.dispose();
for (const accessor of primitive.listAttributes()) {
accessor.dispose();
}
}
mesh.dispose();
}
export async function loadTexture(texture, uri) {
const response = await fetch(uri);
const blob = await response.blob();
texture.setMimeType(blob.type);
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
}
-424
View File
@@ -1,424 +0,0 @@
// Copyright 2023 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.
import {Accessor, Document, Material, Mesh, Primitive, Texture, WebIO} from '@gltf-transform/core';
import {EXTManifold, ManifoldPrimitive} from './manifold-gltf';
import {Mesh as ManifoldMesh, MeshOptions} from './public/manifold-encapsulated-types';
export const attributeDefs = {
'POSITION': {type: Accessor.Type.VEC3, components: 3},
'NORMAL': {type: Accessor.Type.VEC3, components: 3},
'TANGENT': {type: Accessor.Type.VEC4, components: 4},
'TEXCOORD_0': {type: Accessor.Type.VEC2, components: 2},
'TEXCOORD_1': {type: Accessor.Type.VEC2, components: 2},
'COLOR_0': {type: Accessor.Type.VEC3, components: 3},
'JOINTS_0': {type: Accessor.Type.VEC4, components: 4},
'WEIGHTS_0': {type: Accessor.Type.VEC4, components: 4},
'SKIP_1': {type: null, components: 1},
'SKIP_2': {type: null, components: 2},
'SKIP_3': {type: null, components: 3},
'SKIP_4': {type: null, components: 4},
};
export type Attribute = keyof(typeof attributeDefs);
export interface Properties {
material: Material;
attributes: Attribute[];
}
/**
* Call this first to register the manifold extension so that readMesh and
* writeMesh will work.
*/
export function setupIO(io: WebIO) {
return io.registerExtensions([EXTManifold]);
}
/**
* Read an input mesh into Manifold-compatible data structures, whether it
* contains the EXT_manifold extension or not.
*
* @param mesh The Mesh to read.
* @param attributes An array of attributes representing the order of desired
* properties returned in the vertProperties array of the output mesh. If
* omitted, this will be populated with the union of all attributes defined
* in the primitives of the input mesh. If present, the first entry must be
* 'POSITION', and any attributes in the primitives that are not included in
* this list will be ignored, while those in the list but not defined in a
* primitive will be populated with zeros.
* @returns The returned mesh is suitable for initializing a Manifold or Mesh of
* the Manifold library if desired. See Manifold documentation if you prefer
* to use these GL arrays in a different library. The runProperties array
* gives the Material and attributes list associated with each triangle run,
* which in turn corresponds to a primitive of the input mesh. These
* attributes are the intersection of the attributes present on the
* primitive and those requested in the attributes input.
*/
export function readMesh(mesh: Mesh, attributes: Attribute[] = []):
{mesh: MeshOptions, runProperties: Properties[]}|null {
const primitives = mesh.listPrimitives();
if (primitives.length === 0) {
return null;
}
if (attributes.length === 0) {
const attributeSet = new Set<Attribute>();
for (const primitive of primitives) {
const semantics = primitive.listSemantics() as Attribute[];
for (const semantic of semantics) {
attributeSet.add(semantic);
}
}
let semantic: Attribute;
for (semantic in attributeDefs) {
if (attributeSet.has(semantic)) {
attributes.push(semantic);
attributeSet.delete(semantic);
}
}
for (const semantic of attributeSet.keys()) {
attributes.push(semantic);
}
}
if (attributes.length < 1 || attributes[0] !== 'POSITION')
throw new Error('First attribute must be "POSITION".');
let numProp = 0;
const attributeOffsets = attributes.map((numProp = 0, def => {
const last = numProp;
numProp += attributeDefs[def].components;
return last;
}));
const manifoldPrimitive =
mesh.getExtension('EXT_manifold') as ManifoldPrimitive;
let vertPropArray: number[] = [];
let triVertArray: number[] = [];
const runIndexArray = [0];
const mergeFromVertArray = [];
const mergeToVertArray = [];
const runProperties: Properties[] = [];
if (manifoldPrimitive != null) {
const numVert = primitives[0].getAttribute('POSITION')!.getCount();
const foundAttribute = attributes.map((a) => attributeDefs[a].type == null);
vertPropArray = new Array<number>(numProp * numVert);
for (const primitive of primitives) {
const indices = primitive.getIndices();
if (!indices) {
console.log('Skipping non-indexed primitive ', primitive.getName());
continue;
}
const attributesIn = primitive.listSemantics() as Attribute[];
attributes.forEach((attributeOut, idx) => {
if (foundAttribute[idx]) {
return;
}
for (const attributeIn of attributesIn) {
if (attributeIn === attributeOut) {
foundAttribute[idx] = true;
const accessor = primitive.getAttribute(attributeIn)!;
writeProperties(
vertPropArray, accessor, numProp, attributeOffsets[idx]);
}
}
});
triVertArray = [...triVertArray, ...indices.getArray()!];
runIndexArray.push(triVertArray.length);
runProperties.push({
material: primitive.getMaterial()!,
attributes: attributesIn.filter(b => attributes.some(a => a == b))
});
}
const mergeTriVert = manifoldPrimitive.getMergeIndices()?.getArray() ?? [];
const mergeTo = manifoldPrimitive.getMergeValues()?.getArray() ?? [];
const vert2merge = new Map();
for (const [i, idx] of mergeTriVert.entries()) {
vert2merge.set(triVertArray[idx], mergeTo[i]);
}
for (const [from, to] of vert2merge.entries()) {
mergeFromVertArray.push(from);
mergeToVertArray.push(to);
}
} else {
for (const primitive of primitives) {
const indices = primitive.getIndices();
if (!indices) {
console.log('Skipping non-indexed primitive ', primitive.getName());
continue;
}
const numVert = vertPropArray.length / numProp;
vertPropArray =
[...vertPropArray, ...readPrimitive(primitive, numProp, attributes)];
triVertArray =
[...triVertArray, ...indices.getArray()!.map((i) => i + numVert)];
runIndexArray.push(triVertArray.length);
const attributesIn = primitive.listSemantics() as Attribute[];
runProperties.push({
material: primitive.getMaterial()!,
attributes: attributesIn.filter(b => attributes.some(a => a == b))
});
}
}
const vertProperties = new Float32Array(vertPropArray);
const triVerts = new Uint32Array(triVertArray);
const runIndex = new Uint32Array(runIndexArray);
const mergeFromVert = new Uint32Array(mergeFromVertArray);
const mergeToVert = new Uint32Array(mergeToVertArray);
const meshOut =
{numProp, triVerts, vertProperties, runIndex, mergeFromVert, mergeToVert};
return {mesh: meshOut, runProperties};
}
/**
* Write a Manifold Mesh into a glTF Mesh object, using the EXT_manifold
* extension to allow for lossless roundtrip of the manifold mesh through the
* glTF file.
*
* @param doc The glTF Document to which this Mesh will be added.
* @param manifoldMesh The Manifold Mesh to convert to glTF.
* @param id2properties A map from originalID to Properties that include the
* glTF Material and the set of attributes to output. All triangle runs with
* the same originalID will be combined into a single output primitive. Any
* originalIDs not found in the map will have the glTF default material and
* no attributes beyond 'POSITION'. Each attributes array must correspond to
* the manifoldMesh vertProperties, thus the first attribute must always be
* 'POSITION'. Any properties that should not be output for a given
* primitive must use the 'SKIP_*' attributes.
* @returns The glTF Mesh to add to the Document.
*/
export function writeMesh(
doc: Document, manifoldMesh: ManifoldMesh,
id2properties: Map<number, Properties>): Mesh {
if (doc.getRoot().listBuffers().length === 0) {
doc.createBuffer();
}
const buffer = doc.getRoot().listBuffers()[0];
const manifoldExtension = doc.createExtension(EXTManifold);
const mesh = doc.createMesh();
const runIndex = [];
const attributeUnion: Attribute[] = [];
const primitive2attributes = new Map<Primitive, Attribute[]>();
const numRun = manifoldMesh.runIndex.length - 1;
let lastID = -1;
for (let run = 0; run < numRun; ++run) {
const id = manifoldMesh.runOriginalID[run];
if (id == lastID) {
continue;
}
lastID = id;
runIndex.push(manifoldMesh.runIndex[run]);
const indices = doc.createAccessor('primitive indices of ID ' + id)
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array(1));
const primitive = doc.createPrimitive().setIndices(indices);
const properties = id2properties.get(id);
if (properties) {
const {material, attributes} = properties;
if (attributes.length < 1 || attributes[0] !== 'POSITION')
throw new Error('First attribute must be "POSITION".');
primitive.setMaterial(material);
primitive2attributes.set(primitive, attributes);
properties.attributes.forEach((attribute, i) => {
if (i >= attributeUnion.length) {
attributeUnion.push(attribute);
} else {
const size = attributeDefs[attribute].components;
const unionSize = attributeDefs[attributeUnion[i]].components;
if (size != unionSize) {
throw new Error(
'Attribute sizes do not correspond: ' + attribute + ' and ' +
attributeUnion[i]);
}
if (attributeDefs[attributeUnion[i]].type == null) {
attributeUnion[i] = attribute;
}
}
});
} else {
primitive2attributes.set(primitive, ['POSITION']);
}
mesh.addPrimitive(primitive);
}
runIndex.push(manifoldMesh.runIndex[numRun]);
const numVert = manifoldMesh.numVert;
const numProp = manifoldMesh.numProp;
let offset = 0;
attributeUnion.forEach((attribute, aIdx) => {
const def = attributeDefs[attribute];
if (def == null)
throw new Error(attribute + ' is not a recognized attribute.');
if (def.type == null) {
++offset;
return;
}
const n = def.components;
if (offset + n > numProp) throw new Error('Too many attribute channels.');
const array = new Float32Array(n * numVert);
for (let v = 0; v < numVert; ++v) {
for (let i = 0; i < n; ++i) {
let x = manifoldMesh.vertProperties[numProp * v + offset + i];
if (attribute == 'COLOR_0') {
x = Math.max(0, Math.min(1, x));
}
array[n * v + i] = x;
}
}
const accessor = doc.createAccessor(attribute)
.setBuffer(buffer)
.setType(def.type)
.setArray(array);
for (const primitive of mesh.listPrimitives()) {
const attributes = primitive2attributes.get(primitive)!;
if (attributes.length > aIdx &&
attributeDefs[attributes[aIdx]].type != null) {
primitive.setAttribute(attribute, accessor);
}
}
offset += n;
});
const manifoldPrimitive = manifoldExtension.createManifoldPrimitive();
mesh.setExtension('EXT_manifold', manifoldPrimitive);
const indices = doc.createAccessor('manifold indices')
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(manifoldMesh.triVerts);
manifoldPrimitive.setIndices(indices);
manifoldPrimitive.setRunIndex(runIndex);
const vert2merge = [...Array(manifoldMesh.numVert).keys()];
const ind = [];
const val = [];
if (manifoldMesh.mergeFromVert && manifoldMesh.mergeToVert) {
for (const [i, from] of manifoldMesh.mergeFromVert.entries()) {
vert2merge[from] = manifoldMesh.mergeToVert[i];
}
for (const [i, vert] of manifoldMesh.triVerts.entries()) {
const newVert = vert2merge[vert];
if (vert !== newVert) {
ind.push(i);
val.push(newVert);
}
}
}
if (ind.length > 0) {
const indicesAccessor = doc.createAccessor('merge from')
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array(ind));
const valuesAccessor = doc.createAccessor('merge to')
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array(val));
manifoldPrimitive.setMerge(indicesAccessor, valuesAccessor);
}
return mesh;
}
/**
* Helper function to dispose of a Mesh, useful when replacing an existing Mesh
* with one from writeMesh.
*/
export function disposeMesh(mesh: Mesh) {
if (!mesh) return;
const primitives = mesh.listPrimitives();
for (const primitive of primitives) {
primitive.getIndices()?.dispose();
for (const accessor of primitive.listAttributes()) {
accessor.dispose();
}
}
const manifoldPrimitive =
mesh.getExtension('EXT_manifold') as ManifoldPrimitive;
if (manifoldPrimitive) {
manifoldPrimitive.getIndices()?.dispose();
manifoldPrimitive.getMergeIndices()?.dispose();
manifoldPrimitive.getMergeValues()?.dispose();
}
mesh.dispose();
}
/**
* Helper function to download an image and apply it to the given texture.
*
* @param texture The texture to update
* @param uri The location of the image to download
*/
export async function loadTexture(texture: Texture, uri: string) {
const response = await fetch(uri);
const blob = await response.blob();
texture.setMimeType(blob.type);
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
}
function writeProperties(
vertProperties: number[], accessor: Accessor, numProp: number,
offset: number) {
const array = accessor.getArray()!;
const size = accessor.getElementSize();
const numVert = accessor.getCount();
for (let i = 0; i < numVert; ++i) {
for (let j = 0; j < size; ++j) {
vertProperties[numProp * i + offset + j] = array[i * size + j];
}
}
}
function readPrimitive(
primitive: Primitive, numProp: number, attributes: Attribute[]) {
const vertProperties: number[] = [];
let offset = 0;
for (const attribute of attributes) {
const size = attributeDefs[attribute].components;
if (attributeDefs[attribute].type == null) {
offset += size;
continue;
}
const accessor = primitive.getAttribute(attribute);
if (accessor) {
writeProperties(vertProperties, accessor, numProp, offset);
}
offset += size;
}
return vertProperties;
}
+3 -3
View File
@@ -40,7 +40,7 @@
<div class="page">
<div class="container header" style="background-color: #A288B3;">
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD </p>
<div class="margin" id="file-margin">
<div class="margin" id="file-margin" >
<button type="button" id="file" class="blue item"><b class="uparrow"></b>&nbsp;&nbsp;<span
id="current">Intro</span></button>
<div class="dropdown" id="fileDropdown">
@@ -84,6 +84,6 @@
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
crossorigin="anonymous" referrerpolicy="no-referrer">
<script type="module" src="editor.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.3.0/model-viewer.min.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.1.1/model-viewer.min.js"></script>
</html>
</html>
+15 -14
View File
@@ -59,13 +59,13 @@
<span id="poster" slot="poster">Drop a GLB here</span>
</model-viewer>
</body>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.3.0/model-viewer.min.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
<script type="module">
import { WebIO } from '@gltf-transform/core';
import { prune } from '@gltf-transform/functions';
import { KHRONOS_EXTENSIONS } from '@gltf-transform/extensions';
import { SimpleDropzone } from 'simple-dropzone';
import { readMesh, writeMesh, setupIO, disposeMesh } from './gltf-io';
import { readMesh, writeMesh, setupIO } from './gltf-io.js';
import Module from './built/manifold.js';
const io = setupIO(new WebIO());
@@ -147,30 +147,31 @@
const docIn = await io.read(url);
const nodes = docIn.getRoot().listNodes();
for (const node of nodes) {
const attributes = [];
const mesh = node.getMesh();
if (!mesh) {
continue;
}
const tmp = readMesh(mesh);
tmp.mesh.runOriginalID = [];
const id2properties = new Map();
const numID = tmp.runProperties.length;
const firstID = Manifold.reserveIDs(numID);
for (let i = 0; i < numID; ++i) {
tmp.mesh.runOriginalID.push(firstID + i);
id2properties.set(firstID + i, tmp.runProperties[i]);
const materials = [];
const tmpMesh = readMesh(mesh, attributes, materials);
tmpMesh.runOriginalID = [];
const firstID = Manifold.reserveIDs(materials.length);
for (let i = 0; i < materials.length; ++i) {
tmpMesh.runOriginalID.push(firstID + i);
}
const manifoldMesh = new Mesh(tmp.mesh);
disposeMesh(mesh);
const manifoldMesh = new Mesh(tmpMesh);
mesh.dispose();
const matAttributes = [];
materials.forEach(() => matAttributes.push(attributes));
manifoldMesh.merge();
try {
// Test manifoldness - will throw if not.
const manifold = Manifold(manifoldMesh);
node.setMesh(writeMesh(docIn, manifold.getMesh(), id2properties));
node.setMesh(writeMesh(docIn, manifold.getMesh(), matAttributes, materials));
manifold.delete();
anyManifold = true;
} catch (e) {
@@ -12,32 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Accessor, Extension, ExtensionProperty, GLTF, IProperty, PropertyType, ReaderContext, WriterContext} from '@gltf-transform/core';
import {Extension, ExtensionProperty, PropertyType} from '@gltf-transform/core';
const NAME = 'EXT_manifold';
const MERGE = 'MERGE';
interface IManifoldPrimitive extends IProperty {
mergeIndices: Accessor;
mergeValues: Accessor;
indices: Accessor;
runIndex: number[]|Uint32Array;
}
interface ManifoldDef {
manifoldPrimitive: GLTF.IMeshPrimitive;
mergeIndices: number;
mergeValues: number;
}
export class ManifoldPrimitive extends ExtensionProperty<IManifoldPrimitive> {
export class ManifoldPrimitive extends ExtensionProperty {
static EXTENSION_NAME = NAME;
public declare extensionName: typeof NAME;
public declare propertyType: 'ManifoldPrimitive';
public declare parentTypes: [PropertyType.MESH];
init() {
ManifoldPrimitive.EXTENSION_NAME = NAME;
this.EXTENSION_NAME = NAME;
this.propertyType = 'ManifoldPrimitive';
this.parentTypes = [PropertyType.MESH];
}
@@ -56,7 +39,7 @@ export class ManifoldPrimitive extends ExtensionProperty<IManifoldPrimitive> {
return this.getRef('mergeValues');
}
setMerge(indicesAccessor: Accessor, valuesAccessor: Accessor) {
setMerge(indicesAccessor, valuesAccessor) {
if (indicesAccessor.getCount() !== valuesAccessor.getCount())
throw new Error('merge vectors must be the same length.');
this.setRef('mergeIndices', indicesAccessor);
@@ -67,29 +50,28 @@ export class ManifoldPrimitive extends ExtensionProperty<IManifoldPrimitive> {
return this.get('runIndex');
}
setRunIndex(runIndex: number[]|Uint32Array) {
setRunIndex(runIndex) {
return this.set('runIndex', runIndex);
}
setIndices(indices: Accessor) {
setIndices(indices) {
return this.setRef('indices', indices);
}
getIndices() {
return this.getRef('indices')!;
return this.getRef('indices');
}
}
export class EXTManifold extends Extension {
extensionName = NAME;
prewriteTypes = [PropertyType.ACCESSOR];
static EXTENSION_NAME = NAME;
createManifoldPrimitive() {
return new ManifoldPrimitive(this.document.getGraph());
}
read(context: ReaderContext) {
read(context) {
const {json} = context.jsonDoc;
const meshDefs = json.meshes || [];
@@ -100,27 +82,20 @@ export class EXTManifold extends Extension {
const manifoldPrimitive = this.createManifoldPrimitive();
mesh.setExtension(NAME, manifoldPrimitive);
const manifoldDef = meshDef.extensions[NAME] as ManifoldDef;
const manifoldDef = meshDef.extensions[NAME];
if (manifoldDef.manifoldPrimitive) {
let count = 0;
const runIndex = [];
runIndex.push(count);
for (const primitive of mesh.listPrimitives()) {
const indices = primitive.getIndices();
if (!indices) {
console.log('Skipping non-indexed primitive ', primitive.getName());
continue;
}
count += indices.getCount();
count += primitive.getIndices().getCount();
runIndex.push(count);
}
manifoldPrimitive.setRunIndex(runIndex);
manifoldPrimitive.setIndices(
context.accessors[manifoldDef.manifoldPrimitive.indices!]);
}
if (manifoldDef.mergeIndices != null && manifoldDef.mergeValues != null) {
if (manifoldDef.mergeIndices && manifoldDef.mergeValues) {
manifoldPrimitive.setMerge(
context.accessors[manifoldDef.mergeIndices],
context.accessors[manifoldDef.mergeValues]);
@@ -130,34 +105,15 @@ export class EXTManifold extends Extension {
return this;
}
prewrite(context: WriterContext) {
this.document.getRoot().listMeshes().forEach((mesh) => {
const manifoldPrimitive = mesh.getExtension(NAME) as ManifoldPrimitive;
if (!manifoldPrimitive) return;
const indices = manifoldPrimitive.getIndices();
context.addAccessorToUsageGroup(
indices, WriterContext.BufferViewUsage.ELEMENT_ARRAY_BUFFER);
const mergeFrom = manifoldPrimitive.getMergeIndices();
const mergeTo = manifoldPrimitive.getMergeValues();
if (!mergeFrom || !mergeTo) return;
context.addAccessorToUsageGroup(mergeFrom, MERGE);
context.addAccessorToUsageGroup(mergeTo, MERGE);
});
return this;
}
write(context: WriterContext) {
write(context) {
const {json} = context.jsonDoc;
this.document.getRoot().listMeshes().forEach((mesh) => {
const manifoldPrimitive = mesh.getExtension(NAME) as ManifoldPrimitive;
const manifoldPrimitive = mesh.getExtension(NAME);
if (!manifoldPrimitive) return;
const meshIndex = context.meshIndexMap.get(mesh)!;
const meshDef = json.meshes![meshIndex];
const meshIndex = context.meshIndexMap.get(mesh);
const meshDef = json.meshes[meshIndex];
const runIndex = manifoldPrimitive.getRunIndex();
const numPrimitive = runIndex.length - 1;
@@ -168,20 +124,20 @@ export class EXTManifold extends Extension {
}
const mergeIndicesIndex =
context.accessorIndexMap.get(manifoldPrimitive.getMergeIndices()!)!;
context.accessorIndexMap.get(manifoldPrimitive.getMergeIndices());
const mergeValuesIndex =
context.accessorIndexMap.get(manifoldPrimitive.getMergeValues()!)!;
const mergeIndices = json.accessors![mergeIndicesIndex];
const mergeValues = json.accessors![mergeValuesIndex];
context.accessorIndexMap.get(manifoldPrimitive.getMergeValues());
const mergeIndices = json.accessors[mergeIndicesIndex];
const mergeValues = json.accessors[mergeValuesIndex];
const existingPrimitive = meshDef.primitives[0];
const primitive = {
indices: context.accessorIndexMap.get(manifoldPrimitive.getIndices())!,
indices: context.accessorIndexMap.get(manifoldPrimitive.getIndices()),
mode: existingPrimitive.mode,
attributes: {'POSITION': existingPrimitive.attributes['POSITION']}
};
const indices = json.accessors![primitive.indices];
const indices = json.accessors[primitive.indices];
if (!indices) {
return;
}
@@ -190,21 +146,21 @@ export class EXTManifold extends Extension {
indices.sparse = {
count: mergeIndices.count,
indices: {
bufferView: mergeIndices.bufferView!,
bufferView: mergeIndices.bufferView,
byteOffset: mergeIndices.byteOffset,
componentType: mergeIndices.componentType
},
values: {
bufferView: mergeValues.bufferView!,
bufferView: mergeValues.bufferView,
byteOffset: mergeValues.byteOffset,
}
};
}
for (let i = 0; i < numPrimitive; ++i) {
const accessor = json.accessors![meshDef.primitives[i].indices!];
const accessor = json.accessors[meshDef.primitives[i].indices];
accessor.bufferView = indices.bufferView;
accessor.byteOffset = indices.byteOffset! + 4 * runIndex[i];
accessor.byteOffset = indices.byteOffset + 4 * runIndex[i];
accessor.count = runIndex[i + 1] - runIndex[i];
}
+21 -17
View File
@@ -48,11 +48,11 @@
<button id="download">Download manifold GLB</button>
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
</body>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.3.0/model-viewer.min.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
<script type="module">
import { Document, WebIO } from '@gltf-transform/core';
import { clearNodeTransform, flatten, prune } from '@gltf-transform/functions';
import { loadTexture, readMesh, writeMesh, disposeMesh, setupIO } from './gltf-io';
import { loadTexture, readMesh, writeMesh, disposeMesh, setupIO } from './gltf-io.js';
import Module from './built/manifold.js';
const io = setupIO(new WebIO());
@@ -63,9 +63,10 @@
const { Manifold, Mesh } = wasm;
const id2properties = new Map();
const attributes = ['POSITION', 'TEXCOORD_0'];
const materialMap = new Map();
async function readGLB(url) {
async function readGLB(url, attributes, materialMap) {
const manifolds = [];
const docIn = await io.read(url);
await docIn.transform(flatten());
@@ -74,33 +75,30 @@
for (const node of nodes) {
clearNodeTransform(node);
const materials = [];
const tmp = readMesh(node.getMesh());
tmp.mesh.runOriginalID = [];
const numID = tmp.runProperties.length;
const firstID = Manifold.reserveIDs(numID);
for (let i = 0; i < numID; ++i) {
tmp.mesh.runOriginalID.push(firstID + i);
const manifoldMesh = readMesh(node.getMesh(), attributes, materials);
manifoldMesh.runOriginalID = [];
const firstID = Manifold.reserveIDs(materials.length);
for (let i = 0; i < materials.length; ++i) {
manifoldMesh.runOriginalID.push(firstID + i);
ids.push(firstID + i);
id2properties.set(firstID + i, tmp.runProperties[i]);
}
manifolds.push(Manifold(new Mesh(tmp.mesh)));
manifolds.push(Manifold(new Mesh(manifoldMesh)));
}
// pull in materials, TODO: replace with transfer() when available
const startIdx = doc.getRoot().listMaterials().length;
doc.merge(docIn);
doc.getRoot().listScenes().forEach((s) => s.dispose());
doc.getRoot().listBuffers().forEach((s) => s.dispose());
doc.getRoot().listAccessors().forEach((s) => s.dispose());
for (const [i, id] of ids.entries()) {
const material = doc.getRoot().listMaterials()[startIdx + i];
id2properties.get(id).material = material;
materialMap.set(id, material);
}
return Manifold.union(manifolds);
}
const space = await readGLB('/models/space.glb');
const moon = await readGLB('/models/moon.glb');
const space = await readGLB('/models/space.glb', attributes, materialMap);
const moon = await readGLB('/models/moon.glb', attributes, materialMap);
const node = doc.createNode();
doc.createScene().addChild(node);
@@ -131,7 +129,13 @@
const manifoldMesh = manifold.rotate([-90, 0, 0]).getMesh();
disposeMesh(node.getMesh());
const mesh = writeMesh(doc, manifoldMesh, id2properties);
const materials = [];
const matAttributes = [];
for (const id of manifoldMesh.runOriginalID) {
materials.push(materialMap.get(id));
matAttributes.push(attributes);
}
const mesh = writeMesh(doc, manifoldMesh, matAttributes, materials);
node.setMesh(mesh);
await doc.transform(prune());
+786 -722
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -8,25 +8,25 @@
"build": "tsc --noEmit && vite build",
"build:watch": "vite build --watch",
"preview": "vite preview",
"test": "vitest run",
"test": "vitest",
"test:ui": "vitest --ui",
"test:dev": "vitest"
"test:run": "vitest run"
},
"dependencies": {
"@gltf-transform/core": "^3.8.0",
"@gltf-transform/extensions": "^3.8.0",
"@gltf-transform/functions": "^3.8.0",
"@gltf-transform/core": "^3.2.1",
"@gltf-transform/extensions": "^3.2.1",
"@gltf-transform/functions": "^3.2.1",
"@jscadui/3mf-export": "^0.4.2",
"fflate": "^0.8.0",
"gl-matrix": "^3.4.3",
"simple-dropzone": "0.8.3",
"three": "0.158.0"
"three": "0.151.2"
},
"devDependencies": {
"@vitest/ui": "^0.31.1",
"@vitest/web-worker": "^0.31.1",
"typescript": "5.2.2",
"vite": "^4.5.0",
"typescript": "5.0.4",
"vite": "^4.3.2",
"vitest": "^0.31.1"
}
}
+1 -4
View File
@@ -25,11 +25,8 @@ declare class GLTFNode {
clone(parent?: GLTFNode): GLTFNode;
}
type Attribute = 'POSITION'|'NORMAL'|'TANGENT'|'TEXCOORD_0'|'TEXCOORD_1'|
'COLOR_0'|'JOINTS_0'|'WEIGHTS_0'|'SKIP_1'|'SKIP_2'|'SKIP_3'|'SKIP_4';
declare class GLTFMaterial {
attributes?: Attribute[];
attributes?: string[];
roughness?: number;
metallic?: number;
baseColorFactor?: [number, number, number];
+6 -6
View File
@@ -350,12 +350,12 @@ export const examples = {
const hole = Manifold.compose(holes);
result = Manifold.difference([
result,
hole,
hole.rotate([90, 0, 0]),
hole.rotate([0, 90, 0]),
]);
result = Manifold.difference(
result,
hole,
hole.rotate([90, 0, 0]),
hole.rotate([0, 90, 0]),
);
return result;
}
+8 -5
View File
@@ -18,7 +18,7 @@ import {WebIO} from '@gltf-transform/core';
import {expect, suite, test} from 'vitest';
import Module from './built/manifold.js';
import {readMesh, setupIO} from './gltf-io';
import {readMesh, setupIO} from './gltf-io.js';
import {examples} from './public/examples.js';
import ManifoldWorker from './worker?worker';
@@ -63,14 +63,17 @@ async function runExample(name) {
const docIn = await io.read(glbURL);
const nodes = docIn.getRoot().listNodes();
for (const node of nodes) {
const docMesh = node.getMesh();
if (!docMesh) {
const mesh = node.getMesh();
if (!mesh) {
continue;
}
const {mesh} = readMesh(docMesh);
const manifold = wasm.Manifold(mesh);
const attributes = [];
const materials = [];
const manifoldMesh = readMesh(mesh, attributes, materials);
const manifold = wasm.Manifold(manifoldMesh);
const prop = manifold.getProperties();
const genus = manifold.genus();
console.log(genus);
manifold.delete();
resolve({...prop, genus});
}
+22 -16
View File
@@ -20,7 +20,7 @@ import * as glMatrix from 'gl-matrix';
import Module from './built/manifold';
//@ts-ignore
import {Attribute, Properties, setupIO, writeMesh} from './gltf-io';
import {setupIO, writeMesh} from './gltf-io';
import type {GLTFMaterial, Quat} from './public/editor';
import type {CrossSection, Manifold, ManifoldToplevel, Mesh, Vec3} from './public/manifold';
@@ -373,16 +373,19 @@ function addMesh(
// From Z-up to Y-up (glTF)
const manifoldMesh = manifold.getMesh();
const id2properties = new Map<number, Properties>();
const materials = new Array<GLTFMaterial>();
const attributes = new Array<Array<string>>();
for (const id of manifoldMesh.runOriginalID!) {
const material = id2material.get(id) || backupMaterial;
id2properties.set(id, {
material: getCachedMaterial(doc, ghost ? GHOST : material),
attributes: ['POSITION', ...material.attributes ?? []]
});
materials.push(material);
attributes.push(['POSITION', ...material.attributes ?? []]);
}
node.setMesh(writeMesh(doc, manifoldMesh, id2properties));
const gltfMaterials = materials.map((matDef) => {
return getCachedMaterial(doc, ghost ? GHOST : matDef);
});
node.setMesh(writeMesh(doc, manifoldMesh, attributes, gltfMaterials));
const vertices = manifoldMesh.numProp === 3 ?
manifoldMesh.vertProperties :
@@ -399,18 +402,21 @@ function addMesh(
{vertices, indices: manifoldMesh.triVerts, id: `${nextGlobalID++}`});
for (const [run, id] of manifoldMesh.runOriginalID!.entries()) {
const show = shown.has(id);
const inMesh = show ? shown.get(id) : singles.get(id);
let inMesh = shown.get(id);
let single = false;
if (inMesh == null) {
single = true;
inMesh = singles.get(id);
}
if (inMesh == null) {
continue;
}
id2properties.get(id)!.material = getCachedMaterial(
doc, show ? SHOW : (id2material.get(id) || backupMaterial));
const debugNode = doc.createNode('debug')
.setMesh(writeMesh(doc, inMesh, id2properties))
.setMatrix(manifoldMesh.transform(run));
const mat = single ? materials[run] : SHOW;
const debugNode =
doc.createNode('debug')
.setMesh(writeMesh(
doc, inMesh, attributes, [getCachedMaterial(doc, mat)]))
.setMatrix(manifoldMesh.transform(run));
node.addChild(debugNode);
}
}
+14 -16
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Box, FillRule, JoinType, Mat3, Mat4, Polygons, Properties, Rect, SealedFloat32Array, SealedUint32Array, SimplePolygon, Smoothness, Vec2, Vec3} from './manifold-global-types';
import {Box, FillRule, JoinType, Mat4, Polygons, Properties, Rect, SealedFloat32Array, SealedUint32Array, SimplePolygon, Smoothness, Vec2, Vec3} from './manifold-global-types';
/**
* Triangulates a set of /epsilon-valid polygons.
@@ -838,7 +838,19 @@ export class Manifold {
delete(): void;
}
export interface MeshOptions {
export class Mesh {
constructor(options: {
numProp: number,
vertProperties: Float32Array,
triVerts: Uint32Array,
mergeFromVert?: Uint32Array,
mergeToVert?: Uint32Array,
runIndex?: Uint32Array,
runOriginalID?: Uint32Array,
runTransform?: Float32Array,
faceID?: Uint32Array,
halfedgeTangent?: Float32Array
});
numProp: number;
vertProperties: Float32Array;
triVerts: Uint32Array;
@@ -849,20 +861,6 @@ export interface MeshOptions {
runTransform?: Float32Array;
faceID?: Uint32Array;
halfedgeTangent?: Float32Array;
}
export class Mesh {
constructor(options: MeshOptions);
numProp: number;
vertProperties: Float32Array;
triVerts: Uint32Array;
mergeFromVert: Uint32Array;
mergeToVert: Uint32Array;
runIndex: Uint32Array;
runOriginalID: Uint32Array;
runTransform: Float32Array;
faceID: Uint32Array;
halfedgeTangent: Float32Array;
get numTri(): number;
get numVert(): number;
get numRun(): number;
-12
View File
@@ -22,18 +22,6 @@ export interface SealedFloat32Array<N extends number> extends Float32Array {
export type Vec2 = [number, number];
export type Vec3 = [number, number, number];
// 3x3 matrix stored in column-major order
export type Mat3 = [
number,
number,
number,
number,
number,
number,
number,
number,
number,
];
// 4x4 matrix stored in column-major order
export type Mat4 = [
number,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "manifold-3d",
"version": "2.2.2",
"version": "2.1.0",
"description": "Geometry library for topological robustness",
"main": "manifold.js",
"files": [
+3 -3
View File
@@ -52,7 +52,7 @@
}: pkgs.stdenv.mkDerivation {
inherit doCheck;
pname = "manifold-${parallel-backend}";
version = "2.2.2";
version = "2.2.0";
src = self;
nativeBuildInputs = (with pkgs; [
cmake
@@ -103,7 +103,7 @@
parallelBackends)) // {
manifold-js = pkgs.buildEmscriptenPackage {
name = "manifold-js";
version = "2.2.2";
version = "beta";
src = self;
nativeBuildInputs = (with pkgs; [ cmake python39 ]);
buildInputs = [ pkgs.nodejs ];
@@ -134,7 +134,7 @@
# but how should we make it work with other python versions?
manifold3d = with pkgs.python3Packages; buildPythonPackage {
pname = "manifold3d";
version = "2.2.2";
version = "2.2.0";
src = self;
propagatedBuildInputs = [
numpy
-1
View File
@@ -234,7 +234,6 @@ void ExportMesh(const std::string& filename, const MeshGL& mesh,
? 1
: mesh.vertProperties[i * mesh.numProp +
options.mat.colorChannels[j]];
c = glm::saturate(c);
mesh_out->mColors[0][i] = aiColor4D(c.r, c.g, c.b, c.a);
}
}
+2 -2
View File
@@ -1,13 +1,13 @@
[project]
name = "manifold3d"
version = "2.2.2"
version = "2.2.0"
authors = [
{ name="Emmett Lalish", email="elalish@gmail.com" },
]
description = "Library for geometric robustness"
readme = "README.md"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Development Status :: 4 - Beta",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: C++",
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

+26 -40
View File
@@ -463,8 +463,9 @@ struct MapTriRef {
}
};
void UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
const Manifold::Impl &inQ, bool invertQ) {
Vec<TriRef> UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
const Manifold::Impl &inQ, bool invertQ) {
Vec<TriRef> refPQ = outR.meshRelation_.triRef;
const int offsetQ = Manifold::Impl::meshIDCounter_;
for_each_n(
autoPolicy(outR.NumTri()), outR.meshRelation_.triRef.begin(),
@@ -479,6 +480,7 @@ void UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
outR.meshRelation_.meshIDtransform[pair.first + offsetQ].backSide ^=
invertQ;
}
return refPQ;
}
struct Barycentric {
@@ -512,8 +514,8 @@ struct Barycentric {
}
};
void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP,
const Manifold::Impl &inQ) {
void CreateProperties(Manifold::Impl &outR, const Vec<TriRef> &refPQ,
const Manifold::Impl &inP, const Manifold::Impl &inQ) {
const int numPropP = inP.NumProp();
const int numPropQ = inQ.NumProp();
const int numProp = glm::max(numPropP, numPropQ);
@@ -524,8 +526,7 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP,
outR.meshRelation_.triProperties.resize(numTri);
Vec<glm::vec3> bary(outR.halfedge_.size());
for_each_n(autoPolicy(numTri),
zip(countAt(0), outR.meshRelation_.triRef.cbegin()), numTri,
for_each_n(autoPolicy(numTri), zip(countAt(0), refPQ.cbegin()), numTri,
Barycentric({bary, inP.vertPos_, inQ.vertPos_, outR.vertPos_,
inP.halfedge_, inQ.halfedge_, outR.halfedge_,
outR.precision_}));
@@ -533,9 +534,6 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP,
using Entry = std::pair<glm::ivec3, int>;
int idMissProp = outR.NumVert();
std::vector<std::vector<Entry>> propIdx(outR.NumVert() + 1);
std::vector<int> propMissIdx[2];
propMissIdx[0].resize(inQ.NumPropVert(), -1);
propMissIdx[1].resize(inP.NumPropVert(), -1);
outR.meshRelation_.properties.reserve(outR.NumVert() * numProp);
int idx = 0;
@@ -543,14 +541,14 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP,
// Skip collapsed triangles
if (outR.halfedge_[3 * tri].startVert < 0) continue;
const TriRef ref = outR.meshRelation_.triRef[tri];
const bool PQ = ref.meshID == 0;
const int triPQ = refPQ[tri].tri;
const bool PQ = refPQ[tri].meshID == 0;
const int oldNumProp = PQ ? numPropP : numPropQ;
const auto &properties =
PQ ? inP.meshRelation_.properties : inQ.meshRelation_.properties;
const glm::ivec3 &triProp = oldNumProp == 0 ? glm::ivec3(-1)
: PQ ? inP.meshRelation_.triProperties[ref.tri]
: inQ.meshRelation_.triProperties[ref.tri];
: PQ ? inP.meshRelation_.triProperties[triPQ]
: inQ.meshRelation_.triProperties[triPQ];
for (const int i : {0, 1, 2}) {
const int vert = outR.halfedge_[3 * tri + i].startVert;
@@ -558,7 +556,8 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP,
glm::ivec4 key(PQ, idMissProp, -1, -1);
if (oldNumProp > 0) {
int edge = -2;
key[1] = vert;
int edge = -1;
for (const int j : {0, 1, 2}) {
if (uvw[j] == 1) {
// On a retained vert, the propVert must also match
@@ -572,37 +571,24 @@ void CreateProperties(Manifold::Impl &outR, const Manifold::Impl &inP,
// On an edge, both propVerts must match
const int p0 = triProp[Next3(edge)];
const int p1 = triProp[Prev3(edge)];
key[1] = vert;
key[2] = glm::min(p0, p1);
key[3] = glm::max(p0, p1);
} else if (edge == -2) {
key[1] = vert;
}
}
if (key.y == idMissProp && key.z >= 0) {
// only key.x/key.z matters
auto &entry = propMissIdx[key.x][key.z];
if (entry >= 0) {
outR.meshRelation_.triProperties[tri][i] = entry;
continue;
auto &bin = propIdx[key.y];
bool bFound = false;
for (int k = 0; k < bin.size(); ++k) {
if (bin[k].first == glm::ivec3(key.x, key.z, key.w)) {
bFound = true;
outR.meshRelation_.triProperties[tri][i] = bin[k].second;
break;
}
entry = idx;
} else {
auto &bin = propIdx[key.y];
bool bFound = false;
for (int k = 0; k < bin.size(); ++k) {
if (bin[k].first == glm::ivec3(key.x, key.z, key.w)) {
bFound = true;
outR.meshRelation_.triProperties[tri][i] = bin[k].second;
break;
}
}
if (bFound) continue;
bin.push_back(std::make_pair(glm::ivec3(key.x, key.z, key.w), idx));
}
if (bFound) continue;
bin.push_back(std::make_pair(glm::ivec3(key.x, key.z, key.w), idx));
outR.meshRelation_.triProperties[tri][i] = idx++;
for (int p = 0; p < numProp; ++p) {
if (p < oldNumProp) {
glm::vec3 oldProps;
@@ -784,12 +770,12 @@ Manifold::Impl Boolean3::Result(OpType op) const {
if (ManifoldParams().intermediateChecks)
ASSERT(outR.IsManifold(), logicErr, "triangulated mesh is not manifold!");
CreateProperties(outR, inP_, inQ_);
UpdateReference(outR, inP_, inQ_, invertQ);
Vec<TriRef> refPQ = UpdateReference(outR, inP_, inQ_, invertQ);
outR.SimplifyTopology();
CreateProperties(outR, refPQ, inP_, inQ_);
if (ManifoldParams().intermediateChecks)
ASSERT(outR.Is2Manifold(), logicErr, "simplified mesh is not 2-manifold!");
-3
View File
@@ -220,9 +220,6 @@ Manifold Manifold::Sphere(float radius, int circularSegments) {
/**
* Constructs a manifold from a set of polygons by extruding them along the
* Z-axis.
* Note that high twistDegrees with small nDivisions may cause
* self-intersection. This is not checked here and it is up to the user to
* choose the correct parameters.
*
* @param crossSection A set of non-overlapping polygons to extrude.
* @param height Z-extent of extrusion.
+15 -14
View File
@@ -178,10 +178,6 @@ void Manifold::Impl::SimplifyTopology() {
}
#endif
if (!ManifoldParams().cleanupTriangles) {
return;
}
std::vector<int> scratchBuffer;
scratchBuffer.reserve(10);
{
@@ -505,20 +501,25 @@ void Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {
const int tri1 = toRemove.pairedHalfedge / 3;
const int triVert0 = (edge + 1) % 3;
const int triVert1 = toRemove.pairedHalfedge % 3;
const int prop0 = triProp.size() > 0 ? triProp[tri0][edge % 3] : -1;
const int prop1 = triProp.size() > 0
? triProp[tri1][(toRemove.pairedHalfedge + 1) % 3]
: -1;
current = start;
while (current != tri0edge[2]) {
current = NextHalfedge(current);
if (triProp.size() > 0) {
// Update the shifted triangles to the vertBary of endVert
const int tri = current / 3;
const int vIdx = current - 3 * tri;
if (triRef[tri].meshID == triRef[tri0].meshID &&
triRef[tri].tri == triRef[tri0].tri) {
triProp[tri][vIdx] = triProp[tri0][triVert0];
} else if (triRef[tri].meshID == triRef[tri1].meshID &&
triRef[tri].tri == triRef[tri1].tri) {
triProp[tri][vIdx] = triProp[tri1][triVert1];
// Update the shifted triangles to the vertBary of endVert
const int tri = current / 3;
const int vIdx = current - 3 * tri;
if (!shortEdge) {
if (triProp.size() > 0) {
if (triProp[tri][vIdx] == prop0) {
triProp[tri][vIdx] = triProp[tri0][triVert0];
} else if (triProp[tri][vIdx] == prop1) {
triProp[tri][vIdx] = triProp[tri1][triVert1];
}
}
}
+3 -6
View File
@@ -196,13 +196,10 @@ struct SmoothBezier {
const glm::vec3 edgeNormal =
(triNormal[edge.face] + triNormal[halfedge[edge.pairedHalfedge].face]) /
2.0f;
glm::vec3 dir = SafeNormalize(glm::cross(glm::cross(edgeNormal, edgeVec),
vertNormal[edge.startVert]));
glm::vec3 dir = glm::normalize(glm::cross(glm::cross(edgeNormal, edgeVec),
vertNormal[edge.startVert]));
float weight = glm::abs(glm::dot(dir, SafeNormalize(edgeVec)));
if (weight == 0) {
weight = 1;
}
const float weight = glm::abs(glm::dot(dir, glm::normalize(edgeVec)));
// Quadratic weighted bezier for circular interpolation
const glm::vec4 bz2 =
weight *
+1 -1
View File
@@ -607,7 +607,7 @@ class EarClip {
const glm::vec2 size = bBox.Size();
const double minArea = precision_ * glm::max(size.x, size.y);
if (glm::isfinite(maxX) && area < -minArea) {
if (area < -minArea) {
holes_.insert(start);
hole2BBox_.insert({start, bBox});
} else {
+8 -10
View File
@@ -32,11 +32,9 @@ FetchContent_Declare(Thrust
GIT_TAG 2.1.0
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
FIND_PACKAGE_ARGS NAMES Thrust
)
find_package(Thrust QUIET)
if(NOT Thrust_FOUND AND NOT DEFINED thrust_SOURCE_DIR)
FetchContent_Populate(Thrust)
endif()
FetchContent_MakeAvailable(Thrust)
if (TRACY_ENABLE)
include(FetchContent)
@@ -68,7 +66,7 @@ if(MANIFOLD_PAR STREQUAL "TBB")
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(TBB)
set_property(DIRECTORY ${tbb_SOURCE_DIR} PROPERTY EXCLUDE_FROM_ALL YES)
set_property(DIRECTORY ${TBB_SOURCE_DIR} PROPERTY EXCLUDE_FROM_ALL YES)
endif()
target_compile_options(${PROJECT_NAME} INTERFACE -DMANIFOLD_PAR='T')
if(TARGET TBB::tbb)
@@ -86,15 +84,15 @@ endif()
target_include_directories(${PROJECT_NAME} INTERFACE ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(${PROJECT_NAME} INTERFACE glm::glm)
if(DEFINED thrust_SOURCE_DIR)
if(DEFINED Thrust_SOURCE_DIR)
target_include_directories(${PROJECT_NAME} INTERFACE
${thrust_SOURCE_DIR}
${thrust_SOURCE_DIR}/dependencies/libcudacxx/include
${Thrust_SOURCE_DIR}
${Thrust_SOURCE_DIR}/dependencies/libcudacxx/include
)
else()
target_include_directories(${PROJECT_NAME} INTERFACE
${_THRUST_INCLUDE_DIR}
${_THRUST_INCLUDE_DIR}/dependencies/libcudacxx/include
${THRUST_ROOT}
${THRUST_ROOT}/dependencies/libcudacxx/include
)
endif()
-2
View File
@@ -613,8 +613,6 @@ struct ExecutionParams {
bool suppressErrors = false;
/// Deterministic outputs. Will disable some parallel optimizations.
bool deterministic = false;
/// Perform optional but recommended triangle cleanups in SimplifyTopology()
bool cleanupTriangles = true;
};
#ifdef MANIFOLD_DEBUG
+1 -5
View File
@@ -48,11 +48,7 @@ class SparseIndices {
}
SparseIndices() = default;
SparseIndices(size_t size) {
if (size >= std::numeric_limits<int32_t>::max() / sizeof(int64_t))
throw std::out_of_range("SparseIndices too large");
data_ = Vec<char>(size * sizeof(int64_t));
}
SparseIndices(size_t size) : data_(size * sizeof(int64_t)) {}
int size() const { return data_.size() / sizeof(int64_t); }
+2 -451
View File
@@ -163,7 +163,7 @@ TEST(Boolean, Coplanar) {
MeshGL cylinderGL = WithPositionColors(cylinder);
cylinder = Manifold(cylinderGL);
Manifold cylinder2 = cylinder.Scale({0.8f, 0.8f, 1.0f}).Rotate(0, 0, 185);
Manifold cylinder2 = cylinder.Scale({0.8f, 0.8f, 1.0f}).Rotate(0, 0, 5);
Manifold out = cylinder - cylinder2;
ExpectMeshes(out, {{32, 64, 3, 48}});
EXPECT_EQ(out.NumDegenerateTris(), 0);
@@ -187,7 +187,7 @@ TEST(Boolean, CoplanarProp) {
MeshGL cylinderGL = WithIndexColors(cylinder.GetMesh());
cylinder = Manifold(cylinderGL);
Manifold cylinder2 = cylinder.Scale({0.8f, 0.8f, 1.0f}).Rotate(0, 0, 185);
Manifold cylinder2 = cylinder.Scale({0.8f, 0.8f, 1.0f}).Rotate(0, 0, 5);
Manifold out = cylinder - cylinder2;
ExpectMeshes(out, {{52, 104, 3, 88}});
EXPECT_EQ(out.NumDegenerateTris(), 0);
@@ -902,452 +902,3 @@ TEST(Boolean, Sweep) {
PolygonParams().processOverlaps = false;
}
TEST(Boolean, InterpolatedNormals) {
MeshGL a;
a.numProp = 8;
a.vertProperties = {
// 0
-409.0570983886719, -300, -198.83624267578125, 0, -1, 0,
590.9429321289062, 301.1637268066406,
// 1
-1000, -300, 500, 0, -1, 0, 0, 1000,
// 2
-1000, -300, -500, 0, -1, 0, 0, 0,
// 3
-1000, -300, -500, -1, 0, 0, 600, 0,
// 4
-1000, -300, 500, -1, 0, 0, 600, 1000,
// 5
-1000, 300, -500, -1, 0, 0, 0, 0,
// 6
7.179656982421875, -300, -330.03717041015625, 0, -1, 0,
1007.1796264648438, 169.9628448486328,
// 7
1000, 300, 500, 0, 0, 1, 2000, 600,
// 8
403.5837097167969, 300, 500, 0, 0, 1, 1403.583740234375, 600,
// 9
564.2904052734375, 21.64801025390625, 500, 0, 0, 1, 1564.29052734375,
321.64801025390625,
// 10
1000, -300, -500, 0, 0, -1, 2000, 600,
// 11
-1000, -300, -500, 0, 0, -1, 0, 600,
// 12
-1000, 300, -500, 0, 0, -1, 0, 0,
// 13
1000, 300, 500, 0, 1, 0, 0, 1000,
// 14
1000, 300, -500, 0, 1, 0, 0, 0,
// 15
724.5271606445312, 300, 398.83624267578125, 0, 1, 0, 275.47283935546875,
898.8362426757812,
// 16
-115.35255432128906, -300, 500, 0, -1, 0, 884.6475219726562,
1000.0001220703125,
// 17
-384.7195129394531, 166.55722045898438, 500, 0, 0, 1, 615.280517578125,
466.5572509765625,
// 18
-1000, -300, 500, 0, 0, 1, 0, 0,
// 19
-161.6136932373047, -219.87335205078125, 500, 0, 0, 1, 838.3862915039062,
80.12664794921875,
// 20
1000, -300, 500, 0, 0, 1, 2000, 0,
// 21
-115.35255432128906, -300, 500, 0, 0, 1, 884.6475219726562, 0,
// 22
1000, 300, 500, 1, 0, 0, 600, 1000,
// 23
1000, -300, 500, 1, 0, 0, 0, 1000,
// 24
1000, 300, -500, 1, 0, 0, 600, 0,
// 25
566.6257934570312, 300, 23.1280517578125, 0, 1, 0, 433.3742370605469,
523.1281127929688,
// 26
411.5867004394531, -66.51548767089844, -500, 0, 0, -1, 1411.586669921875,
366.5155029296875,
// 27
375.7498779296875, -4.444300651550293, -500, 0, 0, -1, 1375.7498779296875,
304.4443054199219,
// 28
346.7673034667969, 300, -500, 0, 1, 0, 653.2326049804688, 0,
// 29
-153.58984375, 300, -388.552490234375, 0, 1, 0, 1153.58984375,
111.447509765625,
// 30
199.9788818359375, 300, -500, 0, 1, 0, 800.0211791992188, 0,
// 31
-1000, 300, -500, 0, 1, 0, 2000, 0,
// 32
-153.58987426757812, 300, 44.22247314453125, 0, 1, 0, 1153.58984375,
544.2224731445312,
// 33
199.9788818359375, 300, -500, 0, 0, -1, 1199.9791259765625, 0,
// 34
521.6780395507812, -2.9542479515075684, -500, 0, 0, -1, 1521.677978515625,
302.9542541503906,
// 35
346.7673034667969, 300, -500, 0, 0, -1, 1346.767333984375, 0,
// 36
1000, 300, -500, 0, 0, -1, 2000, 0,
// 37
-1000, 300, 500, -1, 0, 0, 0, 1000,
// 38
-1000, 300, 500, 0, 0, 1, 0, 600,
// 39
-1000, 300, 500, 0, 1, 0, 2000, 1000,
// 40
-153.58985900878906, 300, 500, 0, 0, 1, 846.4102172851562, 600,
// 41
88.46627807617188, -253.06915283203125, 500, 0, 0, 1, 1088.4664306640625,
46.93084716796875,
// 42
-153.58985900878906, 300, 500, 0, 1, 0, 1153.58984375, 1000,
// 43
7.1796698570251465, -300, 500, 0, 0, 1, 1007.1797485351562, 0,
// 44
1000, -300, -500, 0, -1, 0, 2000, 0,
// 45
1000, -300, 500, 0, -1, 0, 2000, 1000,
// 46
7.1796698570251465, -300, 500, 0, -1, 0, 1007.1796264648438, 1000,
// 47
403.5837097167969, 300, 500, 0, 1, 0, 596.4163208007812, 1000,
// 48
1000, -300, -500, 1, 0, 0, 0, 0,
// 49
492.3005676269531, -19.915321350097656, -500, 0, 0, -1, 1492.300537109375,
319.91534423828125,
// 50
411.5867004394531, -66.51548767089844, -500, -0.5, 0.8660253882408142, 0,
880.5439453125, 0,
// 51
7.179656982421875, -300, -330.03717041015625, -0.5000000596046448,
0.866025447845459, 0, 383.6058654785156, 0,
// 52
492.3005676269531, -19.915321350097656, -500, -0.5, 0.8660253882408142, 0,
968.1235961914062, 31.876384735107422,
// 53
7.1796698570251465, -300, 500, -0.5000000596046448, 0.866025447845459, 0,
99.71644592285156, 779.979736328125,
// 54
88.46627807617188, -253.06915283203125, 500, -0.5, 0.8660253882408142, 0,
187.91758728027344, 812.0823974609375,
// 55
-153.58985900878906, 300, 500, 0.5, -0.866025447845459, 0,
749.2095947265625, 834.9661865234375,
// 56
-384.7195129394531, 166.55722045898438, 500, 0.5000000596046448,
-0.866025447845459, 0, 1000, 743.6859741210938,
// 57
-153.58987426757812, 300, 44.22247314453125, 0.5, -0.8660253882408142, 0,
593.3245239257812, 406.6754455566406,
// 58
564.2904052734375, 21.64801025390625, 500, -0.5, 0.866025447845459, 0,
704.217041015625, 1000.0000610351562,
// 59
-604.9979248046875, 39.37942886352539, -198.83624267578125, 0.5,
-0.8660253882408142, 0, 1000, 0,
// 60
199.9788818359375, 300, -500, 0.29619815945625305, 0.1710100919008255,
0.9396927356719971, 880.5438842773438, 176.7843475341797,
// 61
-153.58984375, 300, -388.552490234375, 0.29619815945625305,
0.1710100919008255, 0.9396927356719971, 554.6932373046875, 0,
// 62
375.7498779296875, -4.444300651550293, -500, 0.29619812965393066,
0.1710100919008255, 0.9396926760673523, 880.5438842773438,
528.3263549804688,
// 63
566.6257934570312, 300, 23.1280517578125, -0.8137977123260498,
-0.46984636783599854, 0.342020183801651, 239.89218139648438, 600.1796875,
// 64
346.7673034667969, 300, -500, -0.8137977719306946, -0.46984633803367615,
0.342020183801651, 349.8214111328125, 43.478458404541016,
// 65
521.6780395507812, -2.9542479515075684, -500, -0.8137977719306946,
-0.46984633803367615, 0.342020183801651, 0, 43.478458404541016,
// 66
804.9979248046875, 160.62057495117188, 398.83624267578125, -0.5,
0.8660253882408142, 0, 1000, 1000,
// 67
521.6780395507812, -2.9542479515075684, -500, -0.5, 0.8660253882408142, 0,
1000, 43.47837829589844,
// 68
-153.58984375, 300, -388.552490234375, 0.5, -0.8660253882408142, 0,
445.3067626953125, 0,
// 69
-604.9979248046875, 39.37942886352539, -198.83624267578125,
0.29619815945625305, 0.1710100919008255, 0.9396927356719971, 0, 0,
// 70
804.9979248046875, 160.62057495117188, 398.83624267578125,
-0.813797652721405, -0.46984630823135376, 0.3420201539993286, 0, 1000,
// 71
-161.6136932373047, -219.87335205078125, 500, 0.8137977123260498,
0.46984630823135376, -0.3420201539993286, 446.21160888671875,
743.68603515625,
// 72
-604.9979248046875, 39.37942886352539, -198.83624267578125,
0.813797652721405, 0.46984630823135376, -0.3420201539993286, 0, 0,
// 73
-384.7195129394531, 166.55722045898438, 500, 0.8137977123260498,
0.46984636783599854, -0.342020183801651, 0, 743.6859741210938,
// 74
-115.35255432128906, -300, 500, 0.813797652721405, 0.46984633803367615,
-0.3420201539993286, 538.73388671875, 743.68603515625,
// 75
-409.0570983886719, -300, -198.83624267578125, 0.813797652721405,
0.46984630823135376, -0.3420201539993286, 391.8816223144531, 0,
// 76
7.179656982421875, -300, -330.03717041015625, 0.29619815945625305,
0.1710100919008255, 0.9396927356719971, 383.6058654785156, 600,
// 77
564.2904052734375, 21.64801025390625, 500, -0.29619812965393066,
-0.1710100919008255, -0.9396926164627075, 704.2169189453125,
-0.000030517578125,
// 78
403.5837097167969, 300, 500, -0.29619812965393066, -0.1710100919008255,
-0.9396926164627075, 704.2169799804688, 321.4132385253906,
// 79
724.5271606445312, 300, 398.83624267578125, -0.29619815945625305,
-0.1710100919008255, -0.9396926760673523, 1000, 160.94149780273438,
// 80
804.9979248046875, 160.62057495117188, 398.83624267578125,
-0.29619815945625305, -0.1710100919008255, -0.9396927356719971, 1000, 0,
// 81
-409.0570983886719, -300, -198.83624267578125, 0.29619815945625305,
0.1710100919008255, 0.9396927356719971, 0, 391.88165283203125,
// 82
724.5271606445312, 300, 398.83624267578125, -0.813797652721405,
-0.46984630823135376, 0.342020183801651, 160.94149780273438,
1000.0000610351562,
// 83
411.5867004394531, -66.51548767089844, -500, 0.29619815945625305,
0.1710100769996643, 0.9396926164627075, 880.5440063476562,
600.0000610351562};
a.triVerts = {// 0
0, 1, 2,
// 1
3, 4, 5,
// 2
6, 0, 2,
// 3
7, 8, 9,
// 4
10, 11, 12,
// 5
13, 14, 15,
// 6
0, 16, 1,
// 7
17, 18, 19,
// 8
9, 20, 7,
// 9
18, 21, 19,
// 10
22, 23, 24,
// 11
14, 25, 15,
// 12
26, 12, 27,
// 13
14, 28, 25,
// 14
29, 30, 31,
// 15
29, 31, 32,
// 16
12, 33, 27,
// 17
34, 35, 36,
// 18
5, 4, 37,
// 19
17, 38, 18,
// 20
31, 39, 32,
// 21
40, 38, 17,
// 22
9, 41, 20,
// 23
39, 42, 32,
// 24
41, 43, 20,
// 25
6, 2, 44,
// 26
6, 45, 46,
// 27
26, 10, 12,
// 28
47, 13, 15,
// 29
48, 24, 23,
// 30
6, 44, 45,
// 31
26, 49, 10,
// 32
49, 34, 10,
// 33
34, 36, 10,
// 34
50, 51, 52,
// 35
51, 53, 54,
// 36
51, 54, 52,
// 37
55, 56, 57,
// 38
52, 54, 58,
// 39
59, 57, 56,
// 40
60, 61, 62,
// 41
63, 64, 65,
// 42
52, 66, 67,
// 43
59, 68, 57,
// 44
69, 62, 61,
// 45
65, 70, 63,
// 46
71, 72, 73,
// 47
52, 58, 66,
// 48
74, 72, 71,
// 49
74, 75, 72,
// 50
62, 69, 76,
// 51
77, 78, 79,
// 52
79, 80, 77,
// 53
69, 81, 76,
// 54
63, 70, 82,
// 55
76, 83, 62};
a.mergeFromVert = {3, 4, 11, 12, 13, 18, 21, 22, 23, 24, 31, 33, 35, 36,
38, 39, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 55,
56, 57, 58, 60, 61, 62, 63, 64, 65, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83};
a.mergeToVert = {2, 1, 2, 5, 7, 1, 16, 7, 20, 14, 5, 30, 28, 14,
37, 37, 40, 10, 20, 43, 8, 10, 26, 6, 49, 43, 41, 40,
17, 32, 9, 30, 29, 27, 25, 28, 34, 34, 29, 59, 66, 19,
59, 17, 16, 0, 6, 9, 8, 15, 66, 0, 15, 26};
MeshGL b;
b.numProp = 8;
b.vertProperties = {// 0
-1700, -600, -1000, -1, 0, 0, 1200, 0,
// 1
-1700, -600, 1000, -1, 0, 0, 1200, 2000,
// 2
-1700, 600, -1000, -1, 0, 0, 0, 0,
// 3
-1700, -600, -1000, 0, -1, 0, 0, 0,
// 4
300, -600, -1000, 0, -1, 0, 2000, 0,
// 5
-1700, -600, 1000, 0, -1, 0, 0, 2000,
// 6
-1700, -600, -1000, 0, 0, -1, 0, 1200,
// 7
-1700, 600, -1000, 0, 0, -1, 0, 0,
// 8
300, -600, -1000, 0, 0, -1, 2000, 1200,
// 9
-1700, -600, 1000, 0, 0, 1, 0, 0,
// 10
300, -600, 1000, 0, 0, 1, 2000, 0,
// 11
-1700, 600, 1000, 0, 0, 1, 0, 1200,
// 12
-1700, 600, 1000, -1, 0, 0, 0, 2000,
// 13
-1700, 600, -1000, 0, 1, 0, 2000, 0,
// 14
-1700, 600, 1000, 0, 1, 0, 2000, 2000,
// 15
300, 600, 1000, 0, 1, 0, 0, 2000,
// 16
300, -600, -1000, 1, 0, 0, 0, 0,
// 17
300, 600, -1000, 1, 0, 0, 1200, 0,
// 18
300, -600, 1000, 1, 0, 0, 0, 2000,
// 19
300, -600, 1000, 0, -1, 0, 2000, 2000,
// 20
300, 600, -1000, 0, 0, -1, 2000, 0,
// 21
300, 600, -1000, 0, 1, 0, 0, 0,
// 22
300, 600, 1000, 0, 0, 1, 2000, 1200,
// 23
300, 600, 1000, 1, 0, 0, 1200, 2000};
b.triVerts = {// 0
0, 1, 2,
// 1
3, 4, 5,
// 2
6, 7, 8,
// 3
9, 10, 11,
// 4
1, 12, 2,
// 5
13, 14, 15,
// 6
16, 17, 18,
// 7
4, 19, 5,
// 8
7, 20, 8,
// 9
21, 13, 15,
// 10
10, 22, 11,
// 11
17, 23, 18};
b.mergeFromVert = {3, 5, 6, 7, 8, 9, 12, 13, 14, 16, 18, 19, 20, 21, 22, 23};
b.mergeToVert = {0, 1, 0, 2, 4, 1, 11, 2, 11, 4, 10, 10, 17, 17, 15, 15};
a.runOriginalID = {Manifold::ReserveIDs(1)};
b.runOriginalID = {Manifold::ReserveIDs(1)};
Manifold aManifold(a);
Manifold bManifold(b);
auto aMinusB = aManifold - bManifold;
std::vector<MeshGL> meshList;
meshList.emplace_back(a);
meshList.emplace_back(b);
RelatedGL(aMinusB, meshList, false, false);
}
TEST(Boolean, CreatePropertiesSlow) {
Manifold a = Manifold::Sphere(10, 1024).SetProperties(
3, [](float* newprop, glm::vec3 pos, const float* old) {
for (int i = 0; i < 3; i++) newprop[i] = 0;
});
Manifold b = Manifold::Sphere(10, 1024).Translate({5, 0, 0});
Manifold result = a + b;
EXPECT_EQ(result.NumProp(), 3);
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-45
View File
@@ -543,51 +543,6 @@ TEST(Polygon, Eberly) {
TestPoly(polys, 10);
};
TEST(Polygon, Ugly) {
Polygons polys;
polys.push_back({
{2379.21118, 13009.6982}, //
{2292.45776, 13566.7129}, //
{2048.95142, 13894.6953}, //
{2054.84497, 13878.9023}, //
{2055.11914, 13878.9707}, //
{2186.24487, 13527.4189}, //
{2186.24487, 13527.4189}, //
{2186.24487, 13527.4189}, //
{2189.47729, 13552.7422}, //
{2208.28711, 13577.457}, //
{2208.28711, 13577.457}, //
{2236.93433, 13589.4551}, //
{2236.93433, 13589.4551}, //
{2236.93433, 13589.4551}, //
{2208.28711, 13577.457}, //
{2292.45703, 13566.7129}, //
{2208.28711, 13577.457}, //
{2208.28711, 13577.457}, //
{2189.47729, 13552.7422}, //
{2189.47729, 13552.7422}, //
{2189.47729, 13552.7422}, //
{2222.25732, 13474.4775}, //
{2222.25732, 13474.4775}, //
{2222.25732, 13474.4775}, //
{2222.25732, 13474.4775}, //
{2222.25732, 13474.4775}, //
{2189.47729, 13552.7422}, //
{2186.24487, 13527.4189}, //
{2186.61182, 13525.8066}, //
{2222.38232, 13429.9521}, //
{2222.38232, 13429.9521}, //
{2222.38232, 13429.9521}, //
});
polys.push_back({
{2379.03198, 13010.5518}, //
{2378.89648, 13010.541}, //
{2289.28784, 13251.1582}, //
{2379.03027, 13010.5566}, //
});
TestPoly(polys, 32);
};
TEST(Polygon, Sponge4a) {
Polygons polys;
polys.push_back({
-22
View File
@@ -266,28 +266,6 @@ TEST(Samples, SelfIntersect) {
res.GetMeshGL(); // test crash
manifold::PolygonParams().processOverlaps = false;
}
TEST(Samples, GenericTwinBooleanTest7863) {
manifold::PolygonParams().processOverlaps = true;
std::string file = __FILE__;
std::string dir = file.substr(0, file.rfind('/'));
Manifold m1 = ImportMesh(dir + "/models/Generic_Twin_7863.1.t0_left.glb");
Manifold m2 = ImportMesh(dir + "/models/Generic_Twin_7863.1.t0_right.glb");
Manifold res = m1 + m2; // Union
res.GetMeshGL(); // test crash
manifold::PolygonParams().processOverlaps = false;
}
TEST(Samples, Havocglass8Bool) {
manifold::PolygonParams().processOverlaps = true;
std::string file = __FILE__;
std::string dir = file.substr(0, file.rfind('/'));
Manifold m1 = ImportMesh(dir + "/models/Havocglass8_left.glb");
Manifold m2 = ImportMesh(dir + "/models/Havocglass8_right.glb");
Manifold res = m1 + m2; // Union
res.GetMeshGL(); // test crash
manifold::PolygonParams().processOverlaps = false;
}
#endif
TEST(Samples, CondensedMatter16) {
-18
View File
@@ -115,21 +115,3 @@ TEST(SDF, Resize) {
EXPECT_EQ(layers.Status(), Manifold::Error::NoError);
EXPECT_EQ(layers.Genus(), -8);
}
TEST(SDF, SineSurface) {
Mesh surface(LevelSet(
[](glm::vec3 p) {
float mid = glm::sin(p.x) + glm::sin(p.y);
return (p.z > mid - 0.5 && p.z < mid + 0.5) ? 1 : 0;
},
{glm::vec3(-4 * glm::pi<float>()), glm::vec3(4 * glm::pi<float>())}, 1));
Manifold smoothed = Manifold::Smooth(surface).Refine(2);
EXPECT_EQ(smoothed.Status(), Manifold::Error::NoError);
EXPECT_EQ(smoothed.Genus(), -2);
#ifdef MANIFOLD_EXPORT
if (options.exportModels)
ExportMesh("sinesurface.glb", smoothed.GetMeshGL(), {});
#endif
}
+2 -2
View File
@@ -373,7 +373,7 @@ void RelatedGL(const Manifold& out, const std::vector<MeshGL>& originals,
glm::vec3 edges[3];
for (int k : {0, 1, 2}) edges[k] = inTriPos[k] - outTriPos[j];
const float volume = glm::dot(edges[0], glm::cross(edges[1], edges[2]));
ASSERT_LE(volume, area * out.Precision());
ASSERT_LE(volume, area * 100 * out.Precision());
if (checkNormals) {
glm::vec3 normal;
@@ -396,7 +396,7 @@ void RelatedGL(const Manifold& out, const std::vector<MeshGL>& originals,
const float volumeP =
glm::dot(edgesP[0], glm::cross(edgesP[1], edgesP[2]));
ASSERT_LE(volumeP, area * out.Precision());
ASSERT_LE(volumeP, area * 100 * out.Precision());
}
}
}