UNPKG

awv3

Version:
504 lines (421 loc) 20.9 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _three = require('three'); var THREE = _interopRequireWildcard(_three); var _uuidV = require('uuid-v4'); var _uuidV2 = _interopRequireDefault(_uuidV); var _error = require('./error'); var Error = _interopRequireWildcard(_error); var _helpers = require('./helpers'); var _canvas = require('./canvas'); var _dom = require('./dom'); var _dom2 = _interopRequireDefault(_dom); var _interaction = require('./interaction'); var _interaction2 = _interopRequireDefault(_interaction); var _stats = require('../misc/stats'); var _stats2 = _interopRequireDefault(_stats); var _orbit = require('../controls/orbit'); var _orbit2 = _interopRequireDefault(_orbit); var _perspective = require('../three/perspective'); var _perspective2 = _interopRequireDefault(_perspective); var _elementResizeEvent = require('element-resize-event'); var _elementResizeEvent2 = _interopRequireDefault(_elementResizeEvent); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** A view represents a portion of canvas on which webGL can draw. The view is defined and tracked by a dom node on which the drawing take place. */ var View = function () { /** Construct a new View @param {Object} [canvas=lastCreated] - The parent canvas, if none defined the lastCreated will be used @param {Object} [options={}] - Options to initialize the View with @param {HTMLElement} [options.dom=canvas.dom] - The HTML element on which the view will draw @param {Boolean} [options.renderAlways=false] - Set to true the view will render 60fps, set to false it will render on changes (default, recommended) @param {Boolean} [options.visible=true] - Set to true the view will render @param {Function} [options.callbackBefore=undefined] - Callback before the render pass @param {Function} [options.callbackRender=undefined] - Callback to custom-render the scene @param {Function} [options.callbackAfter=undefined] - Callback after the render pass @param {Number} [options.background=canvas.renderer.clearColor] - Background color @param {Number} [options.opacity=0.0] - Background opacity @param {Number} [options.ambientColor=0xffffff] - Ambient color @param {Number} [options.ambientIntensity=1.0] - Ambient intensity @example import View from 'view'; // Create a view, defaults into the same dom as the canvas const view = new View(canvas, { dom: '#view', ambient: 0x909090 }); // Add model to the view's scene view.scene.add(model); @returns {Object} The constructed View */ function View() { var _this = this; var canvas = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _canvas.lastCreated; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; (0, _classCallCheck3.default)(this, View); this.id = (0, _uuidV2.default)(); this.canvas = canvas; this.renderer = canvas.renderer; this.invalidateFrames = 2; this.force = 0; this.dirty = true; this.bounds = { box: new THREE.Box3(), sphere: new THREE.Sphere() }; options = (0, _extends3.default)({ dom: canvas.dom, renderAlways: false, visible: true, callback: undefined, callbackRender: undefined, callbackAfter: undefined, background: this.renderer.clearColor, opacity: 0, defaultCursor: 'auto' }, options); this.dom = (0, _helpers.queryDom)(options.dom); this.renderAlways = options.renderAlways; this.visible = options.visible; this.callbackBefore = options.callback; this.callbackRender = options.callbackRender; this.callbackAfter = options.callbackAfter; this.background = options.background; this.opacity = options.opacity; this.defaultCursor = options.defaultCursor; // Make sure the view hides overflow and is not selectable options.dom.style.cssText += "-webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; user-select: none; overlay: hidden"; // A border will hide gaps caused by imprecise layout returns if (options.background !== this.renderer.clearColor) this.dom.style.border = '2px solid #' + this.background.getHexString(); this.input = new _dom2.default(this, { wheel: function wheel(state) { _this.controls.onMouseWheel(state); _this.hud && _this.controlsHud && _this.controlsHud !== _this.controls && _this.controlsHud.onMouseWheel(state); }, mouseout: function mouseout(state) { _this.interaction.onMouseOut(state); }, mousemove: function mousemove(state) { _this.interaction.onMouseMove(state); _this.input.mouse.down && _this.controls.onMouseMove(state); _this.input.mouse.down && _this.hud && _this.controlsHud && _this.controlsHud !== _this.controls && _this.controlsHud.onMouseMove(state); }, mousedown: function mousedown(state) { _this.interaction.onMouseDown(state); _this.controls.onMouseDown(state); _this.hud && _this.controlsHud && _this.controlsHud !== _this.controls && _this.controlsHud.onMouseDown(state); }, mouseup: function mouseup(state) { _this.interaction.onMouseUp(state); _this.controls.onMouseUp(state); _this.hud && _this.controlsHud && _this.controlsHud !== _this.controls && _this.controlsHud.onMouseUp(state); }, touchstart: function touchstart(state) { _this.interaction.onMouseDown(state); _this.controls.onTouchStart(state); _this.hud && _this.controlsHud && _this.controlsHud !== _this.controls && _this.controlsHud.onTouchStart(state); }, touchmove: function touchmove(state) { _this.interaction.onMouseMove(state); _this.controls.onTouchMove(state); _this.hud && _this.controlsHud && _this.controlsHud !== _this.controls && _this.controlsHud.onTouchMove(state); }, touchend: function touchend(state) { _this.interaction.onMouseUp(state); _this.controls.onTouchEnd(state); _this.hud && _this.controlsHud && _this.controlsHud !== _this.controls && _this.controlsHud.onTouchEnd(state); } }); this.scene = new THREE.Scene(); this.scene.canvas = canvas; this.scene.view = this; this.ambient = new THREE.AmbientLight(options.ambientColor ? options.ambientColor : 0xffffff); this.ambient.intensity = typeof options.ambientIntensity !== 'undefined' ? options.ambientIntensity : 1.0; this.ambient.keep = true; this.ambient.view = this; this.scene.add(this.ambient); this.camera = new _perspective2.default(options); this.controls = new _orbit2.default(this, (0, _extends3.default)({ maxPolarAngle: Math.PI, minDistance: 1, maxDistance: 20000 }, options)); this.hud = false; this.sceneHud = new THREE.Scene(); this.sceneHud.canvas = canvas; this.sceneHud.view = this; this.cameraHud = this.camera; this.controlsHud = this.controls; this.ambientHud = new THREE.AmbientLight(options.ambientColor ? options.ambientColor : 0xffffff); this.ambientHud.intensity = typeof options.ambientIntensity !== 'undefined' ? options.ambientIntensity : 1.0; this.ambientHud.keep = true; this.ambientHud.view = this; this.sceneHud.add(this.ambientHud); this.interaction = new _interaction2.default(this); this.updateScopes(); this.canvas.views.push(this); this.renderer.resize(); (0, _elementResizeEvent2.default)(this.dom, function () { return _this.invalidate(30); }); } (0, _createClass3.default)(View, [{ key: 'destroy', value: function destroy() { if (!this.__destroyed) { this.__destroyed = true; this.input.detach(); this.input.removeListeners(); this.input.removeInspectors(); this.scene.destroy({ keep: false }); this.dom.querySelector('object').remove(); this.render = function () {}; this.clear = function () {}; this.view = undefined; this.canvas = undefined; this.renderer = undefined; this.bound = undefined; this.input = undefined; this.scene = undefined; this.sceneHud = undefined; this.controls = undefined; this.controlsHud = undefined; this.interaction = undefined; this.camera = undefined; this.cameraHud = undefined; this.ambient = undefined; this.ambientHud = undefined; this.dom = undefined; } } }, { key: 'setCursor', /** Sets the cursor style. Applies when the mouse is inside the view's rect. @param {String} style='auto' - CSS cursor style @param {String} [fallback] - Fallback style, should the target style not be available @example // 'grab' is available in WebKit and Blink only view.setCursor('grab', 'move'); */ value: function setCursor() { var style = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.defaultCursor; var fallback = arguments[1]; if (style != this.cursor) { (0, _helpers.setPrefixedValue)(this.dom, "cursor", style, fallback); this.cursor = style; } return this; } /** Projects 2-dimensional coordinate from a 3-dimensional point within the view's scene. @param {THREE.Vector3} point3 - Input point @example // Grab x and y off THREE's projected Vector2 let {x, y} = view.getPoint2D(new THREE.Vector3(10, 20, 100)); @returns {THREE.Vector2} The projected point */ }, { key: 'getPoint2', value: function getPoint2(point3) { var camera = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.camera; var widthHalf = this.width / 2, heightHalf = this.height / 2; var vector = point3.project(camera); vector.x = vector.x * widthHalf + widthHalf; vector.y = -(vector.y * heightHalf) + heightHalf; return vector; } /** * Projects a 2D point into 3D space. Z-Value for the 2D-Point can be passed within bounds of 0 to 1. * Note: maximal depth is used by default (z = 1), so the point is on the "far" frustum (with huge coordinates) */ }, { key: 'getPoint3', value: function getPoint3(point2) { var camera = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.camera; var zValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; var vector = new THREE.Vector3(point2.x / this.width * 2 - 1, -(point2.y / this.height) * 2 + 1, zValue); vector.unproject(camera); return vector; } ///returns a line of all the points corresponding to given 2D view coords }, { key: 'getViewLine3', value: function getViewLine3(point2) { var near = new THREE.Vector3(point2.x / this.width * 2 - 1, -(point2.y / this.height) * 2 + 1, 0); var far = new THREE.Vector3(point2.x / this.width * 2 - 1, -(point2.y / this.height) * 2 + 1, 1); near.unproject(this.camera); far.unproject(this.camera); return new THREE.Line3(near, far); } }, { key: 'updateOverlays', value: function updateOverlays() {} }, { key: 'updateScopes', value: function updateScopes() { var parent = this.dom; while (!!parent && parent != this.canvas.dom) { var scope = this.canvas.scopes.get(parent); if (scope) scope.push(this);else this.canvas.scopes.set(parent, [this]); parent = parent.parentNode; } } }, { key: 'clear', value: function clear(time) { // Measure and check if dirty (size & position changed) this.dirty = this.invalidateFrames > 0 && this.measure(); if (this.visible) { // Call event scheduler this.input.debounce && this.input.update(); // Update controls this.controls.update(time); this.hud && this.controlsHud && this.controlsHud != this.controls && this.controlsHud.update(time); // Update interaction this.interaction.update(); if (this.dirty || this.renderAlways || this.controls.inMotion || this.controlsHud && this.controlsHud.inMotion) { // Clear canvas only if dirty, it isn't necessary for simple interaction if (this.dirty) { this.renderer.dirty = true; this.renderer.gl.setViewport(this.old[0], this.old[1], this.old[2], this.old[3]); this.renderer.gl.setScissor(this.old[0], this.old[1], this.old[2], this.old[3]); this.renderer.gl.setClearColor(this.renderer.clearColor, 0); this.renderer.gl.clear(); } } } // Remove frames if (this.invalidateFrames > 0) this.invalidateFrames--; return this.dirty; } }, { key: 'render', value: function render(time) { if (this.force > 0 || this.dirty || this.renderAlways || this.controls.inMotion || this.controlsHud && this.controlsHud.inMotion || this.stats) { this.callbackBefore && this.callbackBefore(); this.renderer.gl.setViewport(this.new[0], this.new[1], this.new[2], this.new[3]); this.renderer.gl.setScissor(this.new[0], this.new[1], this.new[2], this.new[3]); this.renderer.gl.setClearColor(this.background, this.opacity); this.renderer.gl.clear(); if (!this.callbackRender) { this.renderer.gl.render(this.scene, this.camera); this.renderer.gl.clearDepth(); this.hud && this.renderer.gl.render(this.sceneHud, this.cameraHud); } else this.callbackRender(); this.callbackAfter && this.callbackAfter(); this.stats && this.stats.update(); if (this.force > 0) this.force--; } } }, { key: 'invalidate', value: function invalidate() { var frames = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; this.force += frames; if (this.force > 60) this.force = 60; this.invalidateFrames += frames; if (this.invalidateFrames > 60) this.invalidateFrames = 60; return this; } }, { key: 'measure', value: function measure(force) { var dirty = false; var bounds = this.dom.getBoundingClientRect(); var offset = { top: bounds.top, left: bounds.left, width: bounds.width, height: bounds.height }; offset.top -= this.renderer.offset.top; offset.left -= this.renderer.offset.left; // Size changed, calibrate & invalidate if (force || offset.width != this.width || offset.height != this.height) { this.calibrate(offset.width, offset.height); this.invalidate(10); dirty = true; } // Position changed, invalidate! if (force || offset.top != this.top || offset.left != this.left) { this.invalidate(10); dirty = true; } // Save old data this.old = [this.left * this.renderer.resolution, this.bottom * this.renderer.resolution, this.width * this.renderer.resolution, this.height * this.renderer.resolution]; // Apply new this.width = offset.width; this.height = offset.height; this.top = offset.top; this.left = offset.left; this.bottom = this.renderer.offset.height - offset.height - offset.top; // Premultiply new this.new = [this.left * this.renderer.resolution, this.bottom * this.renderer.resolution, this.width * this.renderer.resolution, this.height * this.renderer.resolution]; // Check visibility var visible = !(this.height <= 0 || this.width <= 0 || this.top >= this.renderer.offset.height || this.left >= this.renderer.offset.width || this.top + this.height <= 0 || this.left + this.width <= 0); if (this.visible != visible) { this.visible = visible; this.visible && this.invalidate(10); dirty = true; } return dirty; } }, { key: 'calibrate', value: function calibrate(width, height) { this.aspect = width / height; if (this.camera instanceof THREE.PerspectiveCamera) { this.camera.aspect = this.aspect; this.camera.updateProjectionMatrix(); this.camera.radius = (width + height) / 4; if (this.hud && this.cameraHud != this.camera) { this.cameraHud.aspect = this.aspect; this.cameraHud.updateProjectionMatrix(); this.cameraHud.radius = (width + height) / 4; } } else if (this.camera instanceof THREE.OrthographicCamera) { width = this.camera.size * this.aspect; height = this.camera.size; this.camera.left = width / -2; this.camera.right = width / 2; this.camera.top = height / 2; this.camera.bottom = height / -2; this.camera.updateProjectionMatrix(); if (this.hud && this.cameraHud != this.camera) { this.cameraHud.left = width / -2; this.cameraHud.right = width / 2; this.cameraHud.top = height / 2; this.cameraHud.bottom = height / -2; this.cameraHud.updateProjectionMatrix(); } } } }, { key: 'updateBounds', value: function updateBounds() { var box = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; this.scene.updateBounds(box); this.bounds.box = this.scene.bounds.box; this.bounds.sphere = this.scene.bounds.sphere; return this; } }, { key: 'showStats', set: function set(value) { if (this.stats) { this.stats.remove(); this.stats = undefined; } if (value) { this.stats = new _stats2.default(); this.dom.appendChild(this.stats.dom); } } }]); return View; }(); exports.default = View;