UNPKG

awv3

Version:
165 lines (139 loc) 6.11 kB
import * as THREE from 'three'; import * as Helpers from './helpers'; import Tween from '../animation/tween'; export default class Renderer { constructor(canvas = Error.log('Factory was initialized without canvas'), options = {}) { options = { resolution: parseFloat(Helpers.url('resolution')) || (window.devicePixelRatio ? window.devicePixelRatio : 1), pixelated: Helpers.url('pixelated') || false, clearColor: new THREE.Color(0), place: 'first', startImmediately: true, precision: 'highp', premultipliedAlpha: true, stencil: true, depth: true, preserveDrawingBuffer: false, alpha: true, antialias: true, logarithmicDepthBuffer: false, sortObjects: true, autoClear: false, canvasStyle: 'position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; overflow: hidden; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; user-select: none;', shadowMapEnabled: true, shadowMapType: THREE.PCFShadowMap, ...options }; this.canvas = canvas; this.resolution = options.resolution; this.clearColor = options.clearColor; this.gl = new THREE.WebGLRenderer({ canvas: options.canvas, precision: options.precision, premultipliedAlpha: options.premultipliedAlpha, stencil: options.stencil, depth: options.depth, preserveDrawingBuffer: options.preserveDrawingBuffer, alpha: options.alpha, antialias: options.antialias, logarithmicDepthBuffer: options.logarithmicDepthBuffer }); this.context = this.gl.domElement; this.context.setAttribute('style', options.canvasStyle); if (!!options.pixelated) this.context.style.imageRendering = 'pixelated'; this.gl.sortObjects = options.sortObjects; this.gl.autoClear = options.autoClear; this.gl.shadowMap.enabled = options.shadowMapEnabled; this.gl.shadowMap.type = options.shadowMapType; if (options.place === 'last') canvas.dom.appendChild(this.context); else canvas.dom.insertBefore(this.context, canvas.dom.firstChild); this.resizeHandler = () => this.resize(); window.addEventListener('resize', this.resizeHandler, false); // View frameworks can sometimes swallow resize events this.resize(); setTimeout(() => this.resize(), 1); setTimeout(() => this.resize(), 100); setTimeout(() => this.resize(), 500); // Clear canvas this.gl.setPixelRatio(this.resolution); this.gl.setScissorTest(false); this.gl.setClearColor(this.clearColor, 0); this.gl.clear(); this.gl.setScissorTest(true); // Declare render loop here, because doing it on the prototype will lexically // bind 'this' using nested functions which would impact performance. var scope = this; this.invalidateFrames = 1; this.dirty = true; this.time = 0; this.render = function(time) { scope.time = time; // Request next frame requestAnimationFrame(scope.render); // Pass 1: measure canvas; do this only when the canvas is dirty if (scope.invalidateFrames > 0 && scope.dirty) { let bounds = scope.context.getBoundingClientRect(); // Test for changes, position & size if ( bounds.left != scope.offset.left || bounds.top != scope.offset.top || bounds.width != scope.offset.width || bounds.height != scope.offset.height ) { scope.offset = bounds; scope.invalidateCanvas(); // Size changed, canvas needs to adapt if (bounds.width != scope.offset.width || bounds.height != scope.offset.height) { scope.gl.setSize( scope.offset.width /* scope.resolution*/, scope.offset.height /* scope.resolution*/, false ); } } scope.invalidateFrames--; } // Update animations Tween.update(time, scope); // Pass 2: measure view changes, stamp out old space let revokeDirtyFlag = true; for (let view of scope.canvas.views) { // If any view has measured changes, the canvas will remain dirty if (view.clear(time)) revokeDirtyFlag = false; } // Pass 3: render view content for (let view of scope.canvas.views) view.render(time); // If nothing has changed in size or position the canvas is clean if (revokeDirtyFlag) scope.dirty = false; }; // Start render loop if (options.startImmediately) this.start(); } destroy() { this.context.remove(); this.render = function() {}; window.removeEventListener('resize', this.resizeHandler); this.gl.dispose(); this.gl = undefined; } start() { this.render(performance.now()); } resize() { this.offset = this.context.getBoundingClientRect(); this.gl.setSize(this.offset.width /* this.resolution*/, this.offset.height /* this.resolution*/, false); this.invalidateCanvas(30); this.invalidateViews(30); } invalidateCanvas(frames = 1) { this.invalidateFrames += frames; if (this.invalidateFrames > 60) this.invalidateFrames = 60; } invalidateViews(frames = 1) { this.dirty = true; for (let view of this.canvas.views) view.invalidate(frames); } }