awv3
Version:
⚡ AWV3 embedded CAD
165 lines (139 loc) • 6.11 kB
JavaScript
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);
}
}