Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Runs example #805

Merged
merged 7 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
added docs
  • Loading branch information
elalish committed May 10, 2024
commit c01f5df49a9fd5a766cfde55cba5412dc7d77f5e
10 changes: 8 additions & 2 deletions bindings/wasm/examples/three.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,14 @@
</head>

<body>
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
involving GLTF export and &lt;model-viewer&gt; can be found <a href="model-viewer.html">here</a>. </p>
<p>This example demonstrates interop between Manifold and Three.js with minimal code - please open dev tools to
inspect the source. Here we generate two multi-material Three.js meshes and convert them to Manifolds while building
a mapping from material to Manifold ID. Then Boolean operations are performed on the Manifolds and the result is
converted back to a Three.js mesh using the material mapping. The input cube has half red faces and half
normal-shaded, while the icosahedron has half blue faces and half normal-shaded. The resulting mesh has three
materials, since one (normal-shaded) was common between the input models.</p>
<p>A slightly more complex example involving our library for GLTF import/export and &lt;model-viewer&gt; can be found
<a href="model-viewer.html">here</a>. </p>
<select>
<option value="union" selected>Union</option>
<option value="difference">Difference</option>
Expand Down
103 changes: 63 additions & 40 deletions bindings/wasm/examples/three.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,96 +16,119 @@ import {BoxGeometry, BufferAttribute, BufferGeometry, IcosahedronGeometry, Mesh

import Module, {Mesh} from './built/manifold.js';

type BooleanOp = 'union'|'difference'|'intersection';

// Load Manifold WASM library
const wasm = await Module();
wasm.setup();

const {Manifold, Mesh} = wasm;

// we have manifold module, let's do some three.js
const camera = new PerspectiveCamera(30, 1, 0.01, 10);
camera.position.z = 1;

const scene = new Scene();
scene.add(camera);
camera.add(new PointLight(0xffffff, 1));

// Define our set of materials
const materials = [
new MeshNormalMaterial({flatShading: true}),
new MeshLambertMaterial({color: 'red', flatShading: true}),
new MeshLambertMaterial({color: 'blue', flatShading: true})
];
const result = new ThreeMesh(undefined, materials);

// Set up Manifold IDs corresponding to materials
const firstID = Manifold.reserveIDs(materials.length);
// ids vector is parallel to materials vector - same indexing
const ids = [...Array<number>(materials.length)].map((_, idx) => firstID + idx);
// Build a mapping to get back from ID to material index
const id2matIndex = new Map();
ids.forEach((id, idx) => id2matIndex.set(id, idx));

const result = new ThreeMesh(undefined, materials);
// Set up Three.js scene
const scene = new Scene();
const camera = new PerspectiveCamera(30, 1, 0.01, 10);
camera.position.z = 1;
camera.add(new PointLight(0xffffff, 1));
scene.add(camera);
scene.add(result);

// Set up Three.js renderer
const output = document.querySelector('#output')!;
const renderer = new WebGLRenderer({canvas: output, antialias: true});
const dim = output.getBoundingClientRect();
renderer.setSize(dim.width, dim.height);
renderer.setAnimationLoop(function(time: number) {
result.rotation.x = time / 2000;
result.rotation.y = time / 1000;
renderer.render(scene, camera);
});

// Create input meshes in Three.js
const cube = new BoxGeometry(0.2, 0.2, 0.2);
cube.clearGroups();
cube.addGroup(0, 18, 0);
cube.addGroup(18, Infinity, 1);
cube.addGroup(0, 18, 0); // First 6 faces colored by normal
cube.addGroup(18, Infinity, 1); // Rest of faces are red

const icosahedron = new IcosahedronGeometry(0.16);
icosahedron.clearGroups();
icosahedron.addGroup(0, 30, 0);
icosahedron.addGroup(30, Infinity, 2);
icosahedron.addGroup(0, 30, 0); // First 10 faces colored by normal
icosahedron.addGroup(30, Infinity, 2); // Rest of faces are blue

// Convert Three.js input meshes to Manifolds
const manifoldCube = new Manifold(geometry2mesh(cube));
const manifoldIcosahedron = new Manifold(geometry2mesh(icosahedron));

const manifold_1 = new Manifold(geometry2mesh(cube));
const manifold_2 = new Manifold(geometry2mesh(icosahedron));
// Set up UI for operations
type BooleanOp = 'union'|'difference'|'intersection';

const csg = function(operation: BooleanOp) {
function csg(operation: BooleanOp) {
result.geometry?.dispose();
result.geometry =
mesh2geometry(Manifold[operation](manifold_1, manifold_2).getMesh());
};
result.geometry = mesh2geometry(
Manifold[operation](manifoldCube, manifoldIcosahedron).getMesh());
}

csg('union');
const selectElement = document.querySelector('select') as HTMLSelectElement;

selectElement.onchange = function() {
csg(selectElement.value as BooleanOp);
};

csg('union');

const output = document.querySelector('#output')!;
const renderer = new WebGLRenderer({canvas: output, antialias: true});
const dim = output.getBoundingClientRect();
renderer.setSize(dim.width, dim.height);
renderer.setAnimationLoop(function(time: number) {
result.rotation.x = time / 2000;
result.rotation.y = time / 1000;
renderer.render(scene, camera);
});

// functions to convert between three.js and wasm
// Convert Three.js BufferGeometry to Manifold Mesh
function geometry2mesh(geometry: BufferGeometry) {
// Only using position in this sample for simplicity. Can interleave any other
// desired attributes here such as UV, normal, etc.
const vertProperties = geometry.attributes.position.array as Float32Array;
// Manifold only uses indexed geometry, so generate an index if necessary.
const triVerts = geometry.index != null ?
geometry.index.array as Uint32Array :
new Uint32Array(vertProperties.length / 3).map((_, idx) => idx);
const runIndex = new Uint32Array(geometry.groups.length + 1)
.map((_, idx) => geometry.groups[idx]?.start ?? 0);
runIndex.set([triVerts.length], runIndex.length - 1);
// Create a triangle run for each group (material).
const runIndex =
new Uint32Array(geometry.groups.length + 1)
.map((_, idx) => geometry.groups[idx]?.start ?? triVerts.length);
// Map the materials to ID
const runOriginalID =
new Uint32Array(geometry.groups.length)
.map((_, idx) => ids[geometry.groups[idx].materialIndex!]);

const mesh =
new Mesh({numProp: 3, vertProperties, triVerts, runIndex, runOriginalID});
// Automatically merge vertices with nearly identical positions to create a
// Manifold. This only fills in the mergeFromVert and mergeToVert vectors -
// these are automatically filled in for any mesh returned by Manifold. These
// are necessary because GL drivers require duplicate verts when any
// properties change, e.g. a UV boundary or sharp corner.
mesh.merge();
return mesh;
}

// Convert Manifold Mesh to Three.js BufferGeometry
function mesh2geometry(mesh: Mesh) {
const geometry = new BufferGeometry();
// Assign buffers
geometry.setAttribute(
'position', new BufferAttribute(mesh.vertProperties, 3));
geometry.setIndex(new BufferAttribute(mesh.triVerts, 1));

// Create a group (material) for each ID. Note that there may be multiple
// triangle runs returned with the same ID, though these will always be
// sequential since they are sorted by ID. In this example there are two runs
// for the MeshNormalMaterial, one corresponding to each input mesh that had
// this ID. This allows runTransform to return the total transformation matrix
// applied to each triangle run from its input mesh - even after many
// consecutive operations.
let id = mesh.runOriginalID[0];
let start = mesh.runIndex[0];
for (let run = 0; run < mesh.numRun; ++run) {
Expand Down
Loading