UNPKG

gl-css3d

Version:

sync CSS 3D transformations to a WebGL scene

162 lines (129 loc) 5 kB
'use strict'; var matrixToCSS = require('matrix-to-css'); var mat4 = require('gl-mat4'); var createMesh = require('gl-mesh'); var glShader = require('gl-shader'); var glslify = require('glslify'); module.exports = function(element, opts) { return new GLCSS3D(element, opts); }; function GLCSS3D(element, opts) { if (!element) throw new Error('gl-css3d requires element'); var domElement = document.createElement('div'); domElement.style.transformStyle = domElement.style.webkitTransformStyle = 'preserve-3d'; domElement.style.overflow = 'hidden'; domElement.style.pointerEvents = 'none'; domElement.style.position = 'absolute'; domElement.style.zIndex = '-1'; // below WebGL canvas domElement.style.top = '0'; domElement.style.left = '0'; domElement.style.margin = '0'; domElement.style.padding = '0'; var cameraElement = document.createElement('div'); cameraElement.style.position = 'absolute'; cameraElement.style.transformStyle = cameraElement.style.webkitTransformStyle = 'preserve-3d'; //cameraElement.style.display = 'none'; cameraElement.style.pointerEvents = 'auto'; // allow mouse interaction cameraElement.appendChild(element); domElement.appendChild(cameraElement); document.body.appendChild(domElement); this.domElement = domElement; this.cameraElement = cameraElement; opts = opts || {}; this.planeWidth = opts.planeWidth || 2; // assume -1 to +1 this.planeHeight = opts.planeHeight || 2; //this.tint = opts.tint || [0.5,0,0,0]; // reddish tint, etc.. useful? (note gl blending mode) this.tint = opts.tint || [0,0,0,0]; // fully transparent this.blend = (opts.blend !== undefined) ? opts.blend : false; // overwrite transparent color this.flipX = (opts.flipX !== undefined) ? opts.flipX : true; this.flipY = (opts.flipY !== undefined) ? opts.flipY : true; this.backface = (opts.backface !== undefined) ? opts.backface : true; this.cutoutMesh = null; this.cutoutShader = null; this.gl = null; } GLCSS3D.prototype.ginit = function(gl) { this.gl = gl; var hx = this.planeWidth / 2; var hy = this.planeHeight / 2; var indices = // triangles forming a rectangle for the front face [ [2, 1, 0], [3, 1, 2] ]; if (this.backface) { indices.push([0, 1, 2]); indices.push([2, 1, 3]); } this.cutoutMesh = createMesh(gl, indices, { "position": [ [-hx, -hy, 0], [-hx, +hy, 0], [+hx, -hy, 0], [+hx, +hy, 0]] }) this.cutoutShader = glShader(gl, glslify("\ attribute vec3 position;\ \ uniform mat4 projection;\ uniform mat4 view;\ \ void main() {\ gl_Position = projection * view * vec4(position, 1.0);\ }", {inline: true}), // color it all transparent so CSS element is visible through glslify("\ precision highp float;\ uniform vec4 color;\ \ void main() {\ gl_FragColor = color;\ }", {inline: true})); }; GLCSS3D.prototype.updatePerspective = function(cameraFOVradians, width, height) { // CSS world perspective - only needs to change on gl-resize (not each rendering tick) var fovPx = 0.5 / Math.tan(cameraFOVradians / 2) * height; this.domElement.style.perspective = this.domElement.style.webkitPerspective = fovPx + 'px'; //domElement.style.perspectiveOrigin = '50% 50%'; // already is the default this.domElement.style.width = width + 'px'; this.domElement.style.height = height + 'px'; this.fovPx = fovPx; // CSS camera element child this.cameraElement.style.width = width + 'px'; this.cameraElement.style.height = height + 'px'; this.width = width; this.height = height; }; var cssMatrix = mat4.create(); GLCSS3D.prototype.updateView = function(view) { var scaleX = this.planeWidth / this.width; var scaleY = this.planeHeight / this.height; var scaleZ = 1; if (this.flipX) scaleX = -scaleX; if (this.flipY) scaleY = -scaleY; mat4.scale(cssMatrix, view, [scaleX, scaleY, scaleZ]); // three.js CSS3Renderer getCameraCSSMatrix inverts these to fix flipped rotation orientation // TODO: matrix transformation instead? cssMatrix[1] = -cssMatrix[1]; cssMatrix[5] = -cssMatrix[5]; cssMatrix[9] = -cssMatrix[9]; cssMatrix[13] = -cssMatrix[13]; this.cameraElement.style.transform = this.cameraElement.style.webkitTransform = 'translateZ('+this.fovPx+'px) ' + matrixToCSS(cssMatrix); }; GLCSS3D.prototype.renderCutout = function(view, proj) { this.cutoutShader.bind() this.cutoutShader.attributes.position.location = 0 this.cutoutShader.uniforms.projection = proj this.cutoutShader.uniforms.view = view this.cutoutShader.uniforms.color = this.tint this.cutoutMesh.bind(this.cutoutShader) if (!this.blend) this.gl.disable(this.gl.BLEND); this.cutoutMesh.draw() if (!this.blend) this.gl.enable(this.gl.BLEND); // TODO: use module to push/restore gl state? this.cutoutMesh.unbind() }; GLCSS3D.prototype.render = function(view, proj) { this.updateView(view); this.renderCutout(view, proj); };