text: OV.ThreeLoader = class
  {
      constructor ()
      {
  
      }
  
      GetExtension ()
      {
          return null;
      }
  
      GetExternalLibraries ()
      {
          return null;
      }
  
      CreateLoader (manager)
      {
          return null;
      }
  
      EnumerateMeshes (loadedObject, processor)
      {
          
      }
  
      GetUpDirection ()
      {
          return null;
      }
  };
  
  OV.ThreeLoaderFbx = class extends OV.ThreeLoader
  {
      constructor ()
      {
          super ();
      }
  
      GetExtension ()
      {
          return 'fbx';
      }
  
      GetExternalLibraries ()
      {
          return [
              'three_loaders/fflate.min.js',
              'three_loaders/TGALoader.js',
              'three_loaders/FBXLoader.js'
          ];
      }
  
      CreateLoader (manager)
      {
          manager.addHandler (/\.tga$/i, new THREE.TGALoader (manager));
          return new THREE.FBXLoader (manager);
      }
  
      EnumerateMeshes (loadedObject, processor)
      {
          loadedObject.traverse ((child) => {
              if (child.isMesh) {
                  processor (child);
              }
          });
      }
  
      GetUpDirection ()
      {
          return OV.Direction.Y;
      }
  };
  
  OV.ThreeLoaderDae = class extends OV.ThreeLoader
  {
      constructor ()
      {
          super ();
      }
  
      GetExtension ()
      {
          return 'dae';
      }
  
      GetExternalLibraries ()
      {
          return [
              'three_loaders/TGALoader.js',
              'three_loaders/ColladaLoader.js'
          ];
      }
  
      CreateLoader (manager)
      {
          manager.addHandler (/\.tga$/i, new THREE.TGALoader (manager));
          return new THREE.ColladaLoader (manager);
      }
  
      EnumerateMeshes (loadedObject, processor)
      {
          loadedObject.scene.traverse ((child) => {
              if (child.isMesh) {
                  processor (child);
              }
          });
      }
  
      GetUpDirection ()
      {
          return OV.Direction.Y;
      }
  };
  
  OV.ThreeLoaderVrml = class extends OV.ThreeLoader
  {
      constructor ()
      {
          super ();
      }
  
      GetExtension ()
      {
          return 'wrl';
      }
  
      GetExternalLibraries ()
      {
          return [
              'three_loaders/chevrotain.min.js',
              'three_loaders/VRMLLoader.js'
          ];
      }
  
      CreateLoader (manager)
      {
          return new THREE.VRMLLoader (manager);
      }
  
      EnumerateMeshes (loadedObject, processor)
      {
          loadedObject.traverse ((child) => {
              if (child.isMesh) {
                  let needToProcess = true;
                  if (Array.isArray (child.material)) {
                      for (let i = 0; i < child.material.length; i++) {
                          if (child.material[i].side === THREE.BackSide) {
                              needToProcess = false;
                              break;
                          }
                      }
                  } else {
                      needToProcess = (child.material.side !== THREE.BackSide);
                  }
                  if (needToProcess) {
                      processor (child);
                  }
              }
          });
      }
  
      GetUpDirection ()
      {
          return OV.Direction.Y;
      }
  };
  
  OV.ThreeLoader3mf = class extends OV.ThreeLoader
  {
      constructor ()
      {
          super ();
      }
  
      GetExtension ()
      {
          return '3mf';
      }
  
      GetExternalLibraries ()
      {
          return [
              'three_loaders/fflate.min.js',
              'three_loaders/3MFLoader.js'
          ];
      }
  
      CreateLoader (manager)
      {
          return new THREE.ThreeMFLoader (manager);
      }
  
      EnumerateMeshes (loadedObject, processor)
      {
          loadedObject.traverse ((child) => {
              if (child.isMesh) {
                  processor (child);
              }
          });
      }
  
      GetUpDirection ()
      {
          return OV.Direction.Z;
      }
  };
  
  OV.ThreeImporter = class extends OV.ImporterBase
  {
      constructor ()
      {
          super ();
          this.loaders = [
              new OV.ThreeLoaderFbx (),
              new OV.ThreeLoaderDae (),
              new OV.ThreeLoaderVrml (),
              new OV.ThreeLoader3mf ()
          ];
      }
  
      CanImportExtension (extension)
      {
          for (let i = 0; i < this.loaders.length; i++)  {
              let loader = this.loaders[i];
              if (loader.GetExtension () === extension) {
                  return true;
              }
          }
          return false;
      }
  
      GetKnownFileFormats ()
      {
          let result = {};
          for (let i = 0; i < this.loaders.length; i++)  {
              let loader = this.loaders[i];
              result[loader.GetExtension ()] = OV.FileFormat.Binary;
          }
          return result;
      }
      
      GetUpDirection ()
      {
          return this.loader.GetUpDirection ();
      }    
      
      ClearContent ()
      {
          this.loader = null;
      }
  
      ResetContent ()
      {
          this.loader = null;
      }
  
      ImportContent (fileContent, onFinish)
      {
          async function LoadLibraries (libraries, onFinish, onError)
          {
              try {
                  for (let i = 0; i < libraries.length; i++) {
                      await OV.LoadExternalLibrary (libraries[i]);
                  }
              } catch (err) {
                  onError ();
              }
              onFinish ();
          }
  
          this.loader = this.FindLoader ();
          if (this.loader === null) {
              onFinish ();
              return;
          }
          
          const libraries = this.loader.GetExternalLibraries ();
          if (libraries === null) {
              onFinish ();
              return;
          }
  
          LoadLibraries (libraries, () => {
              this.LoadModel (fileContent, onFinish);    
          }, () => {
              onFinish ();
          });
      }
  
      FindLoader ()
      {
          for (let i = 0; i < this.loaders.length; i++)  {
              let loader = this.loaders[i];
              if (loader.GetExtension () === this.extension) {
                  return loader;
              }
          }
          return null;
      }
  
      LoadModel (fileContent, onFinish)
      {
          let loadedObject = null;
          let externalFileNames = {};
          let loadingManager = new THREE.LoadingManager (() => {
              if (loadedObject !== null) {
                  this.OnThreeObjectsLoaded (loadedObject, externalFileNames, onFinish);
              }
          });
  
          const mainFileUrl = OV.CreateObjectUrl (fileContent);
          loadingManager.setURLModifier ((url) => {
              if (url === mainFileUrl) {
                  return url;
              }
              if (url.startsWith ('blob:')) {
                  const name = OV.GetFileName (url);
                  const extension = OV.GetFileExtension (url);
                  if (extension.length > 0) {
                      const buffer = this.callbacks.getFileBuffer (url);
                      if (buffer !== null) {
                          let objectUrl = OV.CreateObjectUrl (buffer);
                          externalFileNames[objectUrl] = name;
                          return objectUrl;
                      }
                  }
              }
              return url;
          });
  
          const threeLoader = this.loader.CreateLoader (loadingManager);
          if (threeLoader === null) {
              onFinish ();
              return;
          }
  
          threeLoader.load (mainFileUrl,
              (object) => {
                  loadedObject = object;
              },
              () => {
              },
              (err) => {
                  this.SetError ();
                  this.SetMessage (err);
                  onFinish ();
              }
          );
      }
  
      OnThreeObjectsLoaded (loadedObject, externalFileNames, onFinish)
      {
          function ConvertThreeMaterialToMaterial (threeMaterial, externalFileNames)
          {
              function SetColor (color, threeColor)
              {
                  color.Set (
                      parseInt (threeColor.r * 255.0, 10),
                      parseInt (threeColor.g * 255.0, 10),
                      parseInt (threeColor.b * 255.0, 10)
                  );
              }
          
              function CreateTexture (threeMap, externalFileNames)
              {
                  function GetDataUrl (img)
                  {
                      if (img.data !== undefined && img.data !== null) {
                          let imageData = new ImageData (img.width, img.height);
                          let imageSize = img.width * img.height * 4;
                          for (let i = 0; i < imageSize; i++) {
                              imageData.data[i] = img.data[i];
                          }                        
                          return THREE.ImageUtils.getDataURL (imageData);
                      } else {
                          return THREE.ImageUtils.getDataURL (threeMap.image);
                      }
                  }
  
                  if (threeMap === undefined || threeMap === null) {
                      return null;
                  }
  
                  if (threeMap.image === undefined || threeMap.image === null) {
                      return null;
                  }
  
                  try {
                      const dataUrl = GetDataUrl (threeMap.image);
                      const base64Buffer = OV.Base64DataURIToArrayBuffer (dataUrl);
                      let texture = new OV.TextureMap ();
                      let textureName = externalFileNames[threeMap.image.src];
                      if (textureName === undefined) {
                          textureName = 'Embedded_' + threeMap.id.toString () + '.' + OV.GetFileExtensionFromMimeType (base64Buffer.mimeType);
                      }
                      texture.name = textureName;
                      texture.url = dataUrl;
                      texture.buffer = base64Buffer.buffer;
                      texture.rotation = threeMap.rotation;
                      texture.offset.x = threeMap.offset.x;
                      texture.offset.y = threeMap.offset.y;
                      texture.scale.x = threeMap.repeat.x;
                      texture.scale.y = threeMap.repeat.y;
                      return texture;
                  } catch (err) {
                      return null;
                  }
              }
  
              let material = new OV.Material (OV.MaterialType.Phong);
              material.name = threeMaterial.name;
              SetColor (material.color, threeMaterial.color);
              material.opacity = threeMaterial.opacity;
              material.transparent = threeMaterial.transparent;
              material.alphaTest = threeMaterial.alphaTest;
              if (threeMaterial.type === 'MeshPhongMaterial') {
                  SetColor (material.specular, threeMaterial.specular);
                  material.shininess = threeMaterial.shininess / 100.0;
              }
              material.diffuseMap = CreateTexture (threeMaterial.map, externalFileNames);
              material.normalMap = CreateTexture (threeMaterial.normalMap, externalFileNames);
              material.bumpMap = CreateTexture (threeMaterial.bumpMap, externalFileNames);
  
              return material;
          }
  
          function FindMatchingMaterialIndex (model, threeMaterial, materialIdToIndex, externalFileNames)
          {
              let index = materialIdToIndex[threeMaterial.id];
              if (index !== undefined) {
                  return index;
              }
              let material = ConvertThreeMaterialToMaterial (threeMaterial, externalFileNames);
              index = model.AddMaterial (material);
              materialIdToIndex[threeMaterial.id] = index;
              return index;
          }
  
          let materialIdToIndex = {};
          this.loader.EnumerateMeshes (loadedObject, (child) => {
              let materialIndex = null;
              let mesh = null;
              if (Array.isArray (child.material)) {
                  mesh = OV.ConvertThreeGeometryToMesh (child.geometry, null);
                  if (child.geometry.attributes.color === undefined || child.geometry.attributes.color === null) {
                      let materialIndices = [];
                      for (let i = 0; i < child.material.length; i++) {
                          let material = child.material[i];
                          materialIndices.push (FindMatchingMaterialIndex (this.model, material, materialIdToIndex, externalFileNames));
                      }
                      for (let i = 0; i < child.geometry.groups.length; i++) {
                          let group = child.geometry.groups[i];
                          for (let j = group.start / 3; j < group.start / 3 + group.count / 3; j++) {
                              let triangle = mesh.GetTriangle (j);
                              triangle.SetMaterial (materialIndices[group.materialIndex]);
                          }
                      }
                  }
              } else {
                  materialIndex = FindMatchingMaterialIndex (this.model, child.material, materialIdToIndex, externalFileNames);
                  mesh = OV.ConvertThreeGeometryToMesh (child.geometry, materialIndex);
              }
              if (child.name !== undefined && child.name !== null) {
                  mesh.SetName (child.name);
              }
              child.updateWorldMatrix (true, true);
              if (child.matrixWorld !== undefined && child.matrixWorld !== null) {
                  const matrix = new OV.Matrix (child.matrixWorld.elements);
                  const transformation = new OV.Transformation (matrix);
                  const determinant = matrix.Determinant ();
                  const mirrorByX = OV.IsNegative (determinant);
                  OV.TransformMesh (mesh, transformation);
                  if (mirrorByX) {
                      OV.FlipMeshTrianglesOrientation (mesh);
                  }
              }
              this.model.AddMesh (mesh);
              child.geometry.dispose ();
          });
  
          onFinish ();
      }
  };
  