Skip to content

Commit 443863d

Browse files
authored
Runs example (#805)
* switched three example to TS * use merge * cleanup * material groups * added groups and ids * working * added docs
1 parent 5301542 commit 443863d

File tree

5 files changed

+194
-77
lines changed

5 files changed

+194
-77
lines changed

bindings/wasm/examples/package-lock.json

+36-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindings/wasm/examples/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@gltf-transform/extensions": "^3.8.0",
1818
"@gltf-transform/functions": "^3.8.0",
1919
"@jscadui/3mf-export": "^0.5.0",
20+
"@types/three": "^0.164.0",
2021
"fflate": "^0.8.0",
2122
"gl-matrix": "^3.4.3",
2223
"simple-dropzone": "0.8.3",
@@ -29,4 +30,4 @@
2930
"vite": "^4.5.0",
3031
"vitest": "^0.31.1"
3132
}
32-
}
33+
}

bindings/wasm/examples/three.html

+11-73
Original file line numberDiff line numberDiff line change
@@ -35,83 +35,21 @@
3535
</head>
3636

3737
<body>
38-
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
39-
involving GLTF export and &lt;model-viewer&gt; can be found <a href="model-viewer.html">here</a>. </p>
38+
<p>This example demonstrates interop between Manifold and Three.js with minimal code - please open dev tools to
39+
inspect the source. Here we generate two multi-material Three.js meshes and convert them to Manifolds while building
40+
a mapping from material to Manifold ID. Then Boolean operations are performed on the Manifolds and the result is
41+
converted back to a Three.js mesh using the material mapping. The input cube has half red faces and half
42+
normal-shaded, while the icosahedron has half blue faces and half normal-shaded. The resulting mesh has three
43+
materials, since one (normal-shaded) was common between the input models.</p>
44+
<p>A slightly more complex example involving our library for GLTF import/export and &lt;model-viewer&gt; can be found
45+
<a href="model-viewer.html">here</a>. </p>
4046
<select>
41-
<option value="difference" selected>Difference</option>
47+
<option value="union" selected>Union</option>
48+
<option value="difference">Difference</option>
4249
<option value="intersection">Intersection</option>
43-
<option value="union">Union</option>
4450
</select><br />
4551
<canvas id="output"></canvas>
4652
</body>
47-
<script type="module">
48-
import * as THREE from 'three';
49-
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
50-
import Module from './built/manifold.js';
51-
52-
const wasm = await Module();
53-
wasm.setup();
54-
55-
const { Manifold, Mesh } = wasm;
56-
57-
// we have manifold module, let's do some three.js
58-
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
59-
camera.position.z = 1;
60-
61-
const scene = new THREE.Scene();
62-
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
63-
flatShading: true
64-
}));
65-
scene.add(mesh);
66-
67-
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
68-
69-
const manifold_1 = Manifold.cube([0.2, 0.2, 0.2], true);
70-
const manifold_2 = new Manifold(geometry2mesh(icosahedron));
71-
72-
const csg = function (operation) {
73-
mesh.geometry?.dispose();
74-
mesh.geometry = mesh2geometry(Manifold[operation](manifold_1, manifold_2).getMesh());
75-
};
76-
77-
document.querySelector('select').onchange = function (event) {
78-
csg(event.target.value);
79-
};
80-
81-
csg('difference');
82-
83-
const output = document.querySelector("#output");
84-
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
85-
const dim = output.getBoundingClientRect();
86-
renderer.setSize(dim.width, dim.height);
87-
renderer.setAnimationLoop(function (time) {
88-
mesh.rotation.x = time / 2000;
89-
mesh.rotation.y = time / 1000;
90-
renderer.render(scene, camera);
91-
});
92-
93-
// functions to convert between three.js and wasm
94-
function geometry2mesh(geometry) {
95-
const vertProperties = geometry.attributes.position.array;
96-
const triVerts = geometry.index.array;
97-
return new Mesh({ numProp: 3, vertProperties, triVerts });
98-
}
99-
100-
function mesh2geometry(mesh) {
101-
const geometry = new THREE.BufferGeometry();
102-
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertProperties, 3));
103-
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
104-
return geometry;
105-
}
106-
107-
// most of three.js geometries aren't manifolds, so...
108-
function simplify(geometry) {
109-
delete geometry.attributes.normal;
110-
delete geometry.attributes.uv;
111-
const simplified = mergeVertices(geometry);
112-
simplified.computeVertexNormals();
113-
return simplified;
114-
}
115-
</script>
53+
<script type="module" src="three.ts"></script>
11654

11755
</html>

bindings/wasm/examples/three.ts

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright 2024 The Manifold Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {BoxGeometry, BufferAttribute, BufferGeometry, IcosahedronGeometry, Mesh as ThreeMesh, MeshLambertMaterial, MeshNormalMaterial, PerspectiveCamera, PointLight, Scene, WebGLRenderer} from 'three';
16+
17+
import Module, {Mesh} from './built/manifold.js';
18+
19+
// Load Manifold WASM library
20+
const wasm = await Module();
21+
wasm.setup();
22+
const {Manifold, Mesh} = wasm;
23+
24+
// Define our set of materials
25+
const materials = [
26+
new MeshNormalMaterial({flatShading: true}),
27+
new MeshLambertMaterial({color: 'red', flatShading: true}),
28+
new MeshLambertMaterial({color: 'blue', flatShading: true})
29+
];
30+
const result = new ThreeMesh(undefined, materials);
31+
32+
// Set up Manifold IDs corresponding to materials
33+
const firstID = Manifold.reserveIDs(materials.length);
34+
// ids vector is parallel to materials vector - same indexing
35+
const ids = [...Array<number>(materials.length)].map((_, idx) => firstID + idx);
36+
// Build a mapping to get back from ID to material index
37+
const id2matIndex = new Map();
38+
ids.forEach((id, idx) => id2matIndex.set(id, idx));
39+
40+
// Set up Three.js scene
41+
const scene = new Scene();
42+
const camera = new PerspectiveCamera(30, 1, 0.01, 10);
43+
camera.position.z = 1;
44+
camera.add(new PointLight(0xffffff, 1));
45+
scene.add(camera);
46+
scene.add(result);
47+
48+
// Set up Three.js renderer
49+
const output = document.querySelector('#output')!;
50+
const renderer = new WebGLRenderer({canvas: output, antialias: true});
51+
const dim = output.getBoundingClientRect();
52+
renderer.setSize(dim.width, dim.height);
53+
renderer.setAnimationLoop(function(time: number) {
54+
result.rotation.x = time / 2000;
55+
result.rotation.y = time / 1000;
56+
renderer.render(scene, camera);
57+
});
58+
59+
// Create input meshes in Three.js
60+
const cube = new BoxGeometry(0.2, 0.2, 0.2);
61+
cube.clearGroups();
62+
cube.addGroup(0, 18, 0); // First 6 faces colored by normal
63+
cube.addGroup(18, Infinity, 1); // Rest of faces are red
64+
65+
const icosahedron = new IcosahedronGeometry(0.16);
66+
icosahedron.clearGroups();
67+
icosahedron.addGroup(0, 30, 0); // First 10 faces colored by normal
68+
icosahedron.addGroup(30, Infinity, 2); // Rest of faces are blue
69+
70+
// Convert Three.js input meshes to Manifolds
71+
const manifoldCube = new Manifold(geometry2mesh(cube));
72+
const manifoldIcosahedron = new Manifold(geometry2mesh(icosahedron));
73+
74+
// Set up UI for operations
75+
type BooleanOp = 'union'|'difference'|'intersection';
76+
77+
function csg(operation: BooleanOp) {
78+
result.geometry?.dispose();
79+
result.geometry = mesh2geometry(
80+
Manifold[operation](manifoldCube, manifoldIcosahedron).getMesh());
81+
}
82+
83+
csg('union');
84+
const selectElement = document.querySelector('select') as HTMLSelectElement;
85+
selectElement.onchange = function() {
86+
csg(selectElement.value as BooleanOp);
87+
};
88+
89+
// Convert Three.js BufferGeometry to Manifold Mesh
90+
function geometry2mesh(geometry: BufferGeometry) {
91+
// Only using position in this sample for simplicity. Can interleave any other
92+
// desired attributes here such as UV, normal, etc.
93+
const vertProperties = geometry.attributes.position.array as Float32Array;
94+
// Manifold only uses indexed geometry, so generate an index if necessary.
95+
const triVerts = geometry.index != null ?
96+
geometry.index.array as Uint32Array :
97+
new Uint32Array(vertProperties.length / 3).map((_, idx) => idx);
98+
// Create a triangle run for each group (material).
99+
const runIndex =
100+
new Uint32Array(geometry.groups.length + 1)
101+
.map((_, idx) => geometry.groups[idx]?.start ?? triVerts.length);
102+
// Map the materials to ID
103+
const runOriginalID =
104+
new Uint32Array(geometry.groups.length)
105+
.map((_, idx) => ids[geometry.groups[idx].materialIndex!]);
106+
107+
const mesh =
108+
new Mesh({numProp: 3, vertProperties, triVerts, runIndex, runOriginalID});
109+
// Automatically merge vertices with nearly identical positions to create a
110+
// Manifold. This only fills in the mergeFromVert and mergeToVert vectors -
111+
// these are automatically filled in for any mesh returned by Manifold. These
112+
// are necessary because GL drivers require duplicate verts when any
113+
// properties change, e.g. a UV boundary or sharp corner.
114+
mesh.merge();
115+
return mesh;
116+
}
117+
118+
// Convert Manifold Mesh to Three.js BufferGeometry
119+
function mesh2geometry(mesh: Mesh) {
120+
const geometry = new BufferGeometry();
121+
// Assign buffers
122+
geometry.setAttribute(
123+
'position', new BufferAttribute(mesh.vertProperties, 3));
124+
geometry.setIndex(new BufferAttribute(mesh.triVerts, 1));
125+
// Create a group (material) for each ID. Note that there may be multiple
126+
// triangle runs returned with the same ID, though these will always be
127+
// sequential since they are sorted by ID. In this example there are two runs
128+
// for the MeshNormalMaterial, one corresponding to each input mesh that had
129+
// this ID. This allows runTransform to return the total transformation matrix
130+
// applied to each triangle run from its input mesh - even after many
131+
// consecutive operations.
132+
let id = mesh.runOriginalID[0];
133+
let start = mesh.runIndex[0];
134+
for (let run = 0; run < mesh.numRun; ++run) {
135+
const nextID = mesh.runOriginalID[run + 1];
136+
if (nextID !== id) {
137+
const end = mesh.runIndex[run + 1];
138+
geometry.addGroup(start, end - start, id2matIndex.get(id));
139+
id = nextID;
140+
start = end;
141+
}
142+
}
143+
return geometry;
144+
}

bindings/wasm/examples/vite.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default defineConfig({
99
},
1010
build: {
1111
target: 'esnext',
12+
sourcemap: true,
1213
rollupOptions: {
1314
input: {
1415
manifoldCAD: resolve(__dirname, 'index.html'),

0 commit comments

Comments
 (0)