src/plugins/OBJLoaderPlugin/OBJLoaderPlugin.js
import {Plugin} from "../../viewer/Plugin.js";
import {Node} from "../../viewer/scene/nodes/Node.js";
import {utils} from "../../viewer/scene/utils.js";
import {OBJSceneGraphLoader} from "./OBJSceneGraphLoader.js";
/**
* {@link Viewer} plugin that loads models from [OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file) files.
*
* * Creates an {@link Entity} representing each model it loads, which will have {@link Entity#isModel} set ````true```` and will be registered by {@link Entity#id} in {@link Scene#models}.
* * Creates an {@link Entity} for each object within the model, which will have {@link Entity#isObject} set ````true```` and will be registered by {@link Entity#id} in {@link Scene#objects}.
* * When loading, can set the World-space position, scale and rotation of each model within World space, along with initial properties for all the model's {@link Entity}s.
*
* ## Metadata
*
* OBJLoaderPlugin can also load an accompanying JSON metadata file with each model, which creates a {@link MetaModel} corresponding
* to the model {@link Entity} and a {@link MetaObject} corresponding to each object {@link Entity}.
*
* Each {@link MetaObject} has a {@link MetaObject#type}, which indicates the classification of its corresponding {@link Entity}. When loading
* metadata, we can also provide GLTFModelLoaderPlugin with a custom lookup table of initial values to set on the properties of each type of {@link Entity}. By default, OBJLoaderPlugin
* uses its own map of standard default colors, visibilities and opacities for IFC element types.
*
* ## Usage
*
* [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/cad/#OBJ_SportsCar_ExplodeModel)]
*
* ````javascript
* import {Viewer, OBJLoaderPlugin} from "xeokit-sdk.es.js";
*
* // Create a xeokit Viewer and arrange the camera
* const viewer = new Viewer({
* canvasId: "myCanvas",
* transparent: true
* });
*
* viewer.camera.orbitPitch(20);
*
* // Add an OBJLoaderPlugin to the Viewer
* const objLoader = new OBJLoaderPlugin(viewer);
*
* // Load an OBJ model
* var model = objLoader.load({ // Model is an Entity
* id: "myModel",
* src: "./models/obj/sportsCar/sportsCar.obj",
* edges: true
* });
*
* // When the model has loaded, fit it to view
* model.on("loaded", () => {
* viewer.cameraFlight.flyTo(model);
* })
*
* // Find the model Entity by ID
* model = viewer.scene.models["myModel"];
*
* // Update properties of the model Entity
* model.highlight = [1,0,0];
*
* // Destroy the model
* model.destroy();
* ````
* @class OBJLoaderPlugin
*/
class OBJLoaderPlugin extends Plugin {
/**
* @constructor
*
* @param {Viewer} viewer The Viewer.
* @param {Object} cfg Plugin configuration.
* @param {String} [cfg.id="OBJLoader"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}.
*/
constructor(viewer, cfg) {
super("OBJLoader", viewer, cfg);
/**
* @private
*/
this._sceneGraphLoader = new OBJSceneGraphLoader();
}
/**
* Loads an OBJ model from a file into this OBJLoader's {@link Viewer}.
*
* @param {*} params Loading parameters.
* @param {String} params.id ID to assign to the model's root {@link Entity}, unique among all components in the Viewer's {@link Scene}.
* @param {String} params.src Path to an OBJ file.
* @param {String} [params.metaModelSrc] Path to an optional metadata file.
* @param {Number[]} [params.position=[0,0,0]] The model World-space 3D position.
* @param {Number[]} [params.scale=[1,1,1]] The model's World-space scale.
* @param {Number[]} [params.rotation=[0,0,0]] The model's World-space rotation, as Euler angles given in degrees, for each of the X, Y and Z axis.
* @param {Number[]} [params.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]] The model's world transform matrix. Overrides the position, scale and rotation parameters.
* @param {Number} [params.edgeThreshold=20] When xraying, highlighting, selecting or edging, this is the threshold angle between normals of adjacent triangles, below which their shared wireframe edge is not drawn.
* @returns {Entity} Entity representing the model, which will have {@link Entity#isModel} set ````true```` and will be registered by {@link Entity#id} in {@link Scene#models}
*/
load(params = {}) {
if (params.id && this.viewer.scene.components[params.id]) {
this.error("Component with this ID already exists in viewer: " + params.id + " - will autogenerate this ID");
delete params.id;
}
var modelNode = new Node(this.viewer.scene, utils.apply(params, {
isModel: true
}));
const modelId = modelNode.id; // In case ID was auto-generated
const src = params.src;
if (!src) {
this.error("load() param expected: src");
return modelNode;
}
if (params.metaModelSrc) {
const metaModelSrc = params.metaModelSrc;
utils.loadJSON(metaModelSrc,
(modelMetadata) => {
this.viewer.metaScene.createMetaModel(modelId, modelMetadata);
this._sceneGraphLoader.load(modelNode, src, params);
},
(errMsg) => {
this.error(`load(): Failed to load model modelMetadata for model '${modelId} from '${metaModelSrc}' - ${errMsg}`);
});
} else {
this._sceneGraphLoader.load(modelNode, src, params);
}
modelNode.once("destroyed", () => {
this.viewer.metaScene.destroyMetaModel(modelId);
});
return modelNode;
}
/**
* Destroys this OBJLoaderPlugin.
*/
destroy() {
super.destroy();
}
}
export {OBJLoaderPlugin}