webgl-3d-viewer
Version:
A WebGL ThreeJS STL viewer implementation written in ES6
877 lines (680 loc) • 23 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
var _lodashMathMax = require('lodash/math/max');
var _lodashMathMax2 = _interopRequireDefault(_lodashMathMax);
var _lodashObjectMerge = require('lodash/object/merge');
var _lodashObjectMerge2 = _interopRequireDefault(_lodashObjectMerge);
var _lodashArrayRest = require('lodash/array/rest');
var _lodashArrayRest2 = _interopRequireDefault(_lodashArrayRest);
var _lodashCollectionEach = require('lodash/collection/each');
var _lodashCollectionEach2 = _interopRequireDefault(_lodashCollectionEach);
var _utilsProgressBar = require('./../utils/ProgressBar');
var _utilsProgressBar2 = _interopRequireDefault(_utilsProgressBar);
var _loadersModelLoaderRegistry = require('./../loaders/ModelLoaderRegistry');
var _loadersModelLoaderRegistry2 = _interopRequireDefault(_loadersModelLoaderRegistry);
var _controlsModelControls = require('./../controls/ModelControls');
var _controlsModelControls2 = _interopRequireDefault(_controlsModelControls);
var _controlsDragDropControls = require('./../controls/DragDropControls');
var _controlsDragDropControls2 = _interopRequireDefault(_controlsDragDropControls);
var Viewer = (function () {
function Viewer(domElm) {
var config = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
_classCallCheck(this, Viewer);
// Setup container
this.container = Viewer._setupContainer(domElm);
this.scene = null;
this.camera = null;
this.loader = null;
this.model = null;
this.controls = null;
this.plane = null;
this.axisHelper = null;
this.sphere = null;
this.boundingBox = null;
this.modelWireframe = null;
this.stats = null;
this.group = null;
this.config = {};
this.progressBar = null;
this.loaderPath = null;
this.loaderContent = null;
// Default viewer configuration
this.defaultConfig = {
wireframe: false,
plane: false,
boundingBox: false,
sphere: false,
axis: false,
stats: null,
autoRotate: false,
zoom: false,
dragDrop: false,
material: true,
startupAnimation: false,
fudge: 1.0,
progressBar: {}
};
// Prepare config
this.config = (0, _lodashObjectMerge2['default'])(this.config, this.defaultConfig, config);
// Prepare stats
if (this.config.stats) {
this.stats = this.config.stats;
}
// Init progress
this.progressBar = new _utilsProgressBar2['default'](this.container, this.config.progressBar);
this.progressBar.show();
// Loading state
this.loaded = false;
// Listener
this._resizeListener = null;
}
_createClass(Viewer, [{
key: 'load',
value: function load(path, cb) {
var _this = this;
if (this.loaderPath === path) {
return false;
}
this._unload();
// Setup listener
this._setupListener();
if (!this.progressBar) {
this.progressBar = new _utilsProgressBar2['default'](this.container);
}
var callb = cb || function () {};
var loader = new _loadersModelLoaderRegistry2['default']();
var onLoadCB = function onLoadCB(geometry) {
_this.loaderPath = path;
_this._initializeGeometry(geometry, callb);
if (geometry.registry === true) {
_this.progressBar.hide();
}
};
var onProgressCB = function onProgressCB(item) {
if (item) {
var progress = Math.round(100 * item.loaded / item.total);
if (progress < 0) {
progress = 0;
} else if (progress > 100) {
progress = 100;
}
if (_this.progressBar) {
_this.progressBar.progress = progress;
}
if (progress === 100) {
setTimeout(function () {
if (_this.progressBar) {
_this.progressBar.hide();
}
}, 500);
}
}
};
var onErrorCB = function onErrorCB() {
if (_this.progressBar) {
_this.progressBar.hide();
}
};
if (this.progressBar) {
this.progressBar.show();
}
this.loader = loader.load(path, onLoadCB, onProgressCB, onErrorCB);
}
}, {
key: 'parse',
value: function parse(fileContent, cb) {
if (this.loaded) {
this._unload();
// Setup listener
this._setupListener();
}
this.loaderContent = fileContent;
var callb = cb || function () {};
var loader = new _loadersModelLoaderRegistry2['default']();
var geometry = loader.parse(fileContent);
if (this.progressBar) {
this.progressBar.hide();
}
this._initializeGeometry(geometry, callb);
}
}, {
key: 'enablePlane',
value: function enablePlane() {
var state = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
var cfgState = this.config.plane;
if (cfgState !== state) {
if (this.plane) {
this.group.remove(this.plane);
this.plane = null;
}
if (state === true) {
this._setupPlane();
}
this.config.plane = state;
}
return this.config.plane;
}
}, {
key: 'enableModelWireframe',
value: function enableModelWireframe() {
var state = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
var cfgState = this.config.wireframe;
if (state !== cfgState) {
if (this.modelWireframe) {
this.group.remove(this.modelWireframe);
this.modelWireframe = null;
}
if (state === true) {
this._setupModelWireframe();
}
}
return this.config.wireframe;
}
}, {
key: 'enableAxis',
value: function enableAxis() {
var state = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
var cfgState = this.config.axis;
if (state !== cfgState) {
if (this.axisHelper) {
this.group.remove(this.axisHelper);
this.axisHelper = null;
}
if (state === true) {
this._setupAxisHelper();
}
this.config.state = state;
}
return this.config.axis;
}
}, {
key: 'enableSphere',
value: function enableSphere() {
var state = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
var cfgState = this.config.sphere;
if (state !== cfgState) {
if (this.sphere) {
this.group.remove(this.sphere);
this.sphere = null;
}
if (state === true) {
this._setupSphereGrid();
}
this.config.sphere = state;
}
return this.config.sphere;
}
}, {
key: 'enableBoundingBox',
value: function enableBoundingBox() {
var state = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
var cfgState = this.config.boundingBox;
if (cfgState !== state) {
if (this.boundingBox) {
this.group.remove(this.boundingBox);
this.boundingBox = null;
}
if (state === true) {
this._setupBoundingBox();
}
this.config.boundingBox = state;
}
return this.config.boundingBox;
}
}, {
key: 'enableAutoRotate',
value: function enableAutoRotate() {
var state = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
var cfgState = this.config.autoRotate;
if (this.model && cfgState !== state) {
this.controls.autoRotate = state;
this.config.autoRotate = state;
}
return this.config.autoRotate;
}
}, {
key: 'enableMaterial',
value: function enableMaterial() {
var state = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
if (this.model && state !== this.config.material) {
if (state) {
this.group.add(this.model);
} else {
this.group.remove(this.model);
}
this.config.material = state;
}
return this.config.material;
}
}, {
key: 'setStats',
value: function setStats(stats) {
this.stats = stats;
}
}, {
key: 'setModelColor',
value: function setModelColor(color) {
if (this.model && color) {
this.model.material.color = color;
}
}
}, {
key: 'setModelColorByHexcode',
value: function setModelColorByHexcode(hexcode) {
if (hexcode) {
var colorValue = hexcode.replace('#', '0x');
var color = new THREE.Color(parseInt(colorValue, 16));
this.setModelColor(color);
}
}
}, {
key: 'render',
value: function render() {
// horizontal rotation
if (!this.group) {
return;
}
this.renderer.render(this.scene, this.camera);
}
}, {
key: 'animate',
value: function animate(time) {
var _this2 = this;
if (this.stats) {
this.stats.begin();
}
this.animationId = requestAnimationFrame(function (time) {
_this2.animate(time);
});
if (this.controls) {
this.controls.update(time);
}
this.render();
if (this.stats) {
this.stats.end();
}
}
}, {
key: 'destroy',
value: function destroy() {
this._unload();
this.container.remove();
}
}, {
key: '_setupCamera',
value: function _setupCamera() {
var height = this.container.clientHeight;
var width = this.container.clientWidth;
var camera = new THREE.PerspectiveCamera(45, width / height, 1, 4000);
if (this.model) {
var geometry = this.model.geometry;
geometry.computeBoundingSphere();
var g = this.model.geometry.boundingSphere.radius;
var dist = g * 3;
// fudge factor so you can see the boundaries
camera.position.set(0, 0, dist * this.config.fudge);
}
this.camera = camera;
}
}, {
key: '_setupScene',
value: function _setupScene() {
var scene = new THREE.Scene();
var group = new THREE.Group();
this.scene = scene;
this.group = group;
this.scene.add(this.group);
}
}, {
key: '_setupControls',
value: function _setupControls() {
this._setupCamera();
if (this.model) {
if (this.controls) {
this.controls.destroy();
this.controls = null;
}
var config = this.config;
if (this.model.geometry.registry === true) {
config = (0, _lodashObjectMerge2['default'])({}, this.config, this.model.geometry.controlsConfig);
config.startupAnimation = false;
}
this.controls = new _controlsModelControls2['default'](this.container, this.camera, this.group, config);
}
}
}, {
key: '_setupRenderer',
value: function _setupRenderer() {
var height = this.container.clientHeight;
var width = this.container.clientWidth;
var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
var devicePixelRatio = window.devicePixelRatio || 1;
renderer.setClearColor(0x000000, 0);
renderer.setPixelRatio(devicePixelRatio);
renderer.setSize(width, height);
renderer.gammaInput = true;
renderer.gammaOutput = true;
renderer.shadowMap.enabled = true;
renderer.shadowMap.cullFace = THREE.CullFaceBack;
this.container.appendChild(renderer.domElement);
this.renderer = renderer;
}
}, {
key: '_setupLights',
value: function _setupLights() {
// Ambient
this.scene.add(new THREE.AmbientLight(0xcccccc));
// Light 3
var light = new THREE.SpotLight(0xcccccc);
light.angle = 1.7;
light.position.set(100, 500, 100);
var target = new THREE.Object3D();
target.position.set(0, 0, 0);
light.target = target;
this.scene.add(light);
}
}, {
key: '_setupAxisHelper',
value: function _setupAxisHelper() {
if (this.model) {
if (this.axisHelper) {
this.group.remove(this.axisHelper);
}
// Get max dimention and add 50% overlap for plane
// with a gutter of 10
var geometry = this.model.geometry;
geometry.computeBoundingBox();
geometry.computeBoundingSphere();
var maxDimension = (0, _lodashMathMax2['default'])(this.model.geometry.boundingBox.max);
maxDimension = Math.ceil(~ ~(maxDimension * 1.50) / 10) * 10;
var axisHelper = new THREE.AxisHelper(maxDimension);
// reset center point
axisHelper.position.x = 0;
axisHelper.position.y = 0;
axisHelper.position.z = 0;
this.axisHelper = axisHelper;
this.group.add(this.axisHelper);
this.config.axis = true;
}
}
}, {
key: '_setupPlane',
value: function _setupPlane() {
if (this.model) {
if (this.plane) {
this.group.remove(this.plane);
}
// Getmax dimention and add 10% overlap for plane
// with a gutter of 10
var geometry = this.model.geometry;
geometry.computeBoundingBox();
geometry.computeBoundingSphere();
var maxDimension = (0, _lodashMathMax2['default'])(this.model.geometry.boundingBox.max);
maxDimension = Math.ceil(~ ~(maxDimension * 1.10) / 10) * 10;
var plane = new THREE.GridHelper(maxDimension, 10);
// reset center point
var box = new THREE.Box3().setFromObject(plane);
box.center(plane.position);
plane.position.multiplyScalar(-1);
plane.position.y = geometry.boundingSphere.center.y * -1;
this.plane = plane;
this.group.add(this.plane);
this.config.plane = true;
}
}
}, {
key: '_setupSphereGrid',
value: function _setupSphereGrid() {
if (this.model) {
if (this.sphere) {
this.group.remove(this.sphere);
}
var geometry = this.model.geometry;
geometry.computeBoundingBox();
geometry.computeBoundingSphere();
var _model$geometry$boundingBox$max = this.model.geometry.boundingBox.max;
var x = _model$geometry$boundingBox$max.x;
var y = _model$geometry$boundingBox$max.y;
var z = _model$geometry$boundingBox$max.z;
var d = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2));
var maxDimension = Math.ceil(~ ~(d * 0.60) / 10) * 10;
var geometrySphere = new THREE.SphereGeometry(maxDimension, 10, 10);
var material = new THREE.MeshBasicMaterial({ color: 0x4d635d, wireframe: true });
var sphere = new THREE.Mesh(geometrySphere, material);
// reset center point
var box = new THREE.Box3().setFromObject(sphere);
box.center(sphere.position);
sphere.position.multiplyScalar(-1);
geometrySphere.computeBoundingBox();
geometrySphere.computeBoundingSphere();
sphere.position.x = geometrySphere.boundingSphere.center.x;
sphere.position.y = geometrySphere.boundingSphere.center.y;
sphere.position.z = geometrySphere.boundingSphere.center.z;
this.sphere = sphere;
this.group.add(this.sphere);
this.config.sphere = true;
}
}
}, {
key: '_setupBoundingBox',
value: function _setupBoundingBox() {
if (this.model) {
if (this.boundingBox) {
this.group.remove(this.boundingBox);
}
var wireframe = new THREE.WireframeGeometry(this.model.geometry);
var line = new THREE.LineSegments(wireframe);
line.material.depthTest = false;
line.material.opacity = 0.25;
line.material.transparent = true;
// reset center point
var box = new THREE.Box3().setFromObject(line);
box.center(line.position);
line.position.multiplyScalar(-1);
this.boundingBox = new THREE.BoxHelper(line);
this.group.add(this.boundingBox);
this.config.boundingBox = true;
}
}
}, {
key: '_unload',
value: function _unload() {
var _this3 = this;
cancelAnimationFrame(this.animationId);
if (this.scene !== null) {
var objsToRemoveFromGroup = (0, _lodashArrayRest2['default'])(this.group.children, 1);
(0, _lodashCollectionEach2['default'])(objsToRemoveFromGroup, function (object) {
_this3.group.remove(object);
});
var objsToRemoveFromScene = (0, _lodashArrayRest2['default'])(this.scene.children, 1);
(0, _lodashCollectionEach2['default'])(objsToRemoveFromScene, function (object) {
_this3.scene.remove(object);
});
}
this.scene = null;
this.group = null;
this.camera = null;
this.loader = null;
this.plane = null;
this.axisHelper = null;
this.renderer = null;
this.sphere = null;
this.animationId = null;
this.boundingBox = null;
this.modelWireframe = null;
this.loaderPath = null;
this.loaderContent = null;
// Remove Model
if (this.model) {
this.model.geometry.controlsConfig = this.controls.controlsConfig;
this.model = null;
}
// Remove progressBar
if (this.progressBar) {
this.progressBar.destroy();
this.progressBar = null;
}
// Remove controls
if (this.controls) {
this.controls.destroy();
this.controls = null;
}
// DragDrop controls
if (this.ddControls) {
this.ddControls.destroy();
this.ddControls = null;
}
if (this.container !== null) {
// Clear container
var elem = this.container;
while (elem.lastChild) {
elem.removeChild(elem.lastChild);
}
}
this.loaded = false;
this.loaderPath = null;
// Remove listener
window.removeEventListener('resize', this._resizeListener, false);
this._resizeListener = null;
while (this.container.firstChild) {
this.container.removeChild(this.container.firstChild);
}
}
}, {
key: '_setupModelWireframe',
value: function _setupModelWireframe() {
if (this.model) {
if (this.modelWireframe) {
this.group.remove(this.modelWireframe);
}
var material = new THREE.MeshPhongMaterial({ color: 0xffffff, specular: 0x111111, shininess: 20, wireframe: true });
var mesh = this.model.clone();
mesh.material = material;
this.modelWireframe = mesh;
this.group.add(mesh);
this.config.wireframe = true;
}
}
}, {
key: '_setupListener',
value: function _setupListener() {
var _this4 = this;
this._resizeListener = function (evt) {
_this4._onWindowResize(evt);
};
window.addEventListener('resize', this._resizeListener, false);
if (this.config.dragDrop === true) {
this.ddControls = new _controlsDragDropControls2['default'](this.container, function (result) {
_this4.parse(result);
});
}
}
}, {
key: '_restoreConfig',
value: function _restoreConfig() {
var _config = this.config;
var wireframe = _config.wireframe;
var plane = _config.plane;
var boundingBox = _config.boundingBox;
var sphere = _config.sphere;
var axis = _config.axis;
if (wireframe) {
this._setupModelWireframe();
}
if (plane) {
this._setupPlane();
}
if (boundingBox) {
this._setupBoundingBox();
}
if (sphere) {
this._setupSphereGrid();
}
if (axis) {
this._setupAxisHelper();
}
}
}, {
key: '_initializeGeometry',
value: function _initializeGeometry(geometry, cb) {
var _this5 = this;
var callb = cb || function () {};
this._setupScene();
this._setupRenderer();
this._setupLights();
var n = geometry;
n.computeBoundingSphere();
n.computeBoundingBox();
if (geometry.registry === false) {
n.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI / 2));
}
var material = new THREE.MeshPhongMaterial({ color: 0xb3b3b3, specular: 0x111111, shininess: 20 });
var mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.material = material;
// reset center point
var box = new THREE.Box3().setFromObject(mesh);
box.center(mesh.position);
mesh.position.multiplyScalar(-1);
this.model = mesh;
if (this.config.material) {
this.group.add(this.model);
}
this.scene.updateMatrixWorld();
this._setupControls();
this._restoreConfig();
requestAnimationFrame(function (time) {
_this5.animate(time);
_this5.loaded = true;
callb();
});
}
}, {
key: '_onWindowResize',
value: function _onWindowResize() {
if (this.container) {
var height = this.container.clientHeight;
var width = this.container.clientWidth;
if (this.camera) {
this.camera.aspect = width / height;
this.camera.updateProjectionMatrix();
}
if (this.renderer) {
this.renderer.setSize(width, height);
}
}
}
}, {
key: 'zoom',
set: function set(val) {
this.controls.zoom = val;
this.config.zoom = val;
},
get: function get() {
return this.config.zoom;
}
}], [{
key: '_setupContainer',
value: function _setupContainer(domElm) {
var vElm = document.createElement('div');
vElm.style.height = '100%';
vElm.style.width = '100%';
vElm.style.position = 'absolute';
vElm.style.left = 0;
vElm.style.right = 0;
vElm.style.bottom = 0;
vElm.style.top = 0;
domElm.appendChild(vElm);
return vElm;
}
}]);
return Viewer;
})();
exports['default'] = Viewer;
module.exports = exports['default'];