UNPKG

three-stdlib

Version:

stand-alone library of threejs examples

1,930 lines 71.3 kB
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  return value;
};
import { PropertyBinding, InterpolateLinear, Color, Vector3, CompressedTexture, Texture, MathUtils, RGBAFormat, DoubleSide, BufferAttribute, InterpolateDiscrete, Matrix4, Scene, PlaneGeometry, ShaderMaterial, Uniform, Mesh, PerspectiveCamera, WebGLRenderer, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, ClampToEdgeWrapping, RepeatWrapping, MirroredRepeatWrapping } from "three";
import { version } from "../_polyfill/constants.js";
async function readAsDataURL(blob) {
  const buffer = await blob.arrayBuffer();
  const data = btoa(String.fromCharCode(...new Uint8Array(buffer)));
  return `data:${blob.type || ""};base64,${data}`;
}
let _renderer;
let fullscreenQuadGeometry;
let fullscreenQuadMaterial;
let fullscreenQuad;
function decompress(texture, maxTextureSize = Infinity, renderer = null) {
  if (!fullscreenQuadGeometry)
    fullscreenQuadGeometry = new PlaneGeometry(2, 2, 1, 1);
  if (!fullscreenQuadMaterial)
    fullscreenQuadMaterial = new ShaderMaterial({
      uniforms: { blitTexture: new Uniform(texture) },
      vertexShader: (
        /* glsl */
        `
        varying vec2 vUv;
        void main(){
            vUv = uv;
            gl_Position = vec4(position.xy * 1.0,0.,.999999);
        }
      `
      ),
      fragmentShader: (
        /* glsl */
        `
          uniform sampler2D blitTexture; 
          varying vec2 vUv;

          void main(){ 
              gl_FragColor = vec4(vUv.xy, 0, 1);
              
              #ifdef IS_SRGB
              gl_FragColor = LinearTosRGB( texture2D( blitTexture, vUv) );
              #else
              gl_FragColor = texture2D( blitTexture, vUv);
              #endif
          }
      `
      )
    });
  fullscreenQuadMaterial.uniforms.blitTexture.value = texture;
  fullscreenQuadMaterial.defines.IS_SRGB = "colorSpace" in texture ? texture.colorSpace === "srgb" : texture.encoding === 3001;
  fullscreenQuadMaterial.needsUpdate = true;
  if (!fullscreenQuad) {
    fullscreenQuad = new Mesh(fullscreenQuadGeometry, fullscreenQuadMaterial);
    fullscreenQuad.frustrumCulled = false;
  }
  const _camera = new PerspectiveCamera();
  const _scene = new Scene();
  _scene.add(fullscreenQuad);
  if (!renderer) {
    renderer = _renderer = new WebGLRenderer({ antialias: false });
  }
  renderer.setSize(Math.min(texture.image.width, maxTextureSize), Math.min(texture.image.height, maxTextureSize));
  renderer.clear();
  renderer.render(_scene, _camera);
  const readableTexture = new Texture(renderer.domElement);
  readableTexture.minFilter = texture.minFilter;
  readableTexture.magFilter = texture.magFilter;
  readableTexture.wrapS = texture.wrapS;
  readableTexture.wrapT = texture.wrapT;
  readableTexture.name = texture.name;
  if (_renderer) {
    _renderer.dispose();
    _renderer = null;
  }
  return readableTexture;
}
const KHR_mesh_quantization_ExtraAttrTypes = {
  POSITION: [
    "byte",
    "byte normalized",
    "unsigned byte",
    "unsigned byte normalized",
    "short",
    "short normalized",
    "unsigned short",
    "unsigned short normalized"
  ],
  NORMAL: ["byte normalized", "short normalized"],
  TANGENT: ["byte normalized", "short normalized"],
  TEXCOORD: ["byte", "byte normalized", "unsigned byte", "short", "short normalized", "unsigned short"]
};
const GLTFExporter = /* @__PURE__ */ (() => {
  class GLTFExporter2 {
    constructor() {
      this.pluginCallbacks = [];
      this.register(function(writer) {
        return new GLTFLightExtension(writer);
      });
      this.register(function(writer) {
        return new GLTFMaterialsUnlitExtension(writer);
      });
      this.register(function(writer) {
        return new GLTFMaterialsTransmissionExtension(writer);
      });
      this.register(function(writer) {
        return new GLTFMaterialsVolumeExtension(writer);
      });
      this.register(function(writer) {
        return new GLTFMaterialsIorExtension(writer);
      });
      this.register(function(writer) {
        return new GLTFMaterialsSpecularExtension(writer);
      });
      this.register(function(writer) {
        return new GLTFMaterialsClearcoatExtension(writer);
      });
      this.register(function(writer) {
        return new GLTFMaterialsIridescenceExtension(writer);
      });
      this.register(function(writer) {
        return new GLTFMaterialsSheenExtension(writer);
      });
      this.register(function(writer) {
        return new GLTFMaterialsAnisotropyExtension(writer);
      });
      this.register(function(writer) {
        return new GLTFMaterialsEmissiveStrengthExtension(writer);
      });
    }
    register(callback) {
      if (this.pluginCallbacks.indexOf(callback) === -1) {
        this.pluginCallbacks.push(callback);
      }
      return this;
    }
    unregister(callback) {
      if (this.pluginCallbacks.indexOf(callback) !== -1) {
        this.pluginCallbacks.splice(this.pluginCallbacks.indexOf(callback), 1);
      }
      return this;
    }
    /**
     * Parse scenes and generate GLTF output
     * @param  {Scene or [THREE.Scenes]} input   Scene or Array of THREE.Scenes
     * @param  {Function} onDone  Callback on completed
     * @param  {Function} onError  Callback on errors
     * @param  {Object} options options
     */
    parse(input, onDone, onError, options) {
      const writer = new GLTFWriter();
      const plugins = [];
      for (let i = 0, il = this.pluginCallbacks.length; i < il; i++) {
        plugins.push(this.pluginCallbacks[i](writer));
      }
      writer.setPlugins(plugins);
      writer.write(input, onDone, options).catch(onError);
    }
    parseAsync(input, options) {
      const scope = this;
      return new Promise(function(resolve, reject) {
        scope.parse(input, resolve, reject, options);
      });
    }
  }
  /**
   * Static utility functions
   */
  __publicField(GLTFExporter2, "Utils", {
    insertKeyframe: function(track, time) {
      const tolerance = 1e-3;
      const valueSize = track.getValueSize();
      const times = new track.TimeBufferType(track.times.length + 1);
      const values = new track.ValueBufferType(track.values.length + valueSize);
      const interpolant = track.createInterpolant(new track.ValueBufferType(valueSize));
      let index;
      if (track.times.length === 0) {
        times[0] = time;
        for (let i = 0; i < valueSize; i++) {
          values[i] = 0;
        }
        index = 0;
      } else if (time < track.times[0]) {
        if (Math.abs(track.times[0] - time) < tolerance)
          return 0;
        times[0] = time;
        times.set(track.times, 1);
        values.set(interpolant.evaluate(time), 0);
        values.set(track.values, valueSize);
        index = 0;
      } else if (time > track.times[track.times.length - 1]) {
        if (Math.abs(track.times[track.times.length - 1] - time) < tolerance) {
          return track.times.length - 1;
        }
        times[times.length - 1] = time;
        times.set(track.times, 0);
        values.set(track.values, 0);
        values.set(interpolant.evaluate(time), track.values.length);
        index = times.length - 1;
      } else {
        for (let i = 0; i < track.times.length; i++) {
          if (Math.abs(track.times[i] - time) < tolerance)
            return i;
          if (track.times[i] < time && track.times[i + 1] > time) {
            times.set(track.times.slice(0, i + 1), 0);
            times[i + 1] = time;
            times.set(track.times.slice(i + 1), i + 2);
            values.set(track.values.slice(0, (i + 1) * valueSize), 0);
            values.set(interpolant.evaluate(time), (i + 1) * valueSize);
            values.set(track.values.slice((i + 1) * valueSize), (i + 2) * valueSize);
            index = i + 1;
            break;
          }
        }
      }
      track.times = times;
      track.values = values;
      return index;
    },
    mergeMorphTargetTracks: function(clip, root) {
      const tracks = [];
      const mergedTracks = {};
      const sourceTracks = clip.tracks;
      for (let i = 0; i < sourceTracks.length; ++i) {
        let sourceTrack = sourceTracks[i];
        const sourceTrackBinding = PropertyBinding.parseTrackName(sourceTrack.name);
        const sourceTrackNode = PropertyBinding.findNode(root, sourceTrackBinding.nodeName);
        if (sourceTrackBinding.propertyName !== "morphTargetInfluences" || sourceTrackBinding.propertyIndex === void 0) {
          tracks.push(sourceTrack);
          continue;
        }
        if (sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear) {
          if (sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline) {
            throw new Error("THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.");
          }
          console.warn("THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.");
          sourceTrack = sourceTrack.clone();
          sourceTrack.setInterpolation(InterpolateLinear);
        }
        const targetCount = sourceTrackNode.morphTargetInfluences.length;
        const targetIndex = sourceTrackNode.morphTargetDictionary[sourceTrackBinding.propertyIndex];
        if (targetIndex === void 0) {
          throw new Error("THREE.GLTFExporter: Morph target name not found: " + sourceTrackBinding.propertyIndex);
        }
        let mergedTrack;
        if (mergedTracks[sourceTrackNode.uuid] === void 0) {
          mergedTrack = sourceTrack.clone();
          const values = new mergedTrack.ValueBufferType(targetCount * mergedTrack.times.length);
          for (let j = 0; j < mergedTrack.times.length; j++) {
            values[j * targetCount + targetIndex] = mergedTrack.values[j];
          }
          mergedTrack.name = (sourceTrackBinding.nodeName || "") + ".morphTargetInfluences";
          mergedTrack.values = values;
          mergedTracks[sourceTrackNode.uuid] = mergedTrack;
          tracks.push(mergedTrack);
          continue;
        }
        const sourceInterpolant = sourceTrack.createInterpolant(new sourceTrack.ValueBufferType(1));
        mergedTrack = mergedTracks[sourceTrackNode.uuid];
        for (let j = 0; j < mergedTrack.times.length; j++) {
          mergedTrack.values[j * targetCount + targetIndex] = sourceInterpolant.evaluate(mergedTrack.times[j]);
        }
        for (let j = 0; j < sourceTrack.times.length; j++) {
          const keyframeIndex = this.insertKeyframe(mergedTrack, sourceTrack.times[j]);
          mergedTrack.values[keyframeIndex * targetCount + targetIndex] = sourceTrack.values[j];
        }
      }
      clip.tracks = tracks;
      return clip;
    }
  });
  return GLTFExporter2;
})();
const WEBGL_CONSTANTS = {
  POINTS: 0,
  LINES: 1,
  LINE_LOOP: 2,
  LINE_STRIP: 3,
  TRIANGLES: 4,
  TRIANGLE_STRIP: 5,
  TRIANGLE_FAN: 6,
  BYTE: 5120,
  UNSIGNED_BYTE: 5121,
  SHORT: 5122,
  UNSIGNED_SHORT: 5123,
  INT: 5124,
  UNSIGNED_INT: 5125,
  FLOAT: 5126,
  ARRAY_BUFFER: 34962,
  ELEMENT_ARRAY_BUFFER: 34963,
  NEAREST: 9728,
  LINEAR: 9729,
  NEAREST_MIPMAP_NEAREST: 9984,
  LINEAR_MIPMAP_NEAREST: 9985,
  NEAREST_MIPMAP_LINEAR: 9986,
  LINEAR_MIPMAP_LINEAR: 9987,
  CLAMP_TO_EDGE: 33071,
  MIRRORED_REPEAT: 33648,
  REPEAT: 10497
};
const KHR_MESH_QUANTIZATION = "KHR_mesh_quantization";
const THREE_TO_WEBGL = {};
THREE_TO_WEBGL[NearestFilter] = WEBGL_CONSTANTS.NEAREST;
THREE_TO_WEBGL[NearestMipmapNearestFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST;
THREE_TO_WEBGL[NearestMipmapLinearFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR;
THREE_TO_WEBGL[LinearFilter] = WEBGL_CONSTANTS.LINEAR;
THREE_TO_WEBGL[LinearMipmapNearestFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST;
THREE_TO_WEBGL[LinearMipmapLinearFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR;
THREE_TO_WEBGL[ClampToEdgeWrapping] = WEBGL_CONSTANTS.CLAMP_TO_EDGE;
THREE_TO_WEBGL[RepeatWrapping] = WEBGL_CONSTANTS.REPEAT;
THREE_TO_WEBGL[MirroredRepeatWrapping] = WEBGL_CONSTANTS.MIRRORED_REPEAT;
const PATH_PROPERTIES = {
  scale: "scale",
  position: "translation",
  quaternion: "rotation",
  morphTargetInfluences: "weights"
};
const DEFAULT_SPECULAR_COLOR = /* @__PURE__ */ new Color();
const GLB_HEADER_BYTES = 12;
const GLB_HEADER_MAGIC = 1179937895;
const GLB_VERSION = 2;
const GLB_CHUNK_PREFIX_BYTES = 8;
const GLB_CHUNK_TYPE_JSON = 1313821514;
const GLB_CHUNK_TYPE_BIN = 5130562;
function equalArray(array1, array2) {
  return array1.length === array2.length && array1.every(function(element, index) {
    return element === array2[index];
  });
}
function stringToArrayBuffer(text) {
  return new TextEncoder().encode(text).buffer;
}
function isIdentityMatrix(matrix) {
  return equalArray(matrix.elements, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
}
function getMinMax(attribute, start, count) {
  const output = {
    min: new Array(attribute.itemSize).fill(Number.POSITIVE_INFINITY),
    max: new Array(attribute.itemSize).fill(Number.NEGATIVE_INFINITY)
  };
  for (let i = start; i < start + count; i++) {
    for (let a = 0; a < attribute.itemSize; a++) {
      let value;
      if (attribute.itemSize > 4) {
        value = attribute.array[i * attribute.itemSize + a];
      } else {
        if (a === 0)
          value = attribute.getX(i);
        else if (a === 1)
          value = attribute.getY(i);
        else if (a === 2)
          value = attribute.getZ(i);
        else if (a === 3)
          value = attribute.getW(i);
        if (attribute.normalized === true) {
          value = MathUtils.normalize(value, attribute.array);
        }
      }
      output.min[a] = Math.min(output.min[a], value);
      output.max[a] = Math.max(output.max[a], value);
    }
  }
  return output;
}
function getPaddedBufferSize(bufferSize) {
  return Math.ceil(bufferSize / 4) * 4;
}
function getPaddedArrayBuffer(arrayBuffer, paddingByte = 0) {
  const paddedLength = getPaddedBufferSize(arrayBuffer.byteLength);
  if (paddedLength !== arrayBuffer.byteLength) {
    const array = new Uint8Array(paddedLength);
    array.set(new Uint8Array(arrayBuffer));
    if (paddingByte !== 0) {
      for (let i = arrayBuffer.byteLength; i < paddedLength; i++) {
        array[i] = paddingByte;
      }
    }
    return array.buffer;
  }
  return arrayBuffer;
}
function getCanvas() {
  if (typeof document === "undefined" && typeof OffscreenCanvas !== "undefined") {
    return new OffscreenCanvas(1, 1);
  }
  return document.createElement("canvas");
}
function getToBlobPromise(canvas, mimeType) {
  if (canvas.toBlob !== void 0) {
    return new Promise((resolve) => canvas.toBlob(resolve, mimeType));
  }
  let quality;
  if (mimeType === "image/jpeg") {
    quality = 0.92;
  } else if (mimeType === "image/webp") {
    quality = 0.8;
  }
  return canvas.convertToBlob({
    type: mimeType,
    quality
  });
}
class GLTFWriter {
  constructor() {
    this.plugins = [];
    this.options = {};
    this.pending = [];
    this.buffers = [];
    this.byteOffset = 0;
    this.buffers = [];
    this.nodeMap = /* @__PURE__ */ new Map();
    this.skins = [];
    this.extensionsUsed = {};
    this.extensionsRequired = {};
    this.uids = /* @__PURE__ */ new Map();
    this.uid = 0;
    this.json = {
      asset: {
        version: "2.0",
        generator: "THREE.GLTFExporter"
      }
    };
    this.cache = {
      meshes: /* @__PURE__ */ new Map(),
      attributes: /* @__PURE__ */ new Map(),
      attributesNormalized: /* @__PURE__ */ new Map(),
      materials: /* @__PURE__ */ new Map(),
      textures: /* @__PURE__ */ new Map(),
      images: /* @__PURE__ */ new Map()
    };
  }
  setPlugins(plugins) {
    this.plugins = plugins;
  }
  /**
   * Parse scenes and generate GLTF output
   * @param  {Scene or [THREE.Scenes]} input   Scene or Array of THREE.Scenes
   * @param  {Function} onDone  Callback on completed
   * @param  {Object} options options
   */
  async write(input, onDone, options = {}) {
    this.options = Object.assign(
      {
        // default options
        binary: false,
        trs: false,
        onlyVisible: true,
        maxTextureSize: Infinity,
        animations: [],
        includeCustomExtensions: false
      },
      options
    );
    if (this.options.animations.length > 0) {
      this.options.trs = true;
    }
    this.processInput(input);
    await Promise.all(this.pending);
    const writer = this;
    const buffers = writer.buffers;
    const json = writer.json;
    options = writer.options;
    const extensionsUsed = writer.extensionsUsed;
    const extensionsRequired = writer.extensionsRequired;
    const blob = new Blob(buffers, { type: "application/octet-stream" });
    const extensionsUsedList = Object.keys(extensionsUsed);
    const extensionsRequiredList = Object.keys(extensionsRequired);
    if (extensionsUsedList.length > 0)
      json.extensionsUsed = extensionsUsedList;
    if (extensionsRequiredList.length > 0)
      json.extensionsRequired = extensionsRequiredList;
    if (json.buffers && json.buffers.length > 0)
      json.buffers[0].byteLength = blob.size;
    if (options.binary === true) {
      blob.arrayBuffer().then((result) => {
        const binaryChunk = getPaddedArrayBuffer(result);
        const binaryChunkPrefix = new DataView(new ArrayBuffer(GLB_CHUNK_PREFIX_BYTES));
        binaryChunkPrefix.setUint32(0, binaryChunk.byteLength, true);
        binaryChunkPrefix.setUint32(4, GLB_CHUNK_TYPE_BIN, true);
        const jsonChunk = getPaddedArrayBuffer(stringToArrayBuffer(JSON.stringify(json)), 32);
        const jsonChunkPrefix = new DataView(new ArrayBuffer(GLB_CHUNK_PREFIX_BYTES));
        jsonChunkPrefix.setUint32(0, jsonChunk.byteLength, true);
        jsonChunkPrefix.setUint32(4, GLB_CHUNK_TYPE_JSON, true);
        const header = new ArrayBuffer(GLB_HEADER_BYTES);
        const headerView = new DataView(header);
        headerView.setUint32(0, GLB_HEADER_MAGIC, true);
        headerView.setUint32(4, GLB_VERSION, true);
        const totalByteLength = GLB_HEADER_BYTES + jsonChunkPrefix.byteLength + jsonChunk.byteLength + binaryChunkPrefix.byteLength + binaryChunk.byteLength;
        headerView.setUint32(8, totalByteLength, true);
        const glbBlob = new Blob([header, jsonChunkPrefix, jsonChunk, binaryChunkPrefix, binaryChunk], {
          type: "application/octet-stream"
        });
        glbBlob.arrayBuffer().then(onDone);
      });
    } else {
      if (json.buffers && json.buffers.length > 0) {
        readAsDataURL(blob).then((uri) => {
          json.buffers[0].uri = uri;
          onDone(json);
        });
      } else {
        onDone(json);
      }
    }
  }
  /**
   * Serializes a userData.
   *
   * @param {THREE.Object3D|THREE.Material} object
   * @param {Object} objectDef
   */
  serializeUserData(object, objectDef) {
    if (Object.keys(object.userData).length === 0)
      return;
    const options = this.options;
    const extensionsUsed = this.extensionsUsed;
    try {
      const json = JSON.parse(JSON.stringify(object.userData));
      if (options.includeCustomExtensions && json.gltfExtensions) {
        if (objectDef.extensions === void 0)
          objectDef.extensions = {};
        for (const extensionName in json.gltfExtensions) {
          objectDef.extensions[extensionName] = json.gltfExtensions[extensionName];
          extensionsUsed[extensionName] = true;
        }
        delete json.gltfExtensions;
      }
      if (Object.keys(json).length > 0)
        objectDef.extras = json;
    } catch (error) {
      console.warn(
        "THREE.GLTFExporter: userData of '" + object.name + "' won't be serialized because of JSON.stringify error - " + error.message
      );
    }
  }
  /**
   * Returns ids for buffer attributes.
   * @param  {Object} object
   * @return {Integer}
   */
  getUID(attribute, isRelativeCopy = false) {
    if (this.uids.has(attribute) === false) {
      const uids2 = /* @__PURE__ */ new Map();
      uids2.set(true, this.uid++);
      uids2.set(false, this.uid++);
      this.uids.set(attribute, uids2);
    }
    const uids = this.uids.get(attribute);
    return uids.get(isRelativeCopy);
  }
  /**
   * Checks if normal attribute values are normalized.
   *
   * @param {BufferAttribute} normal
   * @returns {Boolean}
   */
  isNormalizedNormalAttribute(normal) {
    const cache = this.cache;
    if (cache.attributesNormalized.has(normal))
      return false;
    const v = new Vector3();
    for (let i = 0, il = normal.count; i < il; i++) {
      if (Math.abs(v.fromBufferAttribute(normal, i).length() - 1) > 5e-4)
        return false;
    }
    return true;
  }
  /**
   * Creates normalized normal buffer attribute.
   *
   * @param {BufferAttribute} normal
   * @returns {BufferAttribute}
   *
   */
  createNormalizedNormalAttribute(normal) {
    const cache = this.cache;
    if (cache.attributesNormalized.has(normal))
      return cache.attributesNormalized.get(normal);
    const attribute = normal.clone();
    const v = new Vector3();
    for (let i = 0, il = attribute.count; i < il; i++) {
      v.fromBufferAttribute(attribute, i);
      if (v.x === 0 && v.y === 0 && v.z === 0) {
        v.setX(1);
      } else {
        v.normalize();
      }
      attribute.setXYZ(i, v.x, v.y, v.z);
    }
    cache.attributesNormalized.set(normal, attribute);
    return attribute;
  }
  /**
   * Applies a texture transform, if present, to the map definition. Requires
   * the KHR_texture_transform extension.
   *
   * @param {Object} mapDef
   * @param {THREE.Texture} texture
   */
  applyTextureTransform(mapDef, texture) {
    let didTransform = false;
    const transformDef = {};
    if (texture.offset.x !== 0 || texture.offset.y !== 0) {
      transformDef.offset = texture.offset.toArray();
      didTransform = true;
    }
    if (texture.rotation !== 0) {
      transformDef.rotation = texture.rotation;
      didTransform = true;
    }
    if (texture.repeat.x !== 1 || texture.repeat.y !== 1) {
      transformDef.scale = texture.repeat.toArray();
      didTransform = true;
    }
    if (didTransform) {
      mapDef.extensions = mapDef.extensions || {};
      mapDef.extensions["KHR_texture_transform"] = transformDef;
      this.extensionsUsed["KHR_texture_transform"] = true;
    }
  }
  buildMetalRoughTexture(metalnessMap, roughnessMap) {
    if (metalnessMap === roughnessMap)
      return metalnessMap;
    function getEncodingConversion(map) {
      if ("colorSpace" in map ? map.colorSpace === "srgb" : map.encoding === 3001) {
        return function SRGBToLinear(c) {
          return c < 0.04045 ? c * 0.0773993808 : Math.pow(c * 0.9478672986 + 0.0521327014, 2.4);
        };
      }
      return function LinearToLinear(c) {
        return c;
      };
    }
    console.warn("THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.");
    if (metalnessMap instanceof CompressedTexture) {
      metalnessMap = decompress(metalnessMap);
    }
    if (roughnessMap instanceof CompressedTexture) {
      roughnessMap = decompress(roughnessMap);
    }
    const metalness = metalnessMap ? metalnessMap.image : null;
    const roughness = roughnessMap ? roughnessMap.image : null;
    const width = Math.max(metalness ? metalness.width : 0, roughness ? roughness.width : 0);
    const height = Math.max(metalness ? metalness.height : 0, roughness ? roughness.height : 0);
    const canvas = getCanvas();
    canvas.width = width;
    canvas.height = height;
    const context = canvas.getContext("2d");
    context.fillStyle = "#00ffff";
    context.fillRect(0, 0, width, height);
    const composite = context.getImageData(0, 0, width, height);
    if (metalness) {
      context.drawImage(metalness, 0, 0, width, height);
      const convert = getEncodingConversion(metalnessMap);
      const data = context.getImageData(0, 0, width, height).data;
      for (let i = 2; i < data.length; i += 4) {
        composite.data[i] = convert(data[i] / 256) * 256;
      }
    }
    if (roughness) {
      context.drawImage(roughness, 0, 0, width, height);
      const convert = getEncodingConversion(roughnessMap);
      const data = context.getImageData(0, 0, width, height).data;
      for (let i = 1; i < data.length; i += 4) {
        composite.data[i] = convert(data[i] / 256) * 256;
      }
    }
    context.putImageData(composite, 0, 0);
    const reference = metalnessMap || roughnessMap;
    const texture = reference.clone();
    texture.source = new Texture(canvas).source;
    if ("colorSpace" in texture)
      texture.colorSpace = "";
    else
      texture.encoding = 3e3;
    texture.channel = (metalnessMap || roughnessMap).channel;
    if (metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel) {
      console.warn("THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.");
    }
    return texture;
  }
  /**
   * Process a buffer to append to the default one.
   * @param  {ArrayBuffer} buffer
   * @return {Integer}
   */
  processBuffer(buffer) {
    const json = this.json;
    const buffers = this.buffers;
    if (!json.buffers)
      json.buffers = [{ byteLength: 0 }];
    buffers.push(buffer);
    return 0;
  }
  /**
   * Process and generate a BufferView
   * @param  {BufferAttribute} attribute
   * @param  {number} componentType
   * @param  {number} start
   * @param  {number} count
   * @param  {number} target (Optional) Target usage of the BufferView
   * @return {Object}
   */
  processBufferView(attribute, componentType, start, count, target) {
    const json = this.json;
    if (!json.bufferViews)
      json.bufferViews = [];
    let componentSize;
    switch (componentType) {
      case WEBGL_CONSTANTS.BYTE:
      case WEBGL_CONSTANTS.UNSIGNED_BYTE:
        componentSize = 1;
        break;
      case WEBGL_CONSTANTS.SHORT:
      case WEBGL_CONSTANTS.UNSIGNED_SHORT:
        componentSize = 2;
        break;
      default:
        componentSize = 4;
    }
    let byteStride = attribute.itemSize * componentSize;
    if (target === WEBGL_CONSTANTS.ARRAY_BUFFER) {
      byteStride = Math.ceil(byteStride / 4) * 4;
    }
    const byteLength = getPaddedBufferSize(count * byteStride);
    const dataView = new DataView(new ArrayBuffer(byteLength));
    let offset = 0;
    for (let i = start; i < start + count; i++) {
      for (let a = 0; a < attribute.itemSize; a++) {
        let value;
        if (attribute.itemSize > 4) {
          value = attribute.array[i * attribute.itemSize + a];
        } else {
          if (a === 0)
            value = attribute.getX(i);
          else if (a === 1)
            value = attribute.getY(i);
          else if (a === 2)
            value = attribute.getZ(i);
          else if (a === 3)
            value = attribute.getW(i);
          if (attribute.normalized === true) {
            value = MathUtils.normalize(value, attribute.array);
          }
        }
        if (componentType === WEBGL_CONSTANTS.FLOAT) {
          dataView.setFloat32(offset, value, true);
        } else if (componentType === WEBGL_CONSTANTS.INT) {
          dataView.setInt32(offset, value, true);
        } else if (componentType === WEBGL_CONSTANTS.UNSIGNED_INT) {
          dataView.setUint32(offset, value, true);
        } else if (componentType === WEBGL_CONSTANTS.SHORT) {
          dataView.setInt16(offset, value, true);
        } else if (componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT) {
          dataView.setUint16(offset, value, true);
        } else if (componentType === WEBGL_CONSTANTS.BYTE) {
          dataView.setInt8(offset, value);
        } else if (componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE) {
          dataView.setUint8(offset, value);
        }
        offset += componentSize;
      }
      if (offset % byteStride !== 0) {
        offset += byteStride - offset % byteStride;
      }
    }
    const bufferViewDef = {
      buffer: this.processBuffer(dataView.buffer),
      byteOffset: this.byteOffset,
      byteLength
    };
    if (target !== void 0)
      bufferViewDef.target = target;
    if (target === WEBGL_CONSTANTS.ARRAY_BUFFER) {
      bufferViewDef.byteStride = byteStride;
    }
    this.byteOffset += byteLength;
    json.bufferViews.push(bufferViewDef);
    const output = {
      id: json.bufferViews.length - 1,
      byteLength: 0
    };
    return output;
  }
  /**
   * Process and generate a BufferView from an image Blob.
   * @param {Blob} blob
   * @return {Promise<Integer>}
   */
  processBufferViewImage(blob) {
    const writer = this;
    const json = writer.json;
    if (!json.bufferViews)
      json.bufferViews = [];
    return blob.arrayBuffer().then((result) => {
      const buffer = getPaddedArrayBuffer(result);
      const bufferViewDef = {
        buffer: writer.processBuffer(buffer),
        byteOffset: writer.byteOffset,
        byteLength: buffer.byteLength
      };
      writer.byteOffset += buffer.byteLength;
      return json.bufferViews.push(bufferViewDef) - 1;
    });
  }
  /**
   * Process attribute to generate an accessor
   * @param  {BufferAttribute} attribute Attribute to process
   * @param  {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range
   * @param  {Integer} start (Optional)
   * @param  {Integer} count (Optional)
   * @return {Integer|null} Index of the processed accessor on the "accessors" array
   */
  processAccessor(attribute, geometry, start, count) {
    const json = this.json;
    const types = {
      1: "SCALAR",
      2: "VEC2",
      3: "VEC3",
      4: "VEC4",
      9: "MAT3",
      16: "MAT4"
    };
    let componentType;
    if (attribute.array.constructor === Float32Array) {
      componentType = WEBGL_CONSTANTS.FLOAT;
    } else if (attribute.array.constructor === Int32Array) {
      componentType = WEBGL_CONSTANTS.INT;
    } else if (attribute.array.constructor === Uint32Array) {
      componentType = WEBGL_CONSTANTS.UNSIGNED_INT;
    } else if (attribute.array.constructor === Int16Array) {
      componentType = WEBGL_CONSTANTS.SHORT;
    } else if (attribute.array.constructor === Uint16Array) {
      componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT;
    } else if (attribute.array.constructor === Int8Array) {
      componentType = WEBGL_CONSTANTS.BYTE;
    } else if (attribute.array.constructor === Uint8Array) {
      componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE;
    } else {
      throw new Error(
        "THREE.GLTFExporter: Unsupported bufferAttribute component type: " + attribute.array.constructor.name
      );
    }
    if (start === void 0)
      start = 0;
    if (count === void 0)
      count = attribute.count;
    if (count === 0)
      return null;
    const minMax = getMinMax(attribute, start, count);
    let bufferViewTarget;
    if (geometry !== void 0) {
      bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER;
    }
    const bufferView = this.processBufferView(attribute, componentType, start, count, bufferViewTarget);
    const accessorDef = {
      bufferView: bufferView.id,
      byteOffset: bufferView.byteOffset,
      componentType,
      count,
      max: minMax.max,
      min: minMax.min,
      type: types[attribute.itemSize]
    };
    if (attribute.normalized === true)
      accessorDef.normalized = true;
    if (!json.accessors)
      json.accessors = [];
    return json.accessors.push(accessorDef) - 1;
  }
  /**
   * Process image
   * @param  {Image} image to process
   * @param  {Integer} format of the image (RGBAFormat)
   * @param  {Boolean} flipY before writing out the image
   * @param  {String} mimeType export format
   * @return {Integer}     Index of the processed texture in the "images" array
   */
  processImage(image, format, flipY, mimeType = "image/png") {
    if (image !== null) {
      const writer = this;
      const cache = writer.cache;
      const json = writer.json;
      const options = writer.options;
      const pending = writer.pending;
      if (!cache.images.has(image))
        cache.images.set(image, {});
      const cachedImages = cache.images.get(image);
      const key = mimeType + ":flipY/" + flipY.toString();
      if (cachedImages[key] !== void 0)
        return cachedImages[key];
      if (!json.images)
        json.images = [];
      const imageDef = { mimeType };
      const canvas = getCanvas();
      canvas.width = Math.min(image.width, options.maxTextureSize);
      canvas.height = Math.min(image.height, options.maxTextureSize);
      const ctx = canvas.getContext("2d");
      if (flipY === true) {
        ctx.translate(0, canvas.height);
        ctx.scale(1, -1);
      }
      if (image.data !== void 0) {
        if (format !== RGBAFormat) {
          console.error("GLTFExporter: Only RGBAFormat is supported.", format);
        }
        if (image.width > options.maxTextureSize || image.height > options.maxTextureSize) {
          console.warn("GLTFExporter: Image size is bigger than maxTextureSize", image);
        }
        const data = new Uint8ClampedArray(image.height * image.width * 4);
        for (let i = 0; i < data.length; i += 4) {
          data[i + 0] = image.data[i + 0];
          data[i + 1] = image.data[i + 1];
          data[i + 2] = image.data[i + 2];
          data[i + 3] = image.data[i + 3];
        }
        ctx.putImageData(new ImageData(data, image.width, image.height), 0, 0);
      } else {
        ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
      }
      if (options.binary === true) {
        pending.push(
          getToBlobPromise(canvas, mimeType).then((blob) => writer.processBufferViewImage(blob)).then((bufferViewIndex) => {
            imageDef.bufferView = bufferViewIndex;
          })
        );
      } else {
        if (canvas.toDataURL !== void 0) {
          imageDef.uri = canvas.toDataURL(mimeType);
        } else {
          pending.push(
            getToBlobPromise(canvas, mimeType).then(readAsDataURL).then((uri) => {
              imageDef.uri = uri;
            })
          );
        }
      }
      const index = json.images.push(imageDef) - 1;
      cachedImages[key] = index;
      return index;
    } else {
      throw new Error("THREE.GLTFExporter: No valid image data found. Unable to process texture.");
    }
  }
  /**
   * Process sampler
   * @param  {Texture} map Texture to process
   * @return {Integer}     Index of the processed texture in the "samplers" array
   */
  processSampler(map) {
    const json = this.json;
    if (!json.samplers)
      json.samplers = [];
    const samplerDef = {
      magFilter: THREE_TO_WEBGL[map.magFilter],
      minFilter: THREE_TO_WEBGL[map.minFilter],
      wrapS: THREE_TO_WEBGL[map.wrapS],
      wrapT: THREE_TO_WEBGL[map.wrapT]
    };
    return json.samplers.push(samplerDef) - 1;
  }
  /**
   * Process texture
   * @param  {Texture} map Map to process
   * @return {Integer} Index of the processed texture in the "textures" array
   */
  processTexture(map) {
    const writer = this;
    const options = writer.options;
    const cache = this.cache;
    const json = this.json;
    if (cache.textures.has(map))
      return cache.textures.get(map);
    if (!json.textures)
      json.textures = [];
    if (map instanceof CompressedTexture) {
      map = decompress(map, options.maxTextureSize);
    }
    let mimeType = map.userData.mimeType;
    if (mimeType === "image/webp")
      mimeType = "image/png";
    const textureDef = {
      sampler: this.processSampler(map),
      source: this.processImage(map.image, map.format, map.flipY, mimeType)
    };
    if (map.name)
      textureDef.name = map.name;
    this._invokeAll(function(ext) {
      ext.writeTexture && ext.writeTexture(map, textureDef);
    });
    const index = json.textures.push(textureDef) - 1;
    cache.textures.set(map, index);
    return index;
  }
  /**
   * Process material
   * @param  {THREE.Material} material Material to process
   * @return {Integer|null} Index of the processed material in the "materials" array
   */
  processMaterial(material) {
    const cache = this.cache;
    const json = this.json;
    if (cache.materials.has(material))
      return cache.materials.get(material);
    if (material.isShaderMaterial) {
      console.warn("GLTFExporter: THREE.ShaderMaterial not supported.");
      return null;
    }
    if (!json.materials)
      json.materials = [];
    const materialDef = { pbrMetallicRoughness: {} };
    if (material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true) {
      console.warn("GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.");
    }
    const color = material.color.toArray().concat([material.opacity]);
    if (!equalArray(color, [1, 1, 1, 1])) {
      materialDef.pbrMetallicRoughness.baseColorFactor = color;
    }
    if (material.isMeshStandardMaterial) {
      materialDef.pbrMetallicRoughness.metallicFactor = material.metalness;
      materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness;
    } else {
      materialDef.pbrMetallicRoughness.metallicFactor = 0.5;
      materialDef.pbrMetallicRoughness.roughnessFactor = 0.5;
    }
    if (material.metalnessMap || material.roughnessMap) {
      const metalRoughTexture = this.buildMetalRoughTexture(material.metalnessMap, material.roughnessMap);
      const metalRoughMapDef = {
        index: this.processTexture(metalRoughTexture),
        channel: metalRoughTexture.channel
      };
      this.applyTextureTransform(metalRoughMapDef, metalRoughTexture);
      materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef;
    }
    if (material.map) {
      const baseColorMapDef = {
        index: this.processTexture(material.map),
        texCoord: material.map.channel
      };
      this.applyTextureTransform(baseColorMapDef, material.map);
      materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef;
    }
    if (material.emissive) {
      const emissive = material.emissive;
      const maxEmissiveComponent = Math.max(emissive.r, emissive.g, emissive.b);
      if (maxEmissiveComponent > 0) {
        materialDef.emissiveFactor = material.emissive.toArray();
      }
      if (material.emissiveMap) {
        const emissiveMapDef = {
          index: this.processTexture(material.emissiveMap),
          texCoord: material.emissiveMap.channel
        };
        this.applyTextureTransform(emissiveMapDef, material.emissiveMap);
        materialDef.emissiveTexture = emissiveMapDef;
      }
    }
    if (material.normalMap) {
      const normalMapDef = {
        index: this.processTexture(material.normalMap),
        texCoord: material.normalMap.channel
      };
      if (material.normalScale && material.normalScale.x !== 1) {
        normalMapDef.scale = material.normalScale.x;
      }
      this.applyTextureTransform(normalMapDef, material.normalMap);
      materialDef.normalTexture = normalMapDef;
    }
    if (material.aoMap) {
      const occlusionMapDef = {
        index: this.processTexture(material.aoMap),
        texCoord: material.aoMap.channel
      };
      if (material.aoMapIntensity !== 1) {
        occlusionMapDef.strength = material.aoMapIntensity;
      }
      this.applyTextureTransform(occlusionMapDef, material.aoMap);
      materialDef.occlusionTexture = occlusionMapDef;
    }
    if (material.transparent) {
      materialDef.alphaMode = "BLEND";
    } else {
      if (material.alphaTest > 0) {
        materialDef.alphaMode = "MASK";
        materialDef.alphaCutoff = material.alphaTest;
      }
    }
    if (material.side === DoubleSide)
      materialDef.doubleSided = true;
    if (material.name !== "")
      materialDef.name = material.name;
    this.serializeUserData(material, materialDef);
    this._invokeAll(function(ext) {
      ext.writeMaterial && ext.writeMaterial(material, materialDef);
    });
    const index = json.materials.push(materialDef) - 1;
    cache.materials.set(material, index);
    return index;
  }
  /**
   * Process mesh
   * @param  {THREE.Mesh} mesh Mesh to process
   * @return {Integer|null} Index of the processed mesh in the "meshes" array
   */
  processMesh(mesh) {
    const cache = this.cache;
    const json = this.json;
    const meshCacheKeyParts = [mesh.geometry.uuid];
    if (Array.isArray(mesh.material)) {
      for (let i = 0, l = mesh.material.length; i < l; i++) {
        meshCacheKeyParts.push(mesh.material[i].uuid);
      }
    } else {
      meshCacheKeyParts.push(mesh.material.uuid);
    }
    const meshCacheKey = meshCacheKeyParts.join(":");
    if (cache.meshes.has(meshCacheKey))
      return cache.meshes.get(meshCacheKey);
    const geometry = mesh.geometry;
    let mode;
    if (mesh.isLineSegments) {
      mode = WEBGL_CONSTANTS.LINES;
    } else if (mesh.isLineLoop) {
      mode = WEBGL_CONSTANTS.LINE_LOOP;
    } else if (mesh.isLine) {
      mode = WEBGL_CONSTANTS.LINE_STRIP;
    } else if (mesh.isPoints) {
      mode = WEBGL_CONSTANTS.POINTS;
    } else {
      mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES;
    }
    const meshDef = {};
    const attributes = {};
    const primitives = [];
    const targets = [];
    const nameConversion = {
      ...version >= 152 ? {
        uv: "TEXCOORD_0",
        uv1: "TEXCOORD_1",
        uv2: "TEXCOORD_2",
        uv3: "TEXCOORD_3"
      } : {
        uv: "TEXCOORD_0",
        uv2: "TEXCOORD_1"
      },
      color: "COLOR_0",
      skinWeight: "WEIGHTS_0",
      skinIndex: "JOINTS_0"
    };
    const originalNormal = geometry.getAttribute("normal");
    if (originalNormal !== void 0 && !this.isNormalizedNormalAttribute(originalNormal)) {
      console.warn("THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.");
      geometry.setAttribute("normal", this.createNormalizedNormalAttribute(originalNormal));
    }
    let modifiedAttribute = null;
    for (let attributeName in geometry.attributes) {
      if (attributeName.slice(0, 5) === "morph")
        continue;
      const attribute = geometry.attributes[attributeName];
      attributeName = nameConversion[attributeName] || attributeName.toUpperCase();
      const validVertexAttributes = /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/;
      if (!validVertexAttributes.test(attributeName))
        attributeName = "_" + attributeName;
      if (cache.attributes.has(this.getUID(attribute))) {
        attributes[attributeName] = cache.attributes.get(this.getUID(attribute));
        continue;
      }
      modifiedAttribute = null;
      const array = attribute.array;
      if (attributeName === "JOINTS_0" && !(array instanceof Uint16Array) && !(array instanceof Uint8Array)) {
        console.warn('GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.');
        modifiedAttribute = new BufferAttribute(new Uint16Array(array), attribute.itemSize, attribute.normalized);
      }
      const accessor = this.processAccessor(modifiedAttribute || attribute, geometry);
      if (accessor !== null) {
        if (!attributeName.startsWith("_")) {
          this.detectMeshQuantization(attributeName, attribute);
        }
        attributes[attributeName] = accessor;
        cache.attributes.set(this.getUID(attribute), accessor);
      }
    }
    if (originalNormal !== void 0)
      geometry.setAttribute("normal", originalNormal);
    if (Object.keys(attributes).length === 0)
      return null;
    if (mesh.morphTargetInfluences !== void 0 && mesh.morphTargetInfluences.length > 0) {
      const weights = [];
      const targetNames = [];
      const reverseDictionary = {};
      if (mesh.morphTargetDictionary !== void 0) {
        for (const key in mesh.morphTargetDictionary) {
          reverseDictionary[mesh.morphTargetDictionary[key]] = key;
        }
      }
      for (let i = 0; i < mesh.morphTargetInfluences.length; ++i) {
        const target = {};
        let warned = false;
        for (const attributeName in geometry.morphAttributes) {
          if (attributeName !== "position" && attributeName !== "normal") {
            if (!warned) {
              console.warn("GLTFExporter: Only POSITION and NORMAL morph are supported.");
              warned = true;
            }
            continue;
          }
          const attribute = geometry.morphAttributes[attributeName][i];
          const gltfAttributeName = attributeName.toUpperCase();
          const baseAttribute = geometry.attributes[attributeName];
          if (cache.attributes.has(this.getUID(attribute, true))) {
            target[gltfAttributeName] = cache.attributes.get(this.getUID(attribute, true));
            continue;
          }
          const relativeAttribute = attribute.clone();
          if (!geometry.morphTargetsRelative) {
            for (let j = 0, jl = attribute.count; j < jl; j++) {
              for (let a = 0; a < attribute.itemSize; a++) {
                if (a === 0)
                  relativeAttribute.setX(j, attribute.getX(j) - baseAttribute.getX(j));
                if (a === 1)
                  relativeAttribute.setY(j, attribute.getY(j) - baseAttribute.getY(j));
                if (a === 2)
                  relativeAttribute.setZ(j, attribute.getZ(j) - baseAttribute.getZ(j));
                if (a === 3)
                  relativeAttribute.setW(j, attribute.getW(j) - baseAttribute.getW(j));
              }
            }
          }
          target[gltfAttributeName] = this.processAccessor(relativeAttribute, geometry);
          cache.attributes.set(this.getUID(baseAttribute, true), target[gltfAttributeName]);
        }
        targets.push(target);
        weights.push(mesh.morphTargetInfluences[i]);
        if (mesh.morphTargetDictionary !== void 0)
          targetNames.push(reverseDictionary[i]);
      }
      meshDef.weights = weights;
      if (targetNames.length > 0) {
        meshDef.extras = {};
        meshDef.extras.targetNames = targetNames;
      }
    }
    const isMultiMaterial = Array.isArray(mesh.material);
    if (isMultiMaterial && geometry.groups.length === 0)
      return null;
    const materials = isMultiMaterial ? mesh.material : [mesh.material];
    const groups = isMultiMaterial ? geometry.groups : [{ materialIndex: 0, start: void 0, count: void 0 }];
    for (let i = 0, il = groups.length; i < il; i++) {
      const primitive = {
        mode,
        attributes
      };
      this.serializeUserData(geometry, primitive);
      if (targets.length > 0)
        primitive.targets = targets;
      if (geometry.index !== null) {
        let cacheKey = this.getUID(geometry.index);
        if (groups[i].start !== void 0 || groups[i].count !== void 0) {
          cacheKey += ":" + groups[i].start + ":" + groups[i].count;
        }
        if (cache.attributes.has(cacheKey)) {
          primitive.indices = cache.attributes.get(cacheKey);
        } else {
          primitive.indices = this.processAccessor(geometry.index, geometry, groups[i].start, groups[i].count);
          cache.attributes.set(cacheKey, primitive.indices);
        }
        if (primitive.indices === null)
          delete primitive.indices;
      }
      const material = this.processMaterial(materials[groups[i].materialIndex]);
      if (material !== null)
        primitive.material = material;
      primitives.push(primitive);
    }
    meshDef.primitives = primitives;
    if (!json.meshes)
      json.meshes = [];
    this._invokeAll(function(ext) {
      ext.writeMesh && ext.writeMesh(mesh, meshDef);
    });
    const index = json.meshes.push(meshDef) - 1;
    cache.meshes.set(meshCacheKey, index);
    return index;
  }
  /**
   * If a vertex attribute with a
   * [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
   * is used, it is checked whether it is a valid data type according to the
   * [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md)
   * extension.
   * In this case the extension is automatically added to the list of used extensions.
   *
   * @param {string} attributeName
   * @param {THREE.BufferAttribute} attribute
   */
  detectMeshQuantization(attributeName, attribute) {
    if (this.extensionsUsed[KHR_MESH_QUANTIZATION])
      return;
    let attrType = void 0;
    switch (attribute.array.constructor) {
      case Int8Array:
        attrType = "byte";
        break;
      case Uint8Array:
        attrType = "unsigned byte";
        break;
      case Int16Array:
        attrType = "short";
        break;
      case Uint16Array:
        attrType = "unsigned short";
        break;
      default:
        return;
    }
    if (attribute.normalized)
      attrType += " normalized";
    const attrNamePrefix = attributeName.split("_", 1)[0];
    if (KHR_mesh_quantization_ExtraAttrTypes[attrNamePrefix] && KHR_mesh_quantization_ExtraAttrTypes[attrNamePrefix].includes(attrType)) {
      this.extensionsUsed[KHR_MESH_QUANTIZATION] = true;
      this.extensionsRequired[KHR_MESH_QUANTIZATION] = true;
    }
  }
  /**
   * Process camera
   * @param  {THREE.Camera} camera Camera to process
   * @return {Integer}      Index of the processed mesh in the "camera" array
   */
  processCamera(camera) {
    const json = this.json;
    if (!json.cameras)
      json.cameras = [];
    const isOrtho = camera.isOrthographicCamera;
    const cameraDef = {
      type: isOrtho ? "orthographic" : "perspective"
    };
    if (isOrtho) {
      cameraDef.orthographic = {
        xmag: camera.right * 2,
        ymag: camera.top * 2,
        zfar: camera.far <= 0 ? 1e-3 : camera.far,
        znear: camera.near < 0 ? 0 : camera.near
      };
    } else {
      cameraDef.perspective = {
        aspectRatio: camera.aspect,
        yfov: MathUtils.degToRad(camera.fov),
        zfar: camera.far <= 0 ? 1e-3 : camera.far,
        znear: camera.near < 0 ? 0 : camera.near
      };
    }
    if (camera.name !== "")
      cameraDef.name = camera.type;
    return json.cameras.push(cameraDef) - 1;
  }
  /**
   * Creates glTF animation entry from AnimationClip object.
   *
   * Status:
   * - Only properties listed in PATH_PROPERTIES may be animated.
   *
   * @param {THREE.AnimationClip} clip
   * @param {THREE.Object3D} root
   * @return {number|null}
   */
  processAnimation(clip, root) {
    const json = this.json;
    const nodeMap = this.nodeMap;
    if (!json.animations)
      json.animations = [];
    clip = GLTFExporter.Utils.mergeMorphTargetTracks(clip.clone(), root);
    const tracks = clip.tracks;
    const channels = [];
    const samplers = [];
    for (let i = 0; i < tracks.length; ++i) {
      const track = tracks[i];
      const trackBinding = PropertyBinding.parseTrackName(track.name);
      let trackNode = PropertyBinding.findNode(root, trackBinding.nodeName);
      const trackProperty = PATH_PROPERTIES[trackBinding.propertyName];
      if (trackBinding.objectName === "bones") {
        if (trackNode.isSkinnedMesh === true) {
          trackNode = trackNode.skeleton.getBoneByName(trackBinding.objectIndex);
        } else {
          trackNode = void 0;
        }
      }
      if (!trackNode || !trackProperty) {
        console.warn('THREE.GLTFExporter: Could not export animation track "%s".', track.name);
        return null;
      }
      const inputItemSize = 1;
      let outputItemSize = track.values.length / track.times.length;
      if (trackProperty === PATH_PROPERTIES.morphTargetInfluences) {
        outputItemSize /= trackNode.morphTargetInfluences.length;
      }
      let interpolation;
      if (track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true) {
        interpolation = "CUBICSPLINE";
        outputItemSize /= 3;
      } else if (track.getInterpolation() === InterpolateDiscrete) {
        interpolation = "STEP";
      } else {
        interpolation = "LINEAR";
      }
      samplers.push({
        input: this.processAccessor(new BufferAttribute(track.times, inputItemSize)),
        output: this.processAccessor(new BufferAttribute(track.values, outputItemSize)),
        interpolation
      });
      channels.push({
        sampler: samplers.length - 1,
        target: {
          node: nodeMap.get(trackNode),
          path: trackProperty
        }
      });
    }
    json.animations.push({
      name: clip.name || "clip_" + json.animations.length,
      samplers,
      channels
    });
    return json.animations.length - 1;
  }
  /**
   * @param {THREE.Object3D} object
   * @return {number|null}
   */
  processSkin(object) {
    const json = this.json;
    const nodeMap = this.nodeMap;
    const node = json.nodes[nodeMap.get(object)];
    const skeleton = object.skeleton;
    if (skeleton === void 0)
      return null;
    const rootJoint = object.skeleton.bones[0];
    if (rootJoint === void 0)
      return null;
    const joints = [];
    const inverseBindMatrices = new Float32Array(skeleton.bones.length * 16);
    const temporaryBoneInverse = new Matrix4();
    for (let i = 0; i < skeleton.bones.length; ++i) {
      joints.push(nodeMap.get(skeleton.bones[i]));
      temporaryBoneInverse.copy(skeleton.boneInverses[i]);
      temporaryBoneInverse.multiply(object.bindMatrix).toArray(inverseBindMatrices, i * 16);
    }
    if (json.skins === void 0)
      json.skins = [];
    json.skins.push({
      inverseBindMatrices: this.processAccessor(new BufferAttribute(inverseBindMatrices, 16)),
      joints,
      skeleton: nodeMap.get(rootJoint)
    });
    const skinIndex = node.skin = json.skins.length - 1;
    return skinIndex;
  }
  /**
   * Process Object3D node
   * @param  {THREE.Object3D} node Object3D to processNode
   * @return {Integer} Index of the node in the nodes list
   */
  processNode(object) {
    const json = this.json;
    const options = this.options;
    const nodeMap = this.nodeMap;
    if (!json.nodes)
      json.nodes = [];
    const nodeDef = {};
    if (options.trs) {
      const rotation = object.quaternion.toArray();
      const position = object.position.toArray();
      const scale = object.scale.toArray();
      if (!equalArray(rotation, [0, 0, 0, 1])) {
        nodeDef.rotation = rotation;
      }
      if (!equalArray(position, [0, 0, 0])) {
        nodeDef.translation = position;
      }
      if (!equalArray(scale, [1, 1, 1])) {
        nodeDef.scale = scale;
      }
    } else {
      if (object.matrixAutoUpdate) {
        object.updateMatrix();
      }
      if (isIdentityMatrix(object.matrix) === false) {
        nodeDef.matrix = object.matrix.elements;
      }
    }
    if (object.name !== "")
      nodeDef.name = String(object.name);
    this.serializeUserData(object, nodeDef);
    if (object.isMesh || object.isLine || object.isPoints) {
      const meshIndex = this.processMesh(object);
      if (meshIndex !== null)
        nodeDef.mesh = meshIndex;
    } else if (object.isCamera) {
      nodeDef.camera = this.processCamera(object);
    }
    if (object.isSkinnedMesh)
      this.skins.push(object);
    if (object.children.length > 0) {
      const children = [];
      for (let i = 0, l = object.children.length; i < l; i++) {
        const child = object.children[i];
        if (child.visible || options.onlyVisible === false) {
          const nodeIndex2 = this.processNode(child);
          if (nodeIndex2 !== null)
            children.push(nodeIndex2);
        }
      }
      if (children.length > 0)
        nodeDef.children = children;
    }
    this._invokeAll(function(ext) {
      ext.writeNode && ext.writeNode(object, nodeDef);
    });
    const nodeIndex = json.nodes.push(nodeDef) - 1;
    nodeMap.set(object, nodeIndex);
    return nodeIndex;
  }
  /**
   * Process Scene
   * @param  {Scene} node Scene to process
   */
  processScene(scene) {
    const json = this.json;
    const options = this.options;
    if (!json.scenes) {
      json.scenes = [];
      json.scene = 0;
    }
    const sceneDef = {};
    if (scene.name !== "")
      sceneDef.name = scene.name;
    json.scenes.push(sceneDef);
    const nodes = [];
    for (let i = 0, l = scene.children.length; i < l; i++) {
      const child = scene.children[i];
      if (child.visible || options.onlyVisible === false) {
        const nodeIndex = this.processNode(child);
        if (nodeIndex !== null)
          nodes.push(nodeIndex);
      }
    }
    if (nodes.length > 0)
      sceneDef.nodes = nodes;
    this.serializeUserData(scene, sceneDef);
  }
  /**
   * Creates a Scene to hold a list of objects and parse it
   * @param  {Array} objects List of objects to process
   */
  processObjects(objects) {
    const scene = new Scene();
    scene.name = "AuxScene";
    for (let i = 0; i < objects.length; i++) {
      scene.children.push(objects[i]);
    }
    this.processScene(scene);
  }
  /**
   * @param {THREE.Object3D|Array<THREE.Object3D>} input
   */
  processInput(input) {
    const options = this.options;
    input = input instanceof Array ? input : [input];
    this._invokeAll(function(ext) {
      ext.beforeParse && ext.beforeParse(input);
    });
    const objectsWithoutScene = [];
    for (let i = 0; i < input.length; i++) {
      if (input[i] instanceof Scene) {
        this.processScene(input[i]);
      } else {
        objectsWithoutScene.push(input[i]);
      }
    }
    if (objectsWithoutScene.length > 0)
      this.processObjects(objectsWithoutScene);
    for (let i = 0; i < this.skins.length; ++i) {
      this.processSkin(this.skins[i]);
    }
    for (let i = 0; i < options.animations.length; ++i) {
      this.processAnimation(options.animations[i], input[0]);
    }
    this._invokeAll(function(ext) {
      ext.afterParse && ext.afterParse(input);
    });
  }
  _invokeAll(func) {
    for (let i = 0, il = this.plugins.length; i < il; i++) {
      func(this.plugins[i]);
    }
  }
}
class GLTFLightExtension {
  constructor(writer) {
    this.writer = writer;
    this.name = "KHR_lights_punctual";
  }
  writeNode(light, nodeDef) {
    if (!light.isLight)
      return;
    if (!light.isDirectionalLight && !light.isPointLight && !light.isSpotLight) {
      console.warn("THREE.GLTFExporter: Only directional, point, and spot lights are supported.", light);
      return;
    }
    const writer = this.writer;
    const json = writer.json;
    const extensionsUsed = writer.extensionsUsed;
    const lightDef = {};
    if (light.name)
      lightDef.name = light.name;
    lightDef.color = light.color.toArray();
    lightDef.intensity = light.intensity;
    if (light.isDirectionalLight) {
      lightDef.type = "directional";
    } else if (light.isPointLight) {
      lightDef.type = "point";
      if (light.distance > 0)
        lightDef.range = light.distance;
    } else if (light.isSpotLight) {
      lightDef.type = "spot";
      if (light.distance > 0)
        lightDef.range = light.distance;
      lightDef.spot = {};
      lightDef.spot.innerConeAngle = (light.penumbra - 1) * light.angle * -1;
      lightDef.spot.outerConeAngle = light.angle;
    }
    if (light.decay !== void 0 && light.decay !== 2) {
      console.warn(
        "THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, and expects light.decay=2."
      );
    }
    if (light.target && (light.target.parent !== light || light.target.position.x !== 0 || light.target.position.y !== 0 || light.target.position.z !== -1)) {
      console.warn(
        "THREE.GLTFExporter: Light direction may be lost. For best results, make light.target a child of the light with position 0,0,-1."
      );
    }
    if (!extensionsUsed[this.name]) {
      json.extensions = json.extensions || {};
      json.extensions[this.name] = { lights: [] };
      extensionsUsed[this.name] = true;
    }
    const lights = json.extensions[this.name].lights;
    lights.push(lightDef);
    nodeDef.extensions = nodeDef.extensions || {};
    nodeDef.extensions[this.name] = { light: lights.length - 1 };
  }
}
class GLTFMaterialsUnlitExtension {
  constructor(writer) {
    this.writer = writer;
    this.name = "KHR_materials_unlit";
  }
  writeMaterial(material, materialDef) {
    if (!material.isMeshBasicMaterial)
      return;
    const writer = this.writer;
    const extensionsUsed = writer.extensionsUsed;
    materialDef.extensions = materialDef.extensions || {};
    materialDef.extensions[this.name] = {};
    extensionsUsed[this.name] = true;
    materialDef.pbrMetallicRoughness.metallicFactor = 0;
    materialDef.pbrMetallicRoughness.roughnessFactor = 0.9;
  }
}
class GLTFMaterialsClearcoatExtension {
  constructor(writer) {
    this.writer = writer;
    this.name = "KHR_materials_clearcoat";
  }
  writeMaterial(material, materialDef) {
    if (!material.isMeshPhysicalMaterial || material.clearcoat === 0)
      return;
    const writer = this.writer;
    const extensionsUsed = writer.extensionsUsed;
    const extensionDef = {};
    extensionDef.clearcoatFactor = material.clearcoat;
    if (material.clearcoatMap) {
      const clearcoatMapDef = {
        index: writer.processTexture(material.clearcoatMap),
        texCoord: material.clearcoatMap.channel
      };
      writer.applyTextureTransform(clearcoatMapDef, material.clearcoatMap);
      extensionDef.clearcoatTexture = clearcoatMapDef;
    }
    extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness;
    if (material.clearcoatRoughnessMap) {
      const clearcoatRoughnessMapDef = {
        index: writer.processTexture(material.clearcoatRoughnessMap),
        texCoord: material.clearcoatRoughnessMap.channel
      };
      writer.applyTextureTransform(clearcoatRoughnessMapDef, material.clearcoatRoughnessMap);
      extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef;
    }
    if (material.clearcoatNormalMap) {
      const clearcoatNormalMapDef = {
        index: writer.processTexture(material.clearcoatNormalMap),
        texCoord: material.clearcoatNormalMap.channel
      };
      writer.applyTextureTransform(clearcoatNormalMapDef, material.clearcoatNormalMap);
      extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef;
    }
    materialDef.extensions = materialDef.extensions || {};
    materialDef.extensions[this.name] = extensionDef;
    extensionsUsed[this.name] = true;
  }
}
class GLTFMaterialsIridescenceExtension {
  constructor(writer) {
    this.writer = writer;
    this.name = "KHR_materials_iridescence";
  }
  writeMaterial(material, materialDef) {
    if (!material.isMeshPhysicalMaterial || material.iridescence === 0)
      return;
    const writer = this.writer;
    const extensionsUsed = writer.extensionsUsed;
    const extensionDef = {};
    extensionDef.iridescenceFactor = material.iridescence;
    if (material.iridescenceMap) {
      const iridescenceMapDef = {
        index: writer.processTexture(material.iridescenceMap),
        texCoord: material.iridescenceMap.channel
      };
      writer.applyTextureTransform(iridescenceMapDef, material.iridescenceMap);
      extensionDef.iridescenceTexture = iridescenceMapDef;
    }
    extensionDef.iridescenceIor = material.iridescenceIOR;
    extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[0];
    extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[1];
    if (material.iridescenceThicknessMap) {
      const iridescenceThicknessMapDef = {
        index: writer.processTexture(material.iridescenceThicknessMap),
        texCoord: material.iridescenceThicknessMap.channel
      };
      writer.applyTextureTransform(iridescenceThicknessMapDef, material.iridescenceThicknessMap);
      extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef;
    }
    materialDef.extensions = materialDef.extensions || {};
    materialDef.extensions[this.name] = extensionDef;
    extensionsUsed[this.name] = true;
  }
}
class GLTFMaterialsTransmissionExtension {
  constructor(writer) {
    this.writer = writer;
    this.name = "KHR_materials_transmission";
  }
  writeMaterial(material, materialDef) {
    if (!material.isMeshPhysicalMaterial || material.transmission === 0)
      return;
    const writer = this.writer;
    const extensionsUsed = writer.extensionsUsed;
    const extensionDef = {};
    extensionDef.transmissionFactor = material.transmission;
    if (material.transmissionMap) {
      const transmissionMapDef = {
        index: writer.processTexture(material.transmissionMap),
        texCoord: material.transmissionMap.channel
      };
      writer.applyTextureTransform(transmissionMapDef, material.transmissionMap);
      extensionDef.transmissionTexture = transmissionMapDef;
    }
    materialDef.extensions = materialDef.extensions || {};
    materialDef.extensions[this.name] = extensionDef;
    extensionsUsed[this.name] = true;
  }
}
class GLTFMaterialsVolumeExtension {
  constructor(writer) {
    this.writer = writer;
    this.name = "KHR_materials_volume";
  }
  writeMaterial(material, materialDef) {
    if (!material.isMeshPhysicalMaterial || material.transmission === 0)
      return;
    const writer = this.writer;
    const extensionsUsed = writer.extensionsUsed;
    const extensionDef = {};
    extensionDef.thicknessFactor = material.thickness;
    if (material.thicknessMap) {
      const thicknessMapDef = {
        index: writer.processTexture(material.thicknessMap),
        texCoord: material.thicknessMap.channel
      };
      writer.applyTextureTransform(thicknessMapDef, material.thicknessMap);
      extensionDef.thicknessTexture = thicknessMapDef;
    }
    extensionDef.attenuationDistance = material.attenuationDistance;
    extensionDef.attenuationColor = material.attenuationColor.toArray();
    materialDef.extensions = materialDef.extensions || {};
    materialDef.extensions[this.name] = extensionDef;
    extensionsUsed[this.name] = true;
  }
}
class GLTFMaterialsIorExtension {
  constructor(writer) {
    this.writer = writer;
    this.name = "KHR_materials_ior";
  }
  writeMaterial(material, materialDef) {
    if (!material.isMeshPhysicalMaterial || material.ior === 1.5)
      return;
    const writer = this.writer;
    const extensionsUsed = writer.extensionsUsed;
    const extensionDef = {};
    extensionDef.ior = material.ior;
    materialDef.extensions = materialDef.extensions || {};
    materialDef.extensions[this.name] = extensionDef;
    extensionsUsed[this.name] = true;
  }
}
class GLTFMaterialsSpecularExtension {
  constructor(writer) {
    this.writer = writer;
    this.name = "KHR_materials_specular";
  }
  writeMaterial(material, materialDef) {
    if (!material.isMeshPhysicalMaterial || material.specularIntensity === 1 && material.specularColor.equals(DEFAULT_SPECULAR_COLOR) && !material.specularIntensityMap && !material.specularColorTexture)
      return;
    const writer = this.writer;
    const extensionsUsed = writer.extensionsUsed;
    const extensionDef = {};
    if (material.specularIntensityMap) {
      const specularIntensityMapDef = {
        index: writer.processTexture(material.specularIntensityMap),
        texCoord: material.specularIntensityMap.channel
      };
      writer.applyTextureTransform(specularIntensityMapDef, material.specularIntensityMap);
      extensionDef.specularTexture = specularIntensityMapDef;
    }
    if (material.specularColorMap) {
      const specularColorMapDef = {
        index: writer.processTexture(material.specularColorMap),
        texCoord: material.specularColorMap.channel
      };
      writer.applyTextureTransform(specularColorMapDef, material.specularColorMap);
      extensionDef.specularColorTexture = specularColorMapDef;
    }
    extensionDef.specularFactor = material.specularIntensity;
    extensionDef.specularColorFactor = material.specularColor.toArray();
    materialDef.extensions = materialDef.extensions || {};
    materialDef.extensions[this.name] = extensionDef;
    extensionsUsed[this.name] = true;
  }
}
class GLTFMaterialsSheenExtension {
  constructor(writer) {
    this.writer = writer;
    this.name = "KHR_materials_sheen";
  }
  writeMaterial(material, materialDef) {
    if (!material.isMeshPhysicalMaterial || material.sheen == 0)
      return;
    const writer = this.writer;
    const extensionsUsed = writer.extensionsUsed;
    const extensionDef = {};
    if (material.sheenRoughnessMap) {
      const sheenRoughnessMapDef = {
        index: writer.processTexture(material.sheenRoughnessMap),
        texCoord: material.sheenRoughnessMap.channel
      };
      writer.applyTextureTransform(sheenRoughnessMapDef, material.sheenRoughnessMap);
      extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef;
    }
    if (material.sheenColorMap) {
      const sheenColorMapDef = {
        index: writer.processTexture(material.sheenColorMap),
        texCoord: material.sheenColorMap.channel
      };
      writer.applyTextureTransform(sheenColorMapDef, material.sheenColorMap);
      extensionDef.sheenColorTexture = sheenColorMapDef;
    }
    extensionDef.sheenRoughnessFactor = material.sheenRoughness;
    extensionDef.sheenColorFactor = material.sheenColor.toArray();
    materialDef.extensions = materialDef.extensions || {};
    materialDef.extensions[this.name] = extensionDef;
    extensionsUsed[this.name] = true;
  }
}
class GLTFMaterialsAnisotropyExtension {
  constructor(writer) {
    this.writer = writer;
    this.name = "KHR_materials_anisotropy";
  }
  writeMaterial(material, materialDef) {
    if (!material.isMeshPhysicalMaterial || material.anisotropy == 0)
      return;
    const writer = this.writer;
    const extensionsUsed = writer.extensionsUsed;
    const extensionDef = {};
    if (material.anisotropyMap) {
      const anisotropyMapDef = { index: writer.processTexture(material.anisotropyMap) };
      writer.applyTextureTransform(anisotropyMapDef, material.anisotropyMap);
      extensionDef.anisotropyTexture = anisotropyMapDef;
    }
    extensionDef.anisotropyStrength = material.anisotropy;
    extensionDef.anisotropyRotation = material.anisotropyRotation;
    materialDef.extensions = materialDef.extensions || {};
    materialDef.extensions[this.name] = extensionDef;
    extensionsUsed[this.name] = true;
  }
}
class GLTFMaterialsEmissiveStrengthExtension {
  constructor(writer) {
    this.writer = writer;
    this.name = "KHR_materials_emissive_strength";
  }
  writeMaterial(material, materialDef) {
    if (!material.isMeshStandardMaterial || material.emissiveIntensity === 1)
      return;
    const writer = this.writer;
    const extensionsUsed = writer.extensionsUsed;
    const extensionDef = {};
    extensionDef.emissiveStrength = material.emissiveIntensity;
    materialDef.extensions = materialDef.extensions || {};
    materialDef.extensions[this.name] = extensionDef;
    extensionsUsed[this.name] = true;
  }
}
export {
  GLTFExporter
};
//# sourceMappingURL=GLTFExporter.js.map