Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01c38e42ca | ||
|
|
1991232eb7 | ||
|
|
4551288f95 | ||
|
|
2e29784449 | ||
|
|
2a2cb6f88e | ||
|
|
1a91d8db2a | ||
|
|
82b98d9353 | ||
|
|
7273c9bf0f | ||
|
|
4ff3f7cf68 | ||
|
|
fafe4792da | ||
|
|
b439603eb9 | ||
|
|
b6eea01021 | ||
|
|
b9f7228064 | ||
|
|
0ef5f70434 | ||
|
|
bb6acf1f6e | ||
|
|
5930b5c09d | ||
|
|
a842de97c6 | ||
|
|
2942229b49 |
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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}}
|
||||
@@ -1,14 +1,12 @@
|
||||
[](https://codecov.io/github/elalish/manifold)
|
||||
[](https://badge.fury.io/py/manifold3d)
|
||||
[](https://badge.fury.io/js/manifold-3d)
|
||||
[](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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
+2007
-2005
File diff suppressed because it is too large
Load Diff
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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> <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>
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
Generated
+724
-788
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "manifold-3d",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.2",
|
||||
"description": "Geometry library for topological robustness",
|
||||
"main": "manifold.js",
|
||||
"files": [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 |
@@ -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!");
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 *
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user