435 lines
16 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
};
|