playcanvas
Version:
PlayCanvas WebGL game engine
257 lines (254 loc) • 13.5 kB
JavaScript
import { CoreExporter } from './core-exporter.js';
import { zipSync, strToU8 } from '../../../modules/fflate/esm/browser.js';
import { Color } from '../../core/math/color.js';
import { SEMANTIC_POSITION, SEMANTIC_NORMAL, SEMANTIC_TEXCOORD0, SEMANTIC_TEXCOORD1 } from '../../platform/graphics/constants.js';
var ROOT_FILE_NAME = 'root';
var header = '#usda 1.0\n(\n customLayerData = {\n string creator = "PlayCanvas UsdzExporter"\n }\n metersPerUnit = 1\n upAxis = "Y"\n)\n';
var materialListTemplate = (materials)=>'\ndef "Materials"\n{\n ' + materials.join('\n') + "\n}\n";
var meshTemplate = (faceVertexCounts, indices, normals, positions, uv0, uv1)=>'\ndef "Mesh"\n{\n def Mesh "Mesh"\n {\n int[] faceVertexCounts = [' + faceVertexCounts + "]\n int[] faceVertexIndices = [" + indices + "]\n normal3f[] normals = [" + normals + '] (\n interpolation = "vertex"\n )\n point3f[] points = [' + positions + "]\n texCoord2f[] primvars:st = [" + uv0 + '] (\n interpolation = "vertex"\n )\n texCoord2f[] primvars:st1 = [' + uv1 + '] (\n interpolation = "vertex"\n )\n uniform token subdivisionScheme = "none"\n }\n}\n';
var meshInstanceTemplate = (nodeName, meshRefPath, worldMatrix, materialRefPath)=>'\ndef Xform "' + nodeName + '" (\n prepend references = ' + meshRefPath + "\n)\n{\n matrix4d xformOp:transform = " + worldMatrix + '\n uniform token[] xformOpOrder = ["xformOp:transform"]\n\n rel material:binding = ' + materialRefPath + "\n}\n";
var materialValueTemplate = (type, name, value)=>" " + type + " inputs:" + name + " = " + value;
class UsdzExporter extends CoreExporter {
init() {
this.meshMap = new Map();
this.textureMap = new Map();
this.materialMap = new Map();
this.materials = [];
this.files = {};
this.nodeNames = new Set();
}
done() {
this.meshMap = null;
this.textureMap = null;
this.materialMap = null;
this.materials = null;
this.files = null;
this.nodeNames = null;
}
build(entity, options) {
var _this, _loop = function(i) {
var mimeType = 'image/png' ;
var texture = textureArray[i];
var texturePromise = _this.textureToCanvas(texture, textureOptions).then((canvas)=>{
if (canvas) {
return new Promise((resolve)=>canvas.toBlob(resolve, mimeType, 1)).then((blob)=>blob.arrayBuffer());
}
console.warn("Export of texture " + texture.name + " is not currently supported.");
return new Promise((resolve)=>resolve(null));
});
promises.push(texturePromise);
};
if (options === void 0) options = {};
this.init();
this.addFile(null, ROOT_FILE_NAME);
var allMeshInstances = [];
if (entity) {
var renders = entity.findComponents('render');
renders.forEach((render)=>{
allMeshInstances.push(...render.meshInstances);
});
}
var rootContent = '';
allMeshInstances.forEach((meshInstance)=>{
rootContent += this.buildMeshInstance(meshInstance);
});
rootContent += materialListTemplate(this.materials);
this.addFile(null, ROOT_FILE_NAME, '', rootContent);
var textureOptions = {
maxTextureSize: options.maxTextureSize
};
var textureArray = Array.from(this.textureMap.keys());
var promises = [];
for(var i = 0; i < textureArray.length; i++)_this = this, _loop(i);
var finalData = Promise.all(promises).then((values)=>{
values.forEach((textureArrayBuffer, index)=>{
var texture = textureArray[index];
var ids = this.getTextureFileIds(texture);
this.files[ids.fileName] = new Uint8Array(textureArrayBuffer);
});
this.alignFiles();
var arraybuffer = zipSync(this.files, {
level: 0
});
this.done();
return arraybuffer;
});
return finalData;
}
alignFiles() {
var offset = 0;
for(var filename in this.files){
var file = this.files[filename];
var headerSize = 34 + filename.length;
offset += headerSize;
var offsetMod64 = offset & 63;
if (offsetMod64 !== 4) {
var padLength = 64 - offsetMod64;
var padding = new Uint8Array(padLength);
this.files[filename] = [
file,
{
extra: {
12345: padding
}
}
];
}
offset = file.length;
}
}
getFileIds(category, name, ref, extension) {
if (extension === void 0) extension = 'usda';
var fileName = "" + (category ? "" + category + "/" : '') + name + "." + extension;
var refName = "@./" + fileName + "@</" + ref + ">";
return {
name,
fileName,
refName
};
}
getTextureFileIds(texture) {
return this.getFileIds('texture', "Texture_" + texture.id, 'Texture', 'png');
}
addFile(category, uniqueId, refName, content) {
if (refName === void 0) refName = '';
if (content === void 0) content = '';
var contentU8 = null;
if (content) {
content = header + "\n" + content;
contentU8 = strToU8(content);
}
var ids = this.getFileIds(category, uniqueId, refName);
this.files[ids.fileName] = contentU8;
return ids.refName;
}
getMaterialRef(material) {
var materialRef = this.materialMap.get(material);
if (!materialRef) {
materialRef = this.buildMaterial(material);
this.materialMap.set(material, materialRef);
}
return materialRef;
}
getMeshRef(mesh) {
var meshRef = this.meshMap.get(mesh);
if (!meshRef) {
meshRef = this.buildMesh(mesh);
this.meshMap.set(mesh, meshRef);
}
return meshRef;
}
buildArray2(array) {
var components = [];
var count = array.length;
for(var i = 0; i < count; i += 2){
components.push("(" + array[i] + ", " + (1 - array[i + 1]) + ")");
}
return components.join(', ');
}
buildArray3(array) {
var components = [];
var count = array.length;
for(var i = 0; i < count; i += 3){
components.push("(" + array[i] + ", " + array[i + 1] + ", " + array[i + 2] + ")");
}
return components.join(', ');
}
buildMat4(mat) {
var data = mat.data;
var vectors = [];
for(var i = 0; i < 16; i += 4){
vectors.push("(" + data[i] + ", " + data[i + 1] + ", " + data[i + 2] + ", " + data[i + 3] + ")");
}
return "( " + vectors.join(', ') + " )";
}
buildMaterial(material) {
var materialName = "Material_" + material.id;
var materialPath = "/Materials/" + materialName;
var materialPropertyPath = (property)=>"<" + materialPath + property + ">";
var buildTexture = (texture, textureIds, mapType, uvChannel, tiling, offset, rotation, tintColor)=>{
return '\n def Shader "Transform2d_' + mapType + '" (\n sdrMetadata = {\n string role = "math"\n }\n )\n {\n uniform token info:id = "UsdTransform2d"\n float2 inputs:in.connect = ' + materialPropertyPath("/uvReader_" + uvChannel + ".outputs:result") + "\n float inputs:rotation = " + rotation + "\n float2 inputs:scale = (" + tiling.x + ", " + tiling.y + ")\n float2 inputs:translation = (" + offset.x + ", " + offset.y + ')\n float2 outputs:result\n }\n\n def Shader "Texture_' + texture.id + "_" + mapType + '"\n {\n uniform token info:id = "UsdUVTexture"\n asset inputs:file = @' + textureIds.fileName + "@\n float2 inputs:st.connect = " + materialPropertyPath("/Transform2d_" + mapType + ".outputs:result") + '\n token inputs:wrapS = "repeat"\n token inputs:wrapT = "repeat"\n float4 inputs:scale = (' + tintColor.r + ", " + tintColor.g + ", " + tintColor.b + ", " + tintColor.a + ")\n float outputs:r\n float outputs:g\n float outputs:b\n float3 outputs:rgb\n float outputs:a\n }\n ";
};
var inputs = [];
var samplers = [];
var addTexture = (textureSlot, uniform, propType, propName, valueName, handleOpacity, tintTexture)=>{
if (handleOpacity === void 0) handleOpacity = false;
if (tintTexture === void 0) tintTexture = false;
var texture = material[textureSlot];
if (texture) {
var textureIds = this.getTextureFileIds(texture);
this.textureMap.set(texture, textureIds.refName);
var channel = material["" + textureSlot + "Channel"] || 'rgb';
var textureValue = materialPropertyPath("/" + textureIds.name + "_" + valueName + ".outputs:" + channel);
inputs.push(materialValueTemplate(propType, "" + propName + ".connect", textureValue));
if (handleOpacity) {
if (material.alphaTest > 0.0) ;
}
var tiling = material["" + textureSlot + "Tiling"];
var offset = material["" + textureSlot + "Offset"];
var rotation = material["" + textureSlot + "Rotation"];
var uvChannel = material["" + textureSlot + "Uv"] === 1 ? 'st1' : 'st';
var tintColor = tintTexture && uniform ? uniform : Color.WHITE;
samplers.push(buildTexture(texture, textureIds, valueName, uvChannel, tiling, offset, rotation, tintColor));
} else if (uniform) {
var value = propType === 'float' ? "" + uniform : "(" + uniform.r + ", " + uniform.g + ", " + uniform.b + ")";
inputs.push(materialValueTemplate(propType, propName, value));
}
};
addTexture('diffuseMap', material.diffuse, 'color3f', 'diffuseColor', 'diffuse', false, true);
if (material.transparent || material.alphaTest > 0.0) {
addTexture('opacityMap', material.opacity, 'float', 'opacity', 'opacity', true);
}
addTexture('normalMap', null, 'normal3f', 'normal', 'normal');
addTexture('emissiveMap', material.emissive, 'color3f', 'emissiveColor', 'emissive', false, true);
addTexture('aoMap', null, 'float', 'occlusion', 'occlusion');
addTexture('metalnessMap', material.metalness, 'float', 'metallic', 'metallic');
addTexture('glossMap', material.gloss, 'float', 'roughness', 'roughness');
var materialObject = '\n def Material "' + materialName + '"\n {\n def Shader "PreviewSurface"\n {\n uniform token info:id = "UsdPreviewSurface"\n' + inputs.join('\n') + "\n int inputs:useSpecularWorkflow = 0\n token outputs:surface\n }\n\n token outputs:surface.connect = " + materialPropertyPath('/PreviewSurface.outputs:surface') + '\n\n def Shader "uvReader_st"\n {\n uniform token info:id = "UsdPrimvarReader_float2"\n token inputs:varname = "st"\n float2 inputs:fallback = (0.0, 0.0)\n float2 outputs:result\n }\n\n def Shader "uvReader_st1"\n {\n uniform token info:id = "UsdPrimvarReader_float2"\n token inputs:varname = "st1"\n float2 inputs:fallback = (0.0, 0.0)\n float2 outputs:result\n }\n\n ' + samplers.join('\n') + "\n }\n ";
this.materials.push(materialObject);
return materialPropertyPath('');
}
buildMesh(mesh) {
var positions = [];
var indices = [];
var normals = [];
var uv0 = [];
var uv1 = [];
mesh.getVertexStream(SEMANTIC_POSITION, positions);
mesh.getVertexStream(SEMANTIC_NORMAL, normals);
mesh.getVertexStream(SEMANTIC_TEXCOORD0, uv0);
mesh.getVertexStream(SEMANTIC_TEXCOORD1, uv1);
mesh.getIndices(indices);
var indicesCount = indices.length || positions.length;
var faceVertexCounts = Array(indicesCount / 3).fill(3).join(', ');
if (!indices.length) {
for(var i = 0; i < indicesCount; i++){
indices[i] = i;
}
}
var numVerts = positions.length / 3;
normals = normals.length ? normals : Array(numVerts * 3).fill(0);
uv0 = uv0.length ? uv0 : Array(numVerts * 2).fill(0);
uv1 = uv1.length ? uv1 : Array(numVerts * 2).fill(0);
positions = this.buildArray3(positions);
normals = this.buildArray3(normals);
uv0 = this.buildArray2(uv0);
uv1 = this.buildArray2(uv1);
var meshObject = meshTemplate(faceVertexCounts, indices, normals, positions, uv0, uv1);
var refPath = this.addFile('mesh', "Mesh_" + mesh.id, 'Mesh', meshObject);
return refPath;
}
buildMeshInstance(meshInstance) {
var meshRefPath = this.getMeshRef(meshInstance.mesh);
var materialRefPath = this.getMaterialRef(meshInstance.material);
var worldMatrix = this.buildMat4(meshInstance.node.getWorldTransform());
var name = meshInstance.node.name.replace(/[^a-z0-9]/gi, '_');
var nodeName = name;
while(this.nodeNames.has(nodeName)){
nodeName = name + "_" + Math.random().toString(36).slice(2, 7);
}
this.nodeNames.add(nodeName);
return meshInstanceTemplate(nodeName, meshRefPath, worldMatrix, materialRefPath);
}
}
export { UsdzExporter };