ModelHandle/source/export/exportergltf.js
2021-03-28 16:39:44 +02:00

435 lines
16 KiB
JavaScript

OV.ExporterGltf = class extends OV.ExporterBase
{
constructor ()
{
super ();
this.components = {
index : {
type : 5125, // unsigned int 32
size : 4
},
number : {
type : 5126, // float 32
size : 4
}
};
}
CanExport (format, extension)
{
return (format === OV.FileFormat.Text && extension === 'gltf') || (format === OV.FileFormat.Binary && extension === 'glb');
}
ExportContent (model, format, files)
{
if (format === OV.FileFormat.Text) {
this.ExportAsciiContent (model, files);
} else if (format === OV.FileFormat.Binary) {
this.ExportBinaryContent (model, files);
}
}
ExportAsciiContent (model, files)
{
let gltfFile = new OV.ExportedFile ('model.gltf');
let binFile = new OV.ExportedFile ('model.bin');
files.push (gltfFile);
files.push (binFile);
let meshDataArr = this.GetMeshData (model);
let mainBuffer = this.GetMainBuffer (meshDataArr);
let mainJson = this.GetMainJson (meshDataArr);
mainJson.buffers.push ({
uri : binFile.GetName (),
byteLength : mainBuffer.byteLength
});
let fileNameToIndex = [];
this.ExportMaterials (model, mainJson, function (texture) {
let fileName = OV.GetFileName (texture.name);
let textureIndex = fileNameToIndex[fileName];
if (textureIndex === undefined) {
let textureFile = new OV.ExportedFile (fileName);
textureFile.SetUrl (texture.url);
files.push (textureFile);
textureIndex = mainJson.textures.length;
fileNameToIndex[fileName] = textureIndex;
mainJson.images.push ({
uri : fileName
});
mainJson.textures.push ({
source : textureIndex
});
}
return textureIndex;
});
gltfFile.SetContent (JSON.stringify (mainJson, null, 4));
binFile.SetContent (mainBuffer);
}
ExportBinaryContent (model, 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 OV.ExportedFile ('model.glb');
files.push (glbFile);
let meshDataArr = this.GetMeshData (model);
let mainBuffer = this.GetMainBuffer (meshDataArr);
let mainJson = this.GetMainJson (meshDataArr);
let textureBuffers = [];
let textureOffset = mainBuffer.byteLength;
let fileNameToIndex = [];
this.ExportMaterials (model, mainJson, function (texture) {
let fileName = OV.GetFileName (texture.name);
let extension = OV.GetFileExtension (texture.name);
let textureIndex = fileNameToIndex[fileName];
if (textureIndex === undefined) {
let bufferViewIndex = mainJson.bufferViews.length;
textureIndex = mainJson.textures.length;
fileNameToIndex[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, null, 4);
let mainJsonBufferLength = mainJsonString.length;
let mainJsonBufferAlignedLength = AlignToBoundary (mainJsonString.length);
let glbSize = 12 + 8 + mainJsonBufferAlignedLength + 8 + mainBinaryBufferAlignedLength;
let glbWriter = new OV.BinaryWriter (glbSize, true);
glbWriter.WriteUnsignedInteger32 (0x46546C67);
glbWriter.WriteUnsignedInteger32 (2);
glbWriter.WriteUnsignedInteger32 (glbSize);
glbWriter.WriteUnsignedInteger32 (mainJsonBufferAlignedLength);
glbWriter.WriteUnsignedInteger32 (0x4E4F534A);
for (let i = 0; i < mainJsonBufferLength; i++) {
glbWriter.WriteUnsignedCharacter8 (mainJsonString.charCodeAt (i));
}
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.SetContent (glbWriter.GetBuffer ());
}
GetMeshData (model)
{
let meshDataArr = [];
for (let meshIndex = 0; meshIndex < model.MeshCount (); meshIndex++) {
let mesh = model.GetMesh (meshIndex);
let buffer = OV.ConvertMeshToMeshBuffer (mesh);
meshDataArr.push ({
name : mesh.GetName (),
buffer : buffer,
offsets : [],
sizes : []
});
}
return meshDataArr;
}
GetMainBuffer (meshDataArr)
{
let mainBufferSize = 0;
for (let meshIndex = 0; meshIndex < meshDataArr.length; meshIndex++) {
let meshData = meshDataArr[meshIndex];
mainBufferSize += meshData.buffer.GetByteLength (this.components.index.size, this.components.number.size);
}
let writer = new OV.BinaryWriter (mainBufferSize, true);
for (let meshIndex = 0; meshIndex < meshDataArr.length; meshIndex++) {
let meshData = meshDataArr[meshIndex];
let primitives = meshData.buffer.primitives;
for (let primitiveIndex = 0; primitiveIndex < primitives.length; primitiveIndex++) {
let primitive = primitives[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.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 (meshDataArr)
{
function AddBufferView (mainJson, offset, length)
{
mainJson.bufferViews.push ({
buffer : 0,
byteOffset : offset,
byteLength : length,
});
return offset + length;
}
let mainJson = {
asset : {
version : '2.0'
},
scene : 0,
scenes : [
{
nodes : []
}
],
nodes : [],
materials : [],
meshes : [],
buffers : [],
bufferViews : [],
accessors : []
};
for (let meshIndex = 0; meshIndex < meshDataArr.length; meshIndex++) {
let meshData = meshDataArr[meshIndex];
mainJson.scenes[0].nodes.push (meshIndex);
mainJson.nodes.push ({
mesh : meshIndex
});
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 bufferViewIndex = mainJson.bufferViews.length;
let bufferViewOffset = meshData.offsets[primitiveIndex];
bufferViewOffset = AddBufferView (mainJson, bufferViewOffset, primitive.indices.length * this.components.index.size);
bufferViewOffset = AddBufferView (mainJson, bufferViewOffset, primitive.vertices.length * this.components.number.size);
bufferViewOffset = AddBufferView (mainJson, bufferViewOffset, primitive.normals.length * this.components.number.size);
bufferViewOffset = AddBufferView (mainJson, bufferViewOffset, primitive.uvs.length * this.components.number.size);
let accessorIndex = mainJson.accessors.length;
let jsonPrimitive = {
attributes : {
POSITION : accessorIndex + 1,
NORMAL : accessorIndex + 2
},
indices : accessorIndex,
mode : 4,
material : primitive.material
};
let bounds = primitive.GetBounds ();
mainJson.accessors.push ({
bufferView : bufferViewIndex,
byteOffset : 0,
componentType : this.components.index.type,
count : primitive.indices.length,
type : 'SCALAR'
});
mainJson.accessors.push ({
bufferView : bufferViewIndex + 1,
byteOffset : 0,
componentType : this.components.number.type,
count : primitive.vertices.length / 3,
min : bounds.min,
max : bounds.max,
type : 'VEC3'
});
mainJson.accessors.push ({
bufferView : bufferViewIndex + 2,
byteOffset : 0,
componentType : this.components.number.type,
count : primitive.normals.length / 3,
type : 'VEC3'
});
if (primitive.uvs.length > 0) {
mainJson.accessors.push ({
bufferView : bufferViewIndex + 3,
byteOffset : 0,
componentType : this.components.number.type,
count : primitive.uvs.length / 2,
type : 'VEC2'
});
jsonPrimitive.attributes.TEXCOORD_0 = accessorIndex + 3;
}
jsonMesh.primitives.push (jsonPrimitive);
}
mainJson.meshes.push (jsonMesh);
}
return mainJson;
}
ExportMaterials (model, mainJson, addTexture)
{
function ExportMaterial (obj, mainJson, material, addTexture)
{
function ColorToRGBA (color, opacity)
{
return [
OV.SRGBToLinear (color.r / 255.0),
OV.SRGBToLinear (color.g / 255.0),
OV.SRGBToLinear (color.b / 255.0),
opacity
];
}
function ColorToRGB (color)
{
return [
OV.SRGBToLinear (color.r / 255.0),
OV.SRGBToLinear (color.g / 255.0),
OV.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.diffuse, material.opacity),
metallicFactor : 0.0,
roughnessFactor : 1.0
},
emissiveFactor : ColorToRGB (material.emissive),
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 OV.Color (255, 255, 255), material.opacity);
}
jsonMaterial.pbrMetallicRoughness.baseColorTexture = baseColorTexture;
}
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 < model.MaterialCount (); materialIndex++) {
let material = model.GetMaterial (materialIndex);
ExportMaterial (this, mainJson, material, addTexture);
}
}
};