Improve gltf-io and manifold-gltf (#612)
* fixed build copy for npm * update deps * fixed glTF validator errors * fixed bug and Vite * clamped color * converted manifold-gltf to TS * converted gltf-io to TS * fixed TS build * avoid usage warnings * update MV * clean up types * update gltf-io API * fixed bugs * fixed disposal * fixed example * a few fixes and cleanup
This commit is contained in:
@@ -1,290 +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 id = manifoldMesh.runOriginalID[run];
|
||||
const indices = doc.createAccessor('index run of ID: ' + id)
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(1));
|
||||
const primitive =
|
||||
doc.createPrimitive('primitive of ID: ' + id).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(attribute)
|
||||
.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('manifold 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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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,16 +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];
|
||||
}
|
||||
@@ -40,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);
|
||||
@@ -51,16 +67,16 @@ 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')!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +89,7 @@ export class EXTManifold extends Extension {
|
||||
return new ManifoldPrimitive(this.document.getGraph());
|
||||
}
|
||||
|
||||
read(context) {
|
||||
read(context: ReaderContext) {
|
||||
const {json} = context.jsonDoc;
|
||||
const meshDefs = json.meshes || [];
|
||||
|
||||
@@ -84,17 +100,24 @@ 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 != null && manifoldDef.mergeValues != null) {
|
||||
@@ -107,10 +130,15 @@ export class EXTManifold extends Extension {
|
||||
return this;
|
||||
}
|
||||
|
||||
prewrite(context) {
|
||||
prewrite(context: WriterContext) {
|
||||
this.document.getRoot().listMeshes().forEach((mesh) => {
|
||||
const manifoldPrimitive = mesh.getExtension(NAME);
|
||||
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;
|
||||
@@ -121,15 +149,15 @@ export class EXTManifold extends Extension {
|
||||
return this;
|
||||
}
|
||||
|
||||
write(context) {
|
||||
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;
|
||||
@@ -140,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;
|
||||
}
|
||||
@@ -162,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());
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
"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.8.0",
|
||||
|
||||
+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,
|
||||
|
||||
Reference in New Issue
Block a user