angular-3d-viewer
Version:
573 lines (499 loc) • 21.6 kB
JavaScript
import { BinaryWriter } from '../io/binarywriter.js';
import { Utf8StringToArrayBuffer } from '../io/bufferutils.js';
import { FileFormat, GetFileExtension, GetFileName } from '../io/fileutils.js';
import { MeshInstanceId } from '../model/meshinstance.js';
import { RGBColor, SRGBToLinear } from '../model/color.js';
import { MaterialType } from '../model/material.js';
import { ConvertMeshToMeshBuffer } from '../model/meshbuffer.js';
import { ExportedFile, ExporterBase } from './exporterbase.js';
const GltfComponentType =
{
UNSIGNED_INT : 5125,
FLOAT : 5126
};
const GltfBufferType =
{
ARRAY_BUFFER : 34962,
ELEMENT_ARRAY_BUFFER : 34963
};
export class ExporterGltf extends ExporterBase
{
constructor ()
{
super ();
this.components = {
index : {
type : GltfComponentType.UNSIGNED_INT,
size : 4
},
number : {
type : GltfComponentType.FLOAT,
size : 4
}
};
}
CanExport (format, extension)
{
return (format === FileFormat.Text && extension === 'gltf') || (format === FileFormat.Binary && extension === 'glb');
}
ExportContent (exporterModel, format, files, onFinish)
{
if (format === FileFormat.Text) {
this.ExportAsciiContent (exporterModel, files);
} else if (format === FileFormat.Binary) {
this.ExportBinaryContent (exporterModel, files);
}
onFinish ();
}
ExportAsciiContent (exporterModel, files)
{
let gltfFile = new ExportedFile ('model.gltf');
let binFile = new ExportedFile ('model.bin');
files.push (gltfFile);
files.push (binFile);
let meshDataArr = this.GetMeshData (exporterModel);
let mainBuffer = this.GetMainBuffer (meshDataArr);
let mainJson = this.GetMainJson (exporterModel, meshDataArr);
mainJson.buffers.push ({
uri : binFile.GetName (),
byteLength : mainBuffer.byteLength
});
let fileNameToIndex = new Map ();
this.ExportMaterials (exporterModel, mainJson, (texture) => {
let fileName = GetFileName (texture.name);
if (fileNameToIndex.has (fileName)) {
return fileNameToIndex.get (fileName);
} else {
let textureFile = new ExportedFile (fileName);
textureFile.SetBufferContent (texture.buffer);
files.push (textureFile);
let textureIndex = mainJson.textures.length;
fileNameToIndex.set (fileName, textureIndex);
mainJson.images.push ({
uri : fileName
});
mainJson.textures.push ({
source : textureIndex
});
return textureIndex;
}
});
gltfFile.SetTextContent (JSON.stringify (mainJson, null, 4));
binFile.SetBufferContent (mainBuffer);
}
ExportBinaryContent (exporterModel, files)
{
function AlignToBoundary (size)
{
let remainder = size % 4;
if (remainder === 0) {
return size;
}
return size + (4 - remainder);
}
function WriteCharacters (writer, char, count)
{
for (let i = 0; i < count; i++) {
writer.WriteUnsignedCharacter8 (char);
}
}
let glbFile = new ExportedFile ('model.glb');
files.push (glbFile);
let meshDataArr = this.GetMeshData (exporterModel);
let mainBuffer = this.GetMainBuffer (meshDataArr);
let mainJson = this.GetMainJson (exporterModel, meshDataArr);
let textureBuffers = [];
let textureOffset = mainBuffer.byteLength;
let fileNameToIndex = new Map ();
this.ExportMaterials (exporterModel, mainJson, (texture) => {
let fileName = GetFileName (texture.name);
let extension = GetFileExtension (texture.name);
if (fileNameToIndex.has (fileName)) {
return fileNameToIndex.get (fileName);
} else {
let bufferViewIndex = mainJson.bufferViews.length;
let textureIndex = mainJson.textures.length;
fileNameToIndex.set (fileName, textureIndex);
let textureBuffer = texture.buffer;
textureBuffers.push (textureBuffer);
mainJson.bufferViews.push ({
buffer : 0,
byteOffset : textureOffset,
byteLength : textureBuffer.byteLength
});
textureOffset += textureBuffer.byteLength;
mainJson.images.push ({
bufferView : bufferViewIndex,
mimeType : 'image/' + extension
});
mainJson.textures.push ({
source : textureIndex
});
return textureIndex;
}
});
let mainBinaryBufferLength = mainBuffer.byteLength;
for (let i = 0; i < textureBuffers.length; i++) {
let textureBuffer = textureBuffers[i];
mainBinaryBufferLength += textureBuffer.byteLength;
}
let mainBinaryBufferAlignedLength = AlignToBoundary (mainBinaryBufferLength);
mainJson.buffers.push ({
byteLength : mainBinaryBufferAlignedLength
});
let mainJsonString = JSON.stringify (mainJson);
let mainJsonBuffer = Utf8StringToArrayBuffer (mainJsonString);
let mainJsonBufferLength = mainJsonBuffer.byteLength;
let mainJsonBufferAlignedLength = AlignToBoundary (mainJsonBufferLength);
let glbSize = 12 + 8 + mainJsonBufferAlignedLength + 8 + mainBinaryBufferAlignedLength;
let glbWriter = new BinaryWriter (glbSize, true);
glbWriter.WriteUnsignedInteger32 (0x46546C67);
glbWriter.WriteUnsignedInteger32 (2);
glbWriter.WriteUnsignedInteger32 (glbSize);
glbWriter.WriteUnsignedInteger32 (mainJsonBufferAlignedLength);
glbWriter.WriteUnsignedInteger32 (0x4E4F534A);
glbWriter.WriteArrayBuffer (mainJsonBuffer);
WriteCharacters (glbWriter, 32, mainJsonBufferAlignedLength - mainJsonBufferLength);
glbWriter.WriteUnsignedInteger32 (mainBinaryBufferAlignedLength);
glbWriter.WriteUnsignedInteger32 (0x004E4942);
glbWriter.WriteArrayBuffer (mainBuffer);
for (let i = 0; i < textureBuffers.length; i++) {
let textureBuffer = textureBuffers[i];
glbWriter.WriteArrayBuffer (textureBuffer);
}
WriteCharacters (glbWriter, 0, mainBinaryBufferAlignedLength - mainBinaryBufferLength);
glbFile.SetBufferContent (glbWriter.GetBuffer ());
}
GetMeshData (exporterModel)
{
let meshDataArr = [];
exporterModel.EnumerateMeshes ((mesh) => {
let buffer = ConvertMeshToMeshBuffer (mesh);
meshDataArr.push ({
name : mesh.GetName (),
buffer : buffer,
offsets : [],
sizes : []
});
});
return meshDataArr;
}
GetMainBuffer (meshDataArr)
{
let mainBufferSize = 0;
for (let meshData of meshDataArr) {
mainBufferSize += meshData.buffer.GetByteLength (this.components.index.size, this.components.number.size);
}
let writer = new BinaryWriter (mainBufferSize, true);
for (let meshData of meshDataArr) {
for (let primitiveIndex = 0; primitiveIndex < meshData.buffer.PrimitiveCount (); primitiveIndex++) {
let primitive = meshData.buffer.GetPrimitive (primitiveIndex);
let offset = writer.GetPosition ();
for (let i = 0; i < primitive.indices.length; i++) {
writer.WriteUnsignedInteger32 (primitive.indices[i]);
}
for (let i = 0; i < primitive.vertices.length; i++) {
writer.WriteFloat32 (primitive.vertices[i]);
}
for (let i = 0; i < primitive.colors.length; i++) {
writer.WriteFloat32 (SRGBToLinear (primitive.colors[i]));
}
for (let i = 0; i < primitive.normals.length; i++) {
writer.WriteFloat32 (primitive.normals[i]);
}
for (let i = 0; i < primitive.uvs.length; i++) {
let texCoord = primitive.uvs[i];
if (i % 2 === 1) {
texCoord *= -1.0;
}
writer.WriteFloat32 (texCoord);
}
meshData.offsets.push (offset);
meshData.sizes.push (writer.GetPosition () - offset);
}
}
return writer.GetBuffer ();
}
GetMainJson (exporterModel, meshDataArr)
{
class BufferViewCreator
{
constructor (mainJson, byteOffset)
{
this.mainJson = mainJson;
this.byteOffset = byteOffset;
}
AddBufferView (byteLength, target)
{
let bufferView = {
buffer : 0,
byteOffset : this.byteOffset,
byteLength : byteLength,
target : target
};
this.mainJson.bufferViews.push (bufferView);
this.byteOffset += byteLength;
return this.mainJson.bufferViews.length - 1;
}
}
function NodeHasVisibleChildren (model, node)
{
for (let meshIndex of node.GetMeshIndices ()) {
let meshInstanceId = new MeshInstanceId (node.GetId (), meshIndex);
if (model.IsMeshInstanceVisible (meshInstanceId)) {
return true;
}
}
for (let childNode of node.GetChildNodes ()) {
if (NodeHasVisibleChildren (model, childNode)) {
return true;
}
}
return false;
}
function AddNode (model, jsonParent, jsonNodes, node)
{
if (node.IsMeshNode ()) {
for (let meshIndex of node.GetMeshIndices ()) {
AddMeshNode (model, jsonParent, jsonNodes, node, meshIndex, true);
}
} else if (NodeHasVisibleChildren (model, node)) {
let nodeJson = {};
let nodeName = node.GetName ();
if (nodeName.length > 0) {
nodeJson.name = nodeName;
}
let transformation = node.GetTransformation ();
if (!transformation.IsIdentity ()) {
nodeJson.matrix = node.GetTransformation ().GetMatrix ().Get ();
}
jsonNodes.push (nodeJson);
jsonParent.push (jsonNodes.length - 1);
nodeJson.children = [];
AddChildNodes (model, nodeJson.children, jsonNodes, node);
}
}
function AddMeshNode (model, jsonParent, jsonNodes, node, meshIndex, isStandaloneNode)
{
let meshInstanceId = new MeshInstanceId (node.GetId (), meshIndex);
if (!model.IsMeshInstanceVisible (meshInstanceId)) {
return;
}
let nodeJson = {
mesh : model.MapMeshIndex (meshIndex)
};
if (isStandaloneNode) {
let transformation = node.GetTransformation ();
if (!transformation.IsIdentity ()) {
nodeJson.matrix = node.GetTransformation ().GetMatrix ().Get ();
}
}
jsonNodes.push (nodeJson);
jsonParent.push (jsonNodes.length - 1);
}
function AddChildNodes (model, jsonParent, jsonNodes, node)
{
for (let childNode of node.GetChildNodes ()) {
AddNode (model, jsonParent, jsonNodes, childNode);
}
for (let meshIndex of node.GetMeshIndices ()) {
AddMeshNode (model, jsonParent, jsonNodes, node, meshIndex, false);
}
}
let mainJson = {
asset : {
generator : 'https://3dviewer.net',
version : '2.0'
},
scene : 0,
scenes : [
{
nodes : []
}
],
nodes : [],
materials : [],
meshes : [],
buffers : [],
bufferViews : [],
accessors : []
};
let rootNode = exporterModel.GetModel ().GetRootNode ();
AddChildNodes (exporterModel, mainJson.scenes[0].nodes, mainJson.nodes, rootNode);
for (let meshData of meshDataArr) {
let jsonMesh = {
name : this.GetExportedMeshName (meshData.name),
primitives : []
};
let primitives = meshData.buffer.primitives;
for (let primitiveIndex = 0; primitiveIndex < primitives.length; primitiveIndex++) {
let primitive = primitives[primitiveIndex];
let bufferViewCreator = new BufferViewCreator (mainJson, meshData.offsets[primitiveIndex]);
let indicesBufferView = bufferViewCreator.AddBufferView (primitive.indices.length * this.components.index.size, GltfBufferType.ELEMENT_ARRAY_BUFFER);
let verticesBufferView = bufferViewCreator.AddBufferView (primitive.vertices.length * this.components.number.size, GltfBufferType.ARRAY_BUFFER);
let colorsBufferView = null;
if (primitive.colors.length > 0) {
colorsBufferView = bufferViewCreator.AddBufferView (primitive.colors.length * this.components.number.size, GltfBufferType.ARRAY_BUFFER);
}
let normalsBufferView = bufferViewCreator.AddBufferView (primitive.normals.length * this.components.number.size, GltfBufferType.ARRAY_BUFFER);
let uvsBufferView = null;
if (primitive.uvs.length > 0) {
uvsBufferView = bufferViewCreator.AddBufferView (primitive.uvs.length * this.components.number.size, GltfBufferType.ARRAY_BUFFER);
}
let jsonPrimitive = {
attributes : {},
mode : 4,
material : primitive.material
};
let bounds = primitive.GetBounds ();
mainJson.accessors.push ({
bufferView : indicesBufferView,
byteOffset : 0,
componentType : this.components.index.type,
count : primitive.indices.length,
type : 'SCALAR'
});
jsonPrimitive.indices = mainJson.accessors.length - 1;
mainJson.accessors.push ({
bufferView : verticesBufferView,
byteOffset : 0,
componentType : this.components.number.type,
count : primitive.vertices.length / 3,
min : bounds.min,
max : bounds.max,
type : 'VEC3'
});
jsonPrimitive.attributes.POSITION = mainJson.accessors.length - 1;
if (colorsBufferView !== null) {
mainJson.accessors.push ({
bufferView : colorsBufferView,
byteOffset : 0,
componentType : this.components.number.type,
count : primitive.colors.length / 3,
type : 'VEC3'
});
jsonPrimitive.attributes.COLOR_0 = mainJson.accessors.length - 1;
}
mainJson.accessors.push ({
bufferView : normalsBufferView,
byteOffset : 0,
componentType : this.components.number.type,
count : primitive.normals.length / 3,
type : 'VEC3'
});
jsonPrimitive.attributes.NORMAL = mainJson.accessors.length - 1;
if (uvsBufferView !== null) {
mainJson.accessors.push ({
bufferView : uvsBufferView,
byteOffset : 0,
componentType : this.components.number.type,
count : primitive.uvs.length / 2,
type : 'VEC2'
});
jsonPrimitive.attributes.TEXCOORD_0 = mainJson.accessors.length - 1;
}
jsonMesh.primitives.push (jsonPrimitive);
}
mainJson.meshes.push (jsonMesh);
}
return mainJson;
}
ExportMaterials (exporterModel, mainJson, addTexture)
{
function ExportMaterial (obj, mainJson, material, addTexture)
{
function ColorToRGBA (color, opacity)
{
return [
SRGBToLinear (color.r / 255.0),
SRGBToLinear (color.g / 255.0),
SRGBToLinear (color.b / 255.0),
opacity
];
}
function ColorToRGB (color)
{
return [
SRGBToLinear (color.r / 255.0),
SRGBToLinear (color.g / 255.0),
SRGBToLinear (color.b / 255.0)
];
}
function GetTextureParams (mainJson, texture, addTexture)
{
if (texture === null || !texture.IsValid ()) {
return null;
}
if (mainJson.images === undefined) {
mainJson.images = [];
}
if (mainJson.textures === undefined) {
mainJson.textures = [];
}
let textureIndex = addTexture (texture);
let textureParams = {
index : textureIndex
};
if (texture.HasTransformation ()) {
let extensionName = 'KHR_texture_transform';
if (mainJson.extensionsUsed === undefined) {
mainJson.extensionsUsed = [];
}
if (mainJson.extensionsUsed.indexOf (extensionName) === -1) {
mainJson.extensionsUsed.push (extensionName);
}
textureParams.extensions = {
KHR_texture_transform : {
offset : [texture.offset.x, -texture.offset.y],
scale : [texture.scale.x, texture.scale.y],
rotation : -texture.rotation
}
};
}
return textureParams;
}
let jsonMaterial = {
name : obj.GetExportedMaterialName (material.name),
pbrMetallicRoughness : {
baseColorFactor : ColorToRGBA (material.color, material.opacity)
},
emissiveFactor : ColorToRGB (material.emissive),
doubleSided : true,
alphaMode : 'OPAQUE'
};
if (material.transparent) {
// TODO: mask, alphaCutoff?
jsonMaterial.alphaMode = 'BLEND';
}
let baseColorTexture = GetTextureParams (mainJson, material.diffuseMap, addTexture);
if (baseColorTexture !== null) {
if (!material.multiplyDiffuseMap) {
jsonMaterial.pbrMetallicRoughness.baseColorFactor = ColorToRGBA (new RGBColor (255, 255, 255), material.opacity);
}
jsonMaterial.pbrMetallicRoughness.baseColorTexture = baseColorTexture;
}
if (material.type === MaterialType.Physical) {
let metallicTexture = GetTextureParams (mainJson, material.metalnessMap, addTexture);
if (metallicTexture !== null) {
jsonMaterial.pbrMetallicRoughness.metallicRoughnessTexture = metallicTexture;
} else {
jsonMaterial.pbrMetallicRoughness.metallicFactor = material.metalness;
jsonMaterial.pbrMetallicRoughness.roughnessFactor = material.roughness;
}
}
let normalTexture = GetTextureParams (mainJson, material.normalMap, addTexture);
if (normalTexture !== null) {
jsonMaterial.normalTexture = normalTexture;
}
let emissiveTexture = GetTextureParams (mainJson, material.emissiveMap, addTexture);
if (emissiveTexture !== null) {
jsonMaterial.emissiveTexture = emissiveTexture;
}
mainJson.materials.push (jsonMaterial);
}
for (let materialIndex = 0; materialIndex < exporterModel.MaterialCount (); materialIndex++) {
let material = exporterModel.GetMaterial (materialIndex);
ExportMaterial (this, mainJson, material, addTexture);
}
}
}