Compare commits

..
18 Commits
Author SHA1 Message Date
Emmett Lalish 01c38e42ca V2.2.2 (#622)
* bumped version

* added npm publishing automation
2023-11-17 10:20:16 -08:00
Chun Kit LAM 1991232eb7 fix performance regression for CreateProperties (#621)
* fix performance regression for CreateProperties

* added SparseIndices size check

* faster CreateProperties

* fix

* rollback and fix

* applied suggestion
2023-11-17 07:58:33 -08:00
Chun Kit LAM 4551288f95 Use black formatter for python format checking (#618)
* added black formatter to ci

* format gyroid_module.py
2023-11-16 20:44:07 +08:00
Emmett Lalishandpca006132 2e29784449 Update python bindings (#615)
* updated python bindings

* added scallop example

* fix python binding and example

* apply black formatting

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
2023-11-15 22:51:51 -08:00
Johnathon Selstad 2a2cb6f88e [FEAT] Add LevelSet Function to Python Bindings (#616)
* Commit LevelSet Python Binding

* Address PR Comments

* Standardize Indentation
2023-11-15 22:24:45 -08:00
Rob McDonald 1a91d8db2a RFC to make SimplifyTopology() steps optional (#603)
Add option to skip cleaning up triangles in SimplifyTopology()
2023-11-14 15:23:51 -08:00
Emmett Lalish 82b98d9353 Improve gltf-io and manifold-gltf (#612)
* fixed build copy for npm

* update deps

* fixed glTF validator errors

* fixed bug and Vite

* clamped color

* converted manifold-gltf to TS

* converted gltf-io to TS

* fixed TS build

* avoid usage warnings

* update MV

* clean up types

* update gltf-io API

* fixed bugs

* fixed disposal

* fixed example

* a few fixes and cleanup
2023-11-14 15:23:25 -08:00
Emmett Lalish 7273c9bf0f Fix odd number halfedges (#610)
* fixed odd number of halfedges

* both tests fixed

* reenable tests

* wrong isfinite
2023-11-12 16:09:42 -08:00
4ff3f7cf68 Apparent infinite loop case for boolean operation (#608)
* Apparent infinite loop case for boolean operation

This comes from the FAA Generic Twin example model included with BRL-CAD
- the glb files are written out from the input data structures that
triggered the infinite loop behavior.

* Fix formatting

* disabled GenericTwinBooleanTest7863

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
Co-authored-by: Emmett Lalish <elalish@gmail.com>
2023-11-12 15:23:37 -08:00
Clifford Yappandpca006132 fafe4792da Prepare a test input with failing boolean (#609)
* Prepare a test input with failing boolean

Encountered this case when testing Manifold with the BRL-CAD example
file - specifically, the r.glass8 region.  C++ exception "Vec out of
range" is thrown.  Both inputs are the outputs of previous Manifold
boolean evaluations.

* disabled Havocglass8Bool test

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
2023-11-12 14:30:26 -08:00
Emmett Lalish b439603eb9 Fix glTF validator errors (#596)
* fixed build copy for npm

* update deps

* fixed glTF validator errors

* fixed bug and Vite
2023-11-07 16:10:52 -08:00
Chun Kit LAM b6eea01021 fix cmake build error with thrust installed (#595) 2023-11-07 22:49:57 +08:00
Johnathon Selstad b9f7228064 [FIX] Packaged Split() output to Tuple (#593)
* Fix the Split Function

* Fix Formatting

* Also update split_by_plane

* Add Cube Splitting Example
2023-11-07 12:18:27 +08:00
Chun Kit LAM 0ef5f70434 fix wasm cmake problems (#591)
* fix cmake problems

* update emsdk version
2023-11-07 09:38:06 +08:00
Chun Kit LAM bb6acf1f6e V2.2.1 (#589)
* added notes about twistDegree and nDivisions

* update version

* removed perf comparison figure

it is pretty outdated considering we don't have the GPU backend anymore
and performance is different now.
2023-11-02 22:00:27 -07:00
Emmett Lalish 5930b5c09d Fix NaN halfedgeTangents (#588)
fixed CreateTangents
2023-11-02 12:35:44 -07:00
Emmett Lalish a842de97c6 Fix properties (#587)
* fix testing

* added test

* add comments

* tests pass

* cleanup
2023-11-02 12:07:58 -07:00
Chun Kit LAM 2942229b49 push to pypi (#583)
* push to pypi

* update action name
2023-10-31 11:13:02 +08:00
54 changed files with 4337 additions and 3342 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
with:
path: dist/*.tar.gz
pypi-publish-beta:
pypi-publish:
name: Upload release to PyPI
needs: [build_wheels, make_sdist]
runs-on: ubuntu-latest
+4 -1
View File
@@ -18,4 +18,7 @@ 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 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 libthrust-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 latest
./emsdk activate latest
./emsdk install 3.1.45
./emsdk activate 3.1.45
- uses: jwlawson/actions-setup-cmake@v1.12
- name: Build WASM
run: |
+25
View File
@@ -0,0 +1,25 @@
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}}
+7 -5
View File
@@ -1,14 +1,12 @@
[![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.
@@ -29,7 +27,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, 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 ([manifold3d](https://pypi.org/project/manifold3d/)), 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.
@@ -87,6 +85,10 @@ 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)
target_link_libraries(manifold3d PRIVATE manifold sdf)
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")
+42 -26
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,48 +32,64 @@ 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)
+7 -4
View File
@@ -13,18 +13,21 @@
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
+12 -4
View File
@@ -11,7 +11,9 @@ 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)
@@ -19,17 +21,23 @@ 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
@@ -0,0 +1,59 @@
"""
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,10 +24,9 @@ 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)
@@ -41,9 +40,10 @@ 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
@@ -0,0 +1,80 @@
"""
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
@@ -0,0 +1,23 @@
"""
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]
+13 -12
View File
@@ -4,20 +4,21 @@ 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)
+2 -1
View File
@@ -1,7 +1,8 @@
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
+90 -10
View File
@@ -24,6 +24,7 @@
#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> {
@@ -276,7 +277,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)) {
@@ -303,6 +304,28 @@ 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"),
@@ -312,8 +335,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(
@@ -397,16 +420,25 @@ 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::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",
[](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_by_plane",
[](Manifold &self, Float3 normal, float originOffset) {
return self.SplitByPlane(
auto p = 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."
@@ -451,7 +483,17 @@ NB_MODULE(manifold3d, m) {
"Gets the manifold bounding box as a tuple "
"(xmin, ymin, zmin, xmax, ymax, zmax).")
.def_static(
"smooth", [](const Mesh &mesh) { return Manifold::Smooth(mesh); },
"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"),
"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 "
@@ -685,7 +727,45 @@ 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_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");
nb::enum_<Manifold::Error>(m, "Error")
.value("NoError", Manifold::Error::NoError)
+9 -1
View File
@@ -32,13 +32,21 @@ 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}/manifold*.d.ts
${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}/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
@@ -1,285 +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} 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
@@ -0,0 +1,424 @@
// 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.1.1/model-viewer.min.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.3.0/model-viewer.min.js"></script>
</html>
</html>
+14 -15
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/2.0.1/model-viewer.min.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">
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 } from './gltf-io.js';
import { readMesh, writeMesh, setupIO, disposeMesh } from './gltf-io';
import Module from './built/manifold.js';
const io = setupIO(new WebIO());
@@ -147,31 +147,30 @@
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 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(tmpMesh);
mesh.dispose();
const tmp = readMesh(mesh);
const matAttributes = [];
materials.forEach(() => matAttributes.push(attributes));
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 manifoldMesh = new Mesh(tmp.mesh);
disposeMesh(mesh);
manifoldMesh.merge();
try {
// Test manifoldness - will throw if not.
const manifold = Manifold(manifoldMesh);
node.setMesh(writeMesh(docIn, manifold.getMesh(), matAttributes, materials));
node.setMesh(writeMesh(docIn, manifold.getMesh(), id2properties));
manifold.delete();
anyManifold = true;
} catch (e) {
@@ -12,15 +12,32 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Extension, ExtensionProperty, PropertyType} from '@gltf-transform/core';
import {Accessor, Extension, ExtensionProperty, GLTF, IProperty, PropertyType, ReaderContext, WriterContext} from '@gltf-transform/core';
const NAME = 'EXT_manifold';
const MERGE = 'MERGE';
export class ManifoldPrimitive extends ExtensionProperty {
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> {
static EXTENSION_NAME = NAME;
public declare extensionName: typeof NAME;
public declare propertyType: 'ManifoldPrimitive';
public declare parentTypes: [PropertyType.MESH];
init() {
this.EXTENSION_NAME = NAME;
ManifoldPrimitive.EXTENSION_NAME = NAME;
this.propertyType = 'ManifoldPrimitive';
this.parentTypes = [PropertyType.MESH];
}
@@ -39,7 +56,7 @@ export class ManifoldPrimitive extends ExtensionProperty {
return this.getRef('mergeValues');
}
setMerge(indicesAccessor, valuesAccessor) {
setMerge(indicesAccessor: Accessor, valuesAccessor: Accessor) {
if (indicesAccessor.getCount() !== valuesAccessor.getCount())
throw new Error('merge vectors must be the same length.');
this.setRef('mergeIndices', indicesAccessor);
@@ -50,28 +67,29 @@ export class ManifoldPrimitive extends ExtensionProperty {
return this.get('runIndex');
}
setRunIndex(runIndex) {
setRunIndex(runIndex: number[]|Uint32Array) {
return this.set('runIndex', runIndex);
}
setIndices(indices) {
setIndices(indices: Accessor) {
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) {
read(context: ReaderContext) {
const {json} = context.jsonDoc;
const meshDefs = json.meshes || [];
@@ -82,20 +100,27 @@ export class EXTManifold extends Extension {
const manifoldPrimitive = this.createManifoldPrimitive();
mesh.setExtension(NAME, manifoldPrimitive);
const manifoldDef = meshDef.extensions[NAME];
const manifoldDef = meshDef.extensions[NAME] as ManifoldDef;
if (manifoldDef.manifoldPrimitive) {
let count = 0;
const runIndex = [];
runIndex.push(count);
for (const primitive of mesh.listPrimitives()) {
count += primitive.getIndices().getCount();
const indices = primitive.getIndices();
if (!indices) {
console.log('Skipping non-indexed primitive ', primitive.getName());
continue;
}
count += indices.getCount();
runIndex.push(count);
}
manifoldPrimitive.setRunIndex(runIndex);
manifoldPrimitive.setIndices(
context.accessors[manifoldDef.manifoldPrimitive.indices!]);
}
if (manifoldDef.mergeIndices && manifoldDef.mergeValues) {
if (manifoldDef.mergeIndices != null && manifoldDef.mergeValues != null) {
manifoldPrimitive.setMerge(
context.accessors[manifoldDef.mergeIndices],
context.accessors[manifoldDef.mergeValues]);
@@ -105,15 +130,34 @@ export class EXTManifold extends Extension {
return this;
}
write(context) {
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) {
const {json} = context.jsonDoc;
this.document.getRoot().listMeshes().forEach((mesh) => {
const manifoldPrimitive = mesh.getExtension(NAME);
const manifoldPrimitive = mesh.getExtension(NAME) as ManifoldPrimitive;
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;
@@ -124,20 +168,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;
}
@@ -146,21 +190,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];
}
+17 -21
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/2.0.1/model-viewer.min.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">
import { Document, WebIO } from '@gltf-transform/core';
import { clearNodeTransform, flatten, prune } from '@gltf-transform/functions';
import { loadTexture, readMesh, writeMesh, disposeMesh, setupIO } from './gltf-io.js';
import { loadTexture, readMesh, writeMesh, disposeMesh, setupIO } from './gltf-io';
import Module from './built/manifold.js';
const io = setupIO(new WebIO());
@@ -63,10 +63,9 @@
const { Manifold, Mesh } = wasm;
const attributes = ['POSITION', 'TEXCOORD_0'];
const materialMap = new Map();
const id2properties = new Map();
async function readGLB(url, attributes, materialMap) {
async function readGLB(url) {
const manifolds = [];
const docIn = await io.read(url);
await docIn.transform(flatten());
@@ -75,30 +74,33 @@
for (const node of nodes) {
clearNodeTransform(node);
const materials = [];
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);
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);
ids.push(firstID + i);
id2properties.set(firstID + i, tmp.runProperties[i]);
}
manifolds.push(Manifold(new Mesh(manifoldMesh)));
manifolds.push(Manifold(new Mesh(tmp.mesh)));
}
// 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];
materialMap.set(id, material);
id2properties.get(id).material = material;
}
return Manifold.union(manifolds);
}
const space = await readGLB('/models/space.glb', attributes, materialMap);
const moon = await readGLB('/models/moon.glb', attributes, materialMap);
const space = await readGLB('/models/space.glb');
const moon = await readGLB('/models/moon.glb');
const node = doc.createNode();
doc.createScene().addChild(node);
@@ -129,13 +131,7 @@
const manifoldMesh = manifold.rotate([-90, 0, 0]).getMesh();
disposeMesh(node.getMesh());
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);
const mesh = writeMesh(doc, manifoldMesh, id2properties);
node.setMesh(mesh);
await doc.transform(prune());
+724 -788
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",
"test": "vitest run",
"test:ui": "vitest --ui",
"test:run": "vitest run"
"test:dev": "vitest"
},
"dependencies": {
"@gltf-transform/core": "^3.2.1",
"@gltf-transform/extensions": "^3.2.1",
"@gltf-transform/functions": "^3.2.1",
"@gltf-transform/core": "^3.8.0",
"@gltf-transform/extensions": "^3.8.0",
"@gltf-transform/functions": "^3.8.0",
"@jscadui/3mf-export": "^0.4.2",
"fflate": "^0.8.0",
"gl-matrix": "^3.4.3",
"simple-dropzone": "0.8.3",
"three": "0.151.2"
"three": "0.158.0"
},
"devDependencies": {
"@vitest/ui": "^0.31.1",
"@vitest/web-worker": "^0.31.1",
"typescript": "5.0.4",
"vite": "^4.3.2",
"typescript": "5.2.2",
"vite": "^4.5.0",
"vitest": "^0.31.1"
}
}
+4 -1
View File
@@ -25,8 +25,11 @@ 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?: string[];
attributes?: Attribute[];
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;
}
+5 -8
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.js';
import {readMesh, setupIO} from './gltf-io';
import {examples} from './public/examples.js';
import ManifoldWorker from './worker?worker';
@@ -63,17 +63,14 @@ async function runExample(name) {
const docIn = await io.read(glbURL);
const nodes = docIn.getRoot().listNodes();
for (const node of nodes) {
const mesh = node.getMesh();
if (!mesh) {
const docMesh = node.getMesh();
if (!docMesh) {
continue;
}
const attributes = [];
const materials = [];
const manifoldMesh = readMesh(mesh, attributes, materials);
const manifold = wasm.Manifold(manifoldMesh);
const {mesh} = readMesh(docMesh);
const manifold = wasm.Manifold(mesh);
const prop = manifold.getProperties();
const genus = manifold.genus();
console.log(genus);
manifold.delete();
resolve({...prop, genus});
}
+16 -22
View File
@@ -20,7 +20,7 @@ import * as glMatrix from 'gl-matrix';
import Module from './built/manifold';
//@ts-ignore
import {setupIO, writeMesh} from './gltf-io';
import {Attribute, Properties, setupIO, writeMesh} from './gltf-io';
import type {GLTFMaterial, Quat} from './public/editor';
import type {CrossSection, Manifold, ManifoldToplevel, Mesh, Vec3} from './public/manifold';
@@ -373,19 +373,16 @@ function addMesh(
// From Z-up to Y-up (glTF)
const manifoldMesh = manifold.getMesh();
const materials = new Array<GLTFMaterial>();
const attributes = new Array<Array<string>>();
const id2properties = new Map<number, Properties>();
for (const id of manifoldMesh.runOriginalID!) {
const material = id2material.get(id) || backupMaterial;
materials.push(material);
attributes.push(['POSITION', ...material.attributes ?? []]);
id2properties.set(id, {
material: getCachedMaterial(doc, ghost ? GHOST : material),
attributes: ['POSITION', ...material.attributes ?? []]
});
}
const gltfMaterials = materials.map((matDef) => {
return getCachedMaterial(doc, ghost ? GHOST : matDef);
});
node.setMesh(writeMesh(doc, manifoldMesh, attributes, gltfMaterials));
node.setMesh(writeMesh(doc, manifoldMesh, id2properties));
const vertices = manifoldMesh.numProp === 3 ?
manifoldMesh.vertProperties :
@@ -402,21 +399,18 @@ function addMesh(
{vertices, indices: manifoldMesh.triVerts, id: `${nextGlobalID++}`});
for (const [run, id] of manifoldMesh.runOriginalID!.entries()) {
let inMesh = shown.get(id);
let single = false;
if (inMesh == null) {
single = true;
inMesh = singles.get(id);
}
const show = shown.has(id);
const inMesh = show ? shown.get(id) : singles.get(id);
if (inMesh == null) {
continue;
}
const mat = single ? materials[run] : SHOW;
const debugNode =
doc.createNode('debug')
.setMesh(writeMesh(
doc, inMesh, attributes, [getCachedMaterial(doc, mat)]))
.setMatrix(manifoldMesh.transform(run));
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));
node.addChild(debugNode);
}
}
+16 -14
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Box, FillRule, JoinType, Mat4, Polygons, Properties, Rect, SealedFloat32Array, SealedUint32Array, SimplePolygon, Smoothness, Vec2, Vec3} from './manifold-global-types';
import {Box, FillRule, JoinType, Mat3, Mat4, Polygons, Properties, Rect, SealedFloat32Array, SealedUint32Array, SimplePolygon, Smoothness, Vec2, Vec3} from './manifold-global-types';
/**
* Triangulates a set of /epsilon-valid polygons.
@@ -838,19 +838,7 @@ export class Manifold {
delete(): void;
}
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
});
export interface MeshOptions {
numProp: number;
vertProperties: Float32Array;
triVerts: Uint32Array;
@@ -861,6 +849,20 @@ export class Mesh {
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,6 +22,18 @@ 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.1.0",
"version": "2.2.2",
"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.0";
version = "2.2.2";
src = self;
nativeBuildInputs = (with pkgs; [
cmake
@@ -103,7 +103,7 @@
parallelBackends)) // {
manifold-js = pkgs.buildEmscriptenPackage {
name = "manifold-js";
version = "beta";
version = "2.2.2";
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.0";
version = "2.2.2";
src = self;
propagatedBuildInputs = [
numpy
+1
View File
@@ -234,6 +234,7 @@ 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.0"
version = "2.2.2"
authors = [
{ name="Emmett Lalish", email="elalish@gmail.com" },
]
description = "Library for geometric robustness"
readme = "README.md"
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: C++",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

+40 -26
View File
@@ -463,9 +463,8 @@ struct MapTriRef {
}
};
Vec<TriRef> UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
const Manifold::Impl &inQ, bool invertQ) {
Vec<TriRef> refPQ = outR.meshRelation_.triRef;
void UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
const Manifold::Impl &inQ, bool invertQ) {
const int offsetQ = Manifold::Impl::meshIDCounter_;
for_each_n(
autoPolicy(outR.NumTri()), outR.meshRelation_.triRef.begin(),
@@ -480,7 +479,6 @@ Vec<TriRef> UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
outR.meshRelation_.meshIDtransform[pair.first + offsetQ].backSide ^=
invertQ;
}
return refPQ;
}
struct Barycentric {
@@ -514,8 +512,8 @@ struct Barycentric {
}
};
void CreateProperties(Manifold::Impl &outR, const Vec<TriRef> &refPQ,
const Manifold::Impl &inP, const Manifold::Impl &inQ) {
void CreateProperties(Manifold::Impl &outR, 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);
@@ -526,7 +524,8 @@ void CreateProperties(Manifold::Impl &outR, const Vec<TriRef> &refPQ,
outR.meshRelation_.triProperties.resize(numTri);
Vec<glm::vec3> bary(outR.halfedge_.size());
for_each_n(autoPolicy(numTri), zip(countAt(0), refPQ.cbegin()), numTri,
for_each_n(autoPolicy(numTri),
zip(countAt(0), outR.meshRelation_.triRef.cbegin()), numTri,
Barycentric({bary, inP.vertPos_, inQ.vertPos_, outR.vertPos_,
inP.halfedge_, inQ.halfedge_, outR.halfedge_,
outR.precision_}));
@@ -534,6 +533,9 @@ void CreateProperties(Manifold::Impl &outR, const Vec<TriRef> &refPQ,
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;
@@ -541,14 +543,14 @@ void CreateProperties(Manifold::Impl &outR, const Vec<TriRef> &refPQ,
// Skip collapsed triangles
if (outR.halfedge_[3 * tri].startVert < 0) continue;
const int triPQ = refPQ[tri].tri;
const bool PQ = refPQ[tri].meshID == 0;
const TriRef ref = outR.meshRelation_.triRef[tri];
const bool PQ = ref.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[triPQ]
: inQ.meshRelation_.triProperties[triPQ];
: PQ ? inP.meshRelation_.triProperties[ref.tri]
: inQ.meshRelation_.triProperties[ref.tri];
for (const int i : {0, 1, 2}) {
const int vert = outR.halfedge_[3 * tri + i].startVert;
@@ -556,8 +558,7 @@ void CreateProperties(Manifold::Impl &outR, const Vec<TriRef> &refPQ,
glm::ivec4 key(PQ, idMissProp, -1, -1);
if (oldNumProp > 0) {
key[1] = vert;
int edge = -1;
int edge = -2;
for (const int j : {0, 1, 2}) {
if (uvw[j] == 1) {
// On a retained vert, the propVert must also match
@@ -571,24 +572,37 @@ void CreateProperties(Manifold::Impl &outR, const Vec<TriRef> &refPQ,
// 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;
}
}
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 (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;
}
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;
@@ -770,12 +784,12 @@ Manifold::Impl Boolean3::Result(OpType op) const {
if (ManifoldParams().intermediateChecks)
ASSERT(outR.IsManifold(), logicErr, "triangulated mesh is not manifold!");
Vec<TriRef> refPQ = UpdateReference(outR, inP_, inQ_, invertQ);
CreateProperties(outR, inP_, inQ_);
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,6 +220,9 @@ 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.
+14 -15
View File
@@ -178,6 +178,10 @@ void Manifold::Impl::SimplifyTopology() {
}
#endif
if (!ManifoldParams().cleanupTriangles) {
return;
}
std::vector<int> scratchBuffer;
scratchBuffer.reserve(10);
{
@@ -501,25 +505,20 @@ 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);
// 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];
}
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];
}
}
+6 -3
View File
@@ -196,10 +196,13 @@ struct SmoothBezier {
const glm::vec3 edgeNormal =
(triNormal[edge.face] + triNormal[halfedge[edge.pairedHalfedge].face]) /
2.0f;
glm::vec3 dir = glm::normalize(glm::cross(glm::cross(edgeNormal, edgeVec),
vertNormal[edge.startVert]));
glm::vec3 dir = SafeNormalize(glm::cross(glm::cross(edgeNormal, edgeVec),
vertNormal[edge.startVert]));
const float weight = glm::abs(glm::dot(dir, glm::normalize(edgeVec)));
float weight = glm::abs(glm::dot(dir, SafeNormalize(edgeVec)));
if (weight == 0) {
weight = 1;
}
// 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 (area < -minArea) {
if (glm::isfinite(maxX) && area < -minArea) {
holes_.insert(start);
hole2BBox_.insert({start, bBox});
} else {
+10 -8
View File
@@ -32,9 +32,11 @@ FetchContent_Declare(Thrust
GIT_TAG 2.1.0
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
FIND_PACKAGE_ARGS NAMES Thrust
)
FetchContent_MakeAvailable(Thrust)
find_package(Thrust QUIET)
if(NOT Thrust_FOUND AND NOT DEFINED thrust_SOURCE_DIR)
FetchContent_Populate(Thrust)
endif()
if (TRACY_ENABLE)
include(FetchContent)
@@ -66,7 +68,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)
@@ -84,15 +86,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_ROOT}
${THRUST_ROOT}/dependencies/libcudacxx/include
${_THRUST_INCLUDE_DIR}
${_THRUST_INCLUDE_DIR}/dependencies/libcudacxx/include
)
endif()
+2
View File
@@ -613,6 +613,8 @@ 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
+5 -1
View File
@@ -48,7 +48,11 @@ class SparseIndices {
}
SparseIndices() = default;
SparseIndices(size_t size) : data_(size * sizeof(int64_t)) {}
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));
}
int size() const { return data_.size() / sizeof(int64_t); }
+451 -2
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, 5);
Manifold cylinder2 = cylinder.Scale({0.8f, 0.8f, 1.0f}).Rotate(0, 0, 185);
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, 5);
Manifold cylinder2 = cylinder.Scale({0.8f, 0.8f, 1.0f}).Rotate(0, 0, 185);
Manifold out = cylinder - cylinder2;
ExpectMeshes(out, {{52, 104, 3, 88}});
EXPECT_EQ(out.NumDegenerateTris(), 0);
@@ -902,3 +902,452 @@ 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,6 +543,51 @@ 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,6 +266,28 @@ 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,3 +115,21 @@ 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 * 100 * out.Precision());
ASSERT_LE(volume, area * 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 * 100 * out.Precision());
ASSERT_LE(volumeP, area * out.Precision());
}
}
}