Source: loaders.js

/*jshint esversion: 11 */
// @ts-check

/**
 * Access to THREE's loaders within the CS559 framework
 * 
 * @module loaders
 */

import * as T from "../CS559-Three/build/three.module.js";
import { GrObject } from "./GrObject.js";
import { FBXLoader } from "../CS559-Three/examples/jsm/loaders/FBXLoader.js";
import { MTLLoader } from "../CS559-Three/examples/jsm/loaders/MTLLoader.js";
import { OBJLoader } from "../CS559-Three/examples/jsm/loaders/OBJLoader.js";
import { GrCube} from "./SimpleObjects.js";

/**
 * Rescale an object - assumes that the object is a group with 1 mesh in it
 *
 * @param {THREE.Object3D} obj
 */
function normObject(obj, scale = 1.0, center = true, ground = true) {
  // since other bounding box things aren't reliable
  const box = new T.Box3();
  box.setFromObject(obj);
  // easier than vector subtract
  const dx = box.max.x - box.min.x;
  const dy = box.max.y - box.min.y;
  const dz = box.max.z - box.min.z;
  const size = Math.max(dx, dy, dz);
  const s = scale / size;
  obj.scale.set(s, s, s);

  if (center) {
    obj.translateX((-s * (box.max.x + box.min.x)) / 2);
    obj.translateZ((-s * (box.max.z + box.min.z)) / 2);
    if (!ground) {
      // only center Y if not grounding
      obj.translateY((-s * (box.max.y + box.min.y)) / 2);
    }
  }
  if (ground) {
    obj.translateY(-box.min.y * s);
  }
}

/**
 * The loaders have optional callbacks that take a GrObject (not an Object3D!)
 * 
 * @callback LoaderCallback
 * @param {GrObject} object
 */

/**
 * A base class of GrObjects loaded from an OBJ file
 * note: this has to deal with the deferred loading
 *
 * Warning: While ObjLoader2 might be better, ObjLoader is simpler
 */
export class ObjGrObject extends GrObject {
  /**
   *
   * @param {Object} params
   * @property {string} params.obj
   * @property {string} [params.mtl]
   * @property {string} [params.name]
   * @property {Object} [params.mtloptions]
   * @property {Number} [params.norm] - normalize the object (make the largest dimension this value)
   * @property {Number} [params.x] - initial translate for the group
   * @property {Number} [params.y]
   * @property {Number} [params.z]
   * @property {LoaderCallback} [params.callback]
   */
  constructor(params = {}) {
    if (!params.obj) {
      alert("Bad OBJ object - no obj file given!");
      throw "No OBJ given!";
    }

    const name = params.name || `Objfile(${params.obj})`;
    const objholder = new T.Group();

    super(name, objholder);
    const self = this;

    // if there is a material, load it first, and then have that load the OBJ file
    if (params.mtl) {
      const mtloader = new MTLLoader();

      if (params.mtloptions) {
        mtloader.setMaterialOptions(params.mtloptions);
      }

      // note that the callback then calls the Obj Loader
      mtloader.load(params.mtl, function(myMaterialCreator) {
        myMaterialCreator.preload();

        const objLoader = new OBJLoader();
        objLoader.setMaterials(myMaterialCreator);
        
        objLoader.load(params.obj, function(obj) {
          if (params.norm) normObject(obj, params.norm);
          objholder.add(obj);
          if (params.callback) params.callback(self);
        });
      });

    } else {
      // no material file, just an obj
      const objLoader = new OBJLoader();

      objLoader.load(params.obj, function(obj) {
        if (params.norm) normObject(obj, params.norm);
        objholder.add(obj);
        if (params.callback) params.callback(self);
      });

    }
    objholder.translateX(Number(params.x) || 0);
    objholder.translateY(Number(params.y) || 0);
    objholder.translateZ(Number(params.z) || 0);
  }
}

/** 
 * load from an FBX file - this is quite simple 
 * it makes a group so it can stick the FBX object in once
 * it is loaded.
 * 
 * Note: if the loading fails, an error is printed in the console
 * and a red cube is made instead of the loaded object. 
 * (added May 2022)
 * */
export class FbxGrObject extends GrObject {
  /**
   *
   * @param {Object} [params]
   * @property {string} params.fbx
   * @property {Number} [params.norm] - normalize the object (make the largest dimension this value)
   * @property {Number} [params.x] - initial translate for the group
   * @property {Number} [params.y]
   * @property {Number} [params.z]
   * @property {String} [params.name]
   * @property {LoaderCallback} [params.callback]
   */
  constructor(params = {}) {
    const name = params.name || `FBXfile(${params.fbx})`;
    const objholder = new T.Group();
    super(name, objholder);
    const self = this;

    const fbx = new FBXLoader();
    
    fbx.load(params.fbx, 
        function(obj) { /* loaded callback */ 
            if (params.norm) normObject(obj, params.norm);
            objholder.add(obj);
            if (params.callback) params.callback(self);
            },
        undefined, /* progress callback */
        function(error) {  /* error callback */
            console.log(error);
            // put a dummy object in as an error warning
            let tempGr = new GrCube({color:"red"});
            let obj = tempGr.objects[0];
            if (params.norm) normObject(obj, params.norm);
            objholder.add(obj);
            if (params.callback) params.callback(self);
        }
        );

    objholder.translateX(Number(params.x) || 0);
    objholder.translateY(Number(params.y) || 0);
    objholder.translateZ(Number(params.z) || 0);
  }
}