UNPKG

awv3

Version:
526 lines (439 loc) 17.3 kB
import _extends from "@babel/runtime/helpers/extends"; import _createClass from "@babel/runtime/helpers/createClass"; import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose"; import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized"; var _class, _temp; import * as THREE from 'three'; import v4 from 'uuid-v4'; import { queryDom, setPrefixedValue } from './helpers'; import { lastCreated } from './canvas'; import DomEvents from './dom'; import Interaction from './interaction'; import Stats from '../misc/stats'; import Orbit from '../controls/orbit'; import CombinedCamera from '../three/combinedcamera'; import Events from '../core/events'; import { resizeListen, resizeUnlisten } from 'dom-resize'; /** 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 = (_temp = _class = /*#__PURE__*/ function (_Events) { _inheritsLoose(View, _Events); /** Construct a new View @param {Object} [canvas=lastCreated] - The parent canvas, if none defined the lastCreated will be used @param {Object} [options={}] - this.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(canvas, options) { var _this; if (canvas === void 0) { canvas = lastCreated; } if (options === void 0) { options = {}; } _this = _Events.call(this) || this; _this.id = v4(); _this.canvas = canvas; _this.renderer = canvas.renderer; _this.resolution = _this.renderer.resolution; _this.invalidateFrames = 2; _this.force = 0; _this.dirty = true; _this.bounds = { box: new THREE.Box3(), sphere: new THREE.Sphere() }; _this.options = _extends({ dom: canvas.dom, renderAlways: false, visible: true, callback: undefined, callbackRender: undefined, callbackAfter: undefined, background: _this.renderer.clearColor, opacity: 0, defaultCursor: 'auto' }, options); _this.dom = queryDom(_this.options.dom); _this.renderAlways = _this.options.renderAlways; _this.visible = _this.options.visible; _this.callbackBefore = _this.options.callback; _this.callbackRender = _this.options.callbackRender; _this.callbackAfter = _this.options.callbackAfter; _this.background = _this.options.background; _this.opacity = _this.options.opacity; _this.defaultCursor = _this.options.defaultCursor; // Make sure the view hides overflow and is not selectable _this.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 (_this.options.background !== _this.renderer.clearColor) _this.dom.style.border = "2px solid #" + _this.background.getHexString(); _this.input = new DomEvents(_assertThisInitialized(_this), { wheel: function wheel(state) { _this.controls.onMouseWheel(state); _this.hud.forEach(function (hud) { return hud.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.forEach(function (hud) { return hud.onMouseMove(state); }); }, mousedown: function mousedown(state) { _this.interaction.onMouseDown(state); _this.controls.onMouseDown(state); _this.hud.forEach(function (hud) { return hud.onMouseDown(state); }); }, mouseup: function mouseup(state) { _this.interaction.onMouseUp(state); _this.controls.onMouseUp(state); _this.hud.forEach(function (hud) { return hud.onMouseUp(state); }); }, touchstart: function touchstart(state) { _this.interaction.onMouseDown(state); _this.controls.onTouchStart(state); _this.hud.forEach(function (hud) { return hud.onTouchStart(state); }); }, touchmove: function touchmove(state) { _this.interaction.onMouseMove(state); _this.controls.onTouchMove(state); _this.hud.forEach(function (hud) { return hud.onTouchMove(state); }); }, touchend: function touchend(state) { _this.interaction.onMouseUp(state); _this.controls.onTouchEnd(state); _this.hud.forEach(function (hud) { return hud.onTouchEnd(state); }); } }); _this.scene = new THREE.Scene(); _this.scene.canvas = canvas; _this.scene.view = _assertThisInitialized(_this); _this.ambient = new THREE.AmbientLight(_this.options.ambientColor ? _this.options.ambientColor : 0xffffff); _this.ambient.intensity = typeof _this.options.ambientIntensity !== 'undefined' ? _this.options.ambientIntensity : 1.0; _this.ambient.keep = true; _this.ambient.view = _assertThisInitialized(_this); _this.scene.add(_this.ambient); _this.camera = new CombinedCamera(_assertThisInitialized(_this), _this.options); // his.camera = Being set by the above _this.controls = new Orbit(_assertThisInitialized(_this), _extends({ maxPolarAngle: Math.PI, minDistance: 1, maxDistance: 20000 }, _this.options)); _this.hud = []; _this.interaction = new Interaction(_assertThisInitialized(_this)); _this.canvas.views.push(_assertThisInitialized(_this)); _this.renderer.resize(); resizeListen(_this.dom, function () { return _this.invalidate(30); }); if (_this.options.stats) { _this.showStats = true; } return _this; } var _proto = View.prototype; _proto.destroy = function destroy() { if (!this.__destroyed) { this.__destroyed = true; this.input.detach(); this.input.removeListeners(); this.input.removeInspectors(); this.scene.destroy({ keep: false }); this.render = function () {}; this.clear = function () {}; resizeUnlisten(this.dom); this.dom = undefined; this.view = undefined; this.canvas = undefined; this.renderer = undefined; this.bound = undefined; this.input = undefined; this.scene = undefined; this.hud.forEach(function (hud) { return hud.destroy(); }); this.hud = []; } }; _proto.addHud = function addHud(obj) { this.hud = this.hud.concat(obj); }; _proto.removeHud = function removeHud(obj) { this.hud = this.hud.filter(function (hud) { return hud !== obj; }); }; /** 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'); */ _proto.setCursor = function setCursor(style, fallback) { if (style === void 0) { style = this.defaultCursor; } if (style != this.cursor) { 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 */ _proto.getPoint2 = function getPoint2(point3, camera) { if (camera === void 0) { camera = this.camera.display; } 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) */ _proto.getPoint3 = function getPoint3(point2, camera, zValue) { if (camera === void 0) { camera = this.camera.display; } if (zValue === void 0) { zValue = 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 _proto.getViewLine3 = function getViewLine3(point2, camera) { if (camera === void 0) { camera = this.camera.display; } 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(camera); far.unproject(camera); return new THREE.Line3(near, far); }; /** * Calculate the scale factor that will make the object at the point3 position be radiusPx pixels wide. * Example use: object.scale.set(calculateScaleFactor(object.getWorldPosition(), radiusPx)); * @param point3 Vector3 position of the object * @param radiusPx Number desired radius of the object in pixels * @return Number scale factor for the object */ _proto.calculateScaleFactor = function calculateScaleFactor(point3, radiusPx, camera) { if (camera === void 0) { camera = this.camera.display; } var point2 = this.getPoint2(point3.clone()); var scale = 0; for (var i = 0; i < 2; ++i) { var point2off = point2.clone().setComponent(i, point2.getComponent(i) + radiusPx); var point3off = this.getPoint3(point2off, camera, point2off.z); scale = Math.max(scale, point3.distanceTo(point3off)); } return scale; }; _proto.clear = 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.forEach(function (hud) { return hud.update(time); }); // Update interaction this.interaction.update(); if (this.dirty || this.renderAlways || this.controls.inMotion || this.hudInMotion) { // Clear canvas only if dirty, it isn't necessary for simple interaction if (this.dirty) { this.renderer.dirty = true; if (this.renderer.options.scissorTest) { this.renderer.gl.setViewport(this.old[0], this.old[1], this.old[2], this.old[3]); this.renderer.gl.setScissor(this.old[0] * this.resolution, this.old[1] * this.resolution, this.old[2] * this.resolution, this.old[3] * this.resolution); } this.renderer.gl.setClearColor(this.renderer.clearColor, 0); this.renderer.gl.clear(); } } } // Remove frames if (this.invalidateFrames > 0) this.invalidateFrames--; return this.dirty; }; _proto.render = function render(time) { if (this.force > 0 || this.dirty || this.renderAlways || this.controls.inMotion || this.stats || this.hudInMotion) { this.callbackBefore && this.callbackBefore(); if (this.renderer.options.scissorTest) { this.renderer.gl.setViewport(this.new[0], this.new[1], this.new[2], this.new[3]); this.renderer.gl.setScissor(this.new[0] * this.resolution, this.new[1] * this.resolution, this.new[2] * this.resolution, this.new[3] * this.resolution); } var camera = this.camera; if (!this.callbackRender) { this.renderer.gl.setClearColor(this.background, this.opacity); this.renderer.gl.clear(); this.renderer.gl.render(this.scene, this.camera.display); this.renderer.gl.clearDepth(); this.hud.forEach(function (hud) { return hud.render(); }); } else this.callbackRender(); this.callbackAfter && this.callbackAfter(); this.stats && this.stats.update(); if (this.force > 0) this.force--; } }; _proto.invalidate = function invalidate(frames) { if (frames === void 0) { frames = 1; } this.force += frames; if (this.force > 60) this.force = 60; this.invalidateFrames += frames; if (this.invalidateFrames > 60) this.invalidateFrames = 60; return this; }; _proto.measure = 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; this.emit(View.Events.SizeChanged, offset); } // Position changed, invalidate! if (force || offset.top != this.top || offset.left != this.left) { this.invalidate(10); dirty = true; this.emit(View.Events.PositionChanged, offset); } // 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; }; _proto.calibrate = function calibrate(width, height) { this.aspect = width / height; this.camera.aspect = this.aspect; this.camera.updateProjectionMatrix(); this.camera.radius = (width + height) / 4; this.hud.forEach(function (hud) { return hud.calibrate(width, height); }); }; _proto.updateBounds = function updateBounds(box) { if (box === void 0) { box = undefined; } this.scene.updateBounds(box); this.bounds.box = this.scene.bounds.box; this.bounds.sphere = this.scene.bounds.sphere; return this; }; _createClass(View, [{ key: "hudInMotion", get: function get() { return this.hud.length > 0 && this.hud.reduce(function (acc, val) { return { inMotion: acc.inMotion && val.inMotion }; }).inMotion; } }, { key: "showStats", set: function set(value) { if (this.stats) { this.stats.remove(); this.stats = undefined; } if (value) { this.stats = new Stats(); this.dom.appendChild(this.stats.dom); } } }]); return View; }(Events), Object.defineProperty(_class, "Events", { configurable: true, enumerable: true, writable: true, value: { SizeChanged: 'SizeChanged', PositionChanged: 'PositionChanged' } }), _temp); export { View as default };