forge-svf-utils
Version:
Utilities for working with Autodesk Forge SVF file format.
519 lines (518 loc) • 19.4 kB
JavaScript
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const path = __importStar(require("path"));
const fse = __importStar(require("fs-extra"));
const util_1 = require("util");
const BufferSizeLimit = 5 << 20;
const DefaultMaterial = {
pbrMetallicRoughness: {
baseColorFactor: [0.25, 0.25, 0.25, 1.0],
metallicFactor: 0.0,
roughnessFactor: 0.5
}
};
class Writer {
constructor() {
this.downloads = [];
this.manifest = {
asset: {
version: '2.0',
generator: 'forge-svf-utils',
copyright: '2019 (c) Autodesk'
},
buffers: [],
bufferViews: [],
accessors: [],
meshes: [],
materials: [],
nodes: [],
scenes: [],
textures: [],
images: [],
scene: -1
};
this.bufferStream = null;
this.bufferSize = 0;
this.baseDir = '';
}
write(svf, baseDir) {
this.baseDir = baseDir;
this.bufferStream = null;
this.bufferSize = 0;
this.manifest = {
asset: {
version: '2.0',
generator: 'forge-svf-utils',
copyright: '2019 (c) Autodesk'
},
buffers: [],
bufferViews: [],
accessors: [],
meshes: [],
materials: [],
nodes: [],
scenes: [],
textures: [],
images: [],
scene: -1
};
const manifestScenes = this.manifest.scenes;
manifestScenes.push(this.writeScene(svf));
this.manifest.scene = 0;
const gltfPath = path.join(this.baseDir, 'output.gltf');
fse.ensureDirSync(this.baseDir);
fse.writeFileSync(gltfPath, JSON.stringify(this.manifest, null, 4));
}
writeScene(svf) {
let scene = {
name: 'main',
nodes: []
};
const manifestNodes = this.manifest.nodes;
const manifestMaterials = this.manifest.materials;
for (const fragment of svf.fragments) {
const node = this.writeFragment(fragment, svf);
// Only output nodes that have a mesh
if (!util_1.isUndefined(node.mesh)) {
const index = manifestNodes.length;
manifestNodes.push(node);
scene.nodes.push(index);
}
}
for (const material of svf.materials) {
const mat = this.writeMaterial(material, svf);
manifestMaterials.push(mat);
}
return scene;
}
writeFragment(fragment, svf) {
let node = {
name: fragment.dbID.toString()
};
if (fragment.transform) {
const xform = fragment.transform;
if ('t' in xform) {
const t = xform.t;
node.translation = [t.x, t.y, t.z];
}
if ('s' in xform) {
const s = xform.s;
node.scale = [s.x, s.y, s.z];
}
if ('q' in xform) {
const q = xform.q;
node.rotation = [q.x, q.y, q.z, q.w];
}
if ('matrix' in xform) {
const m = xform.matrix;
const t = xform.t;
node.matrix = [
m[0], m[3], m[6], 0,
m[1], m[4], m[7], 0,
m[2], m[5], m[8], 0,
t.x, t.y, t.z, 1
]; // 4x4, column major
delete node.translation; // Translation is already included in the 4x4 matrix
}
}
const geometry = svf.geometries[fragment.geometryID];
const fragmesh = svf.meshpacks[geometry.packID][geometry.entityID];
const manifestMeshes = this.manifest.meshes;
if (fragmesh) {
let mesh;
if ('isLines' in fragmesh) {
mesh = this.writeLineGeometry(fragmesh, svf);
}
else if ('isPoints' in fragmesh) {
mesh = this.writePointGeometry(fragmesh, svf);
}
else {
mesh = this.writeMeshGeometry(fragmesh, svf);
}
node.mesh = manifestMeshes.length;
manifestMeshes.push(mesh);
for (const primitive of mesh.primitives) {
primitive.material = fragment.materialID;
}
}
else {
console.warn('Could not find mesh for fragment', fragment, 'geometry', geometry);
}
return node;
}
writeMeshGeometry(fragmesh, svf) {
let mesh = {
primitives: []
};
const manifestBuffers = this.manifest.buffers;
// Prepare new writable stream if needed
if (this.bufferStream === null || this.bufferSize > BufferSizeLimit) {
if (this.bufferStream) {
this.bufferStream.close();
this.bufferStream = null;
this.bufferSize = 0;
}
const bufferUri = `${manifestBuffers.length}.bin`;
manifestBuffers.push({ uri: bufferUri, byteLength: 0 });
this.bufferStream = fse.createWriteStream(path.join(this.baseDir, bufferUri));
}
const bufferID = manifestBuffers.length - 1;
const buffer = manifestBuffers[bufferID];
const bufferViews = this.manifest.bufferViews;
const accessors = this.manifest.accessors;
const hasUVs = fragmesh.uvmaps && fragmesh.uvmaps.length > 0;
const indexBufferViewID = bufferViews.length;
let indexBufferView = {
buffer: bufferID,
byteOffset: -1,
byteLength: -1
};
bufferViews.push(indexBufferView);
const positionBufferViewID = bufferViews.length;
let positionBufferView = {
buffer: bufferID,
byteOffset: -1,
byteLength: -1
};
bufferViews.push(positionBufferView);
const normalBufferViewID = bufferViews.length;
let normalBufferView = {
buffer: bufferID,
byteOffset: -1,
byteLength: -1
};
bufferViews.push(normalBufferView);
let uvBufferViewID, uvBufferView;
if (hasUVs) {
uvBufferViewID = bufferViews.length;
uvBufferView = {
buffer: bufferID,
byteOffset: -1,
byteLength: -1
};
bufferViews.push(uvBufferView);
}
const indexAccessorID = accessors.length;
let indexAccessor = {
bufferView: indexBufferViewID,
componentType: 5123,
count: -1,
type: 'SCALAR'
};
accessors.push(indexAccessor);
const positionAccessorID = accessors.length;
let positionAccessor = {
bufferView: positionBufferViewID,
componentType: 5126,
count: -1,
type: 'VEC3',
min: [fragmesh.min.x, fragmesh.min.y, fragmesh.min.z],
max: [fragmesh.max.x, fragmesh.max.y, fragmesh.max.z]
};
accessors.push(positionAccessor);
const normalAccessorID = accessors.length;
let normalAccessor = {
bufferView: normalBufferViewID,
componentType: 5126,
count: -1,
type: 'VEC3'
};
accessors.push(normalAccessor);
mesh.primitives.push({
attributes: {
POSITION: positionAccessorID,
NORMAL: normalAccessorID
},
indices: indexAccessorID
});
let uvAccessorID, uvAccessor;
if (hasUVs) {
uvAccessorID = accessors.length;
uvAccessor = {
bufferView: uvBufferViewID,
componentType: 5126,
count: -1,
type: 'VEC2'
};
accessors.push(uvAccessor);
mesh.primitives[0].attributes.TEXCOORD_0 = uvAccessorID;
}
// Indices
const indices = Buffer.from(fragmesh.indices.buffer);
this.bufferStream.write(indices);
this.bufferSize += indices.byteLength;
indexAccessor.count = indices.byteLength / 2;
indexBufferView.byteOffset = buffer.byteLength;
indexBufferView.byteLength = indices.byteLength;
buffer.byteLength += indices.byteLength;
if (buffer.byteLength % 4 !== 0) {
// Pad to 4-byte multiples
const pad = 4 - buffer.byteLength % 4;
this.bufferStream.write(new Uint8Array(pad));
this.bufferSize += pad;
buffer.byteLength += pad;
}
// Vertices
const vertices = Buffer.from(fragmesh.vertices.buffer);
this.bufferStream.write(vertices);
this.bufferSize += vertices.byteLength;
positionAccessor.count = vertices.byteLength / 4 / 3;
positionBufferView.byteOffset = buffer.byteLength;
positionBufferView.byteLength = vertices.byteLength;
buffer.byteLength += vertices.byteLength;
// Normals
if (fragmesh.normals) {
const normals = Buffer.from(fragmesh.normals.buffer);
this.bufferStream.write(normals);
this.bufferSize += normals.byteLength;
normalAccessor.count = normals.byteLength / 4 / 3;
normalBufferView.byteOffset = buffer.byteLength;
normalBufferView.byteLength = normals.byteLength;
buffer.byteLength += normals.byteLength;
}
// UVs (only the first UV map if there's one)
if (hasUVs && !util_1.isUndefined(uvAccessor) && !util_1.isUndefined(uvBufferView)) {
const uvs = Buffer.from(fragmesh.uvmaps[0].uvs.buffer);
this.bufferStream.write(uvs);
this.bufferSize += uvs.byteLength;
uvAccessor.count = uvs.byteLength / 4 / 2;
uvBufferView.byteOffset = buffer.byteLength;
uvBufferView.byteLength = uvs.byteLength;
buffer.byteLength += uvs.byteLength;
}
return mesh;
}
writeLineGeometry(fragmesh, svf) {
let mesh = {
primitives: []
};
const manifestBuffers = this.manifest.buffers;
// Prepare new writable stream if needed
if (this.bufferStream === null || this.bufferSize > BufferSizeLimit) {
if (this.bufferStream) {
this.bufferStream.close();
this.bufferStream = null;
this.bufferSize = 0;
}
const bufferUri = `${manifestBuffers.length}.bin`;
manifestBuffers.push({ uri: bufferUri, byteLength: 0 });
this.bufferStream = fse.createWriteStream(path.join(this.baseDir, bufferUri));
}
const bufferID = manifestBuffers.length - 1;
const buffer = manifestBuffers[bufferID];
const bufferViews = this.manifest.bufferViews;
const accessors = this.manifest.accessors;
const indexBufferViewID = bufferViews.length;
let indexBufferView = {
buffer: bufferID,
byteOffset: -1,
byteLength: -1
};
bufferViews.push(indexBufferView);
const positionBufferViewID = bufferViews.length;
let positionBufferView = {
buffer: bufferID,
byteOffset: -1,
byteLength: -1
};
bufferViews.push(positionBufferView);
const indexAccessorID = accessors.length;
let indexAccessor = {
bufferView: indexBufferViewID,
componentType: 5123,
count: -1,
type: 'SCALAR'
};
accessors.push(indexAccessor);
const positionAccessorID = accessors.length;
let positionAccessor = {
bufferView: positionBufferViewID,
componentType: 5126,
count: -1,
type: 'VEC3',
};
accessors.push(positionAccessor);
mesh.primitives.push({
mode: 1,
attributes: {
POSITION: positionAccessorID
},
indices: indexAccessorID
});
// Indices
const indices = Buffer.from(fragmesh.indices.buffer);
this.bufferStream.write(indices);
this.bufferSize += indices.byteLength;
indexAccessor.count = indices.byteLength / 2;
indexBufferView.byteOffset = buffer.byteLength;
indexBufferView.byteLength = indices.byteLength;
buffer.byteLength += indices.byteLength;
if (buffer.byteLength % 4 !== 0) {
// Pad to 4-byte multiples
const pad = 4 - buffer.byteLength % 4;
this.bufferStream.write(new Uint8Array(pad));
this.bufferSize += pad;
buffer.byteLength += pad;
}
// Vertices
const vertices = Buffer.from(fragmesh.vertices.buffer);
this.bufferStream.write(vertices);
this.bufferSize += vertices.byteLength;
positionAccessor.count = vertices.byteLength / 4 / 3;
positionBufferView.byteOffset = buffer.byteLength;
positionBufferView.byteLength = vertices.byteLength;
buffer.byteLength += vertices.byteLength;
// Colors, if available
if (fragmesh.colors) {
const colorBufferViewID = bufferViews.length;
let colorBufferView = {
buffer: bufferID,
byteOffset: -1,
byteLength: -1
};
bufferViews.push(colorBufferView);
const colorAccessorID = accessors.length;
let colorAccessor = {
bufferView: colorBufferViewID,
componentType: 5126,
count: -1,
type: 'VEC3',
};
accessors.push(colorAccessor);
mesh.primitives[0].attributes['COLOR_0'] = colorAccessorID;
const colors = Buffer.from(fragmesh.colors.buffer);
this.bufferStream.write(colors);
this.bufferSize += colors.byteLength;
colorAccessor.count = colors.byteLength / 4 / 3;
colorBufferView.byteOffset = buffer.byteLength;
colorBufferView.byteLength = colors.byteLength;
buffer.byteLength += colors.byteLength;
}
return mesh;
}
writePointGeometry(fragmesh, svf) {
let mesh = {
primitives: []
};
const manifestBuffers = this.manifest.buffers;
// Prepare new writable stream if needed
if (this.bufferStream === null || this.bufferSize > BufferSizeLimit) {
if (this.bufferStream) {
this.bufferStream.close();
this.bufferStream = null;
this.bufferSize = 0;
}
const bufferUri = `${manifestBuffers.length}.bin`;
manifestBuffers.push({ uri: bufferUri, byteLength: 0 });
this.bufferStream = fse.createWriteStream(path.join(this.baseDir, bufferUri));
}
const bufferID = manifestBuffers.length - 1;
const buffer = manifestBuffers[bufferID];
const bufferViews = this.manifest.bufferViews;
const accessors = this.manifest.accessors;
const positionBufferViewID = bufferViews.length;
let positionBufferView = {
buffer: bufferID,
byteOffset: -1,
byteLength: -1
};
bufferViews.push(positionBufferView);
const positionAccessorID = accessors.length;
let positionAccessor = {
bufferView: positionBufferViewID,
componentType: 5126,
count: -1,
type: 'VEC3',
};
accessors.push(positionAccessor);
mesh.primitives.push({
mode: 0,
attributes: {
POSITION: positionAccessorID
}
});
// Vertices
const vertices = Buffer.from(fragmesh.vertices.buffer);
this.bufferStream.write(vertices);
this.bufferSize += vertices.byteLength;
positionAccessor.count = vertices.byteLength / 4 / 3;
positionBufferView.byteOffset = buffer.byteLength;
positionBufferView.byteLength = vertices.byteLength;
buffer.byteLength += vertices.byteLength;
// Colors, if available
if (fragmesh.colors) {
const colorBufferViewID = bufferViews.length;
let colorBufferView = {
buffer: bufferID,
byteOffset: -1,
byteLength: -1
};
bufferViews.push(colorBufferView);
const colorAccessorID = accessors.length;
let colorAccessor = {
bufferView: colorBufferViewID,
componentType: 5126,
count: -1,
type: 'VEC3',
};
accessors.push(colorAccessor);
mesh.primitives[0].attributes['COLOR_0'] = colorAccessorID;
const colors = Buffer.from(fragmesh.colors.buffer);
this.bufferStream.write(colors);
this.bufferSize += colors.byteLength;
colorAccessor.count = colors.byteLength / 4 / 3;
colorBufferView.byteOffset = buffer.byteLength;
colorBufferView.byteLength = colors.byteLength;
buffer.byteLength += colors.byteLength;
}
return mesh;
}
writeMaterial(mat, svf) {
if (!mat) {
return DefaultMaterial;
}
let material = {
pbrMetallicRoughness: {
baseColorFactor: mat.diffuse,
metallicFactor: mat.metal ? 1.0 : 0.0,
}
};
if (!util_1.isUndefined(mat.opacity) && material.pbrMetallicRoughness.baseColorFactor) {
material.alphaMode = 'BLEND';
material.pbrMetallicRoughness.baseColorFactor[3] = mat.opacity;
}
if (mat.maps) {
const manifestTextures = this.manifest.textures;
if (mat.maps.diffuse) {
const textureID = manifestTextures.length;
manifestTextures.push(this.writeTexture(mat.maps.diffuse, svf));
material.pbrMetallicRoughness.baseColorTexture = {
index: textureID,
texCoord: 0
};
}
}
return material;
}
writeTexture(map, svf) {
const manifestImages = this.manifest.images;
let imageID = manifestImages.findIndex(image => image.uri === map.uri);
if (imageID === -1) {
imageID = manifestImages.length;
const uri = map.uri.toLowerCase();
manifestImages.push({ uri });
const filepath = path.join(this.baseDir, uri);
fse.ensureDirSync(path.dirname(filepath));
fse.writeFileSync(filepath, svf.images[uri]);
}
return { source: imageID };
}
}
exports.Writer = Writer;