UNPKG

forge-svf-utils

Version:

Utilities for working with Autodesk Forge SVF file format.

519 lines (518 loc) 19.4 kB
"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;