UNPKG

elation-engine

Version:
1,266 lines (1,165 loc) 80.7 kB
if (typeof THREE == 'undefined') { THREE = {}; } elation.require([ "engine.external.three.three", "engine.external.three.three-objects", "engine.external.three.three-controls", "engine.external.three.three-postprocessing", "engine.external.three.three-shaders", "engine.external.three.three-extras", "engine.external.three.CSS3DRenderer", "engine.external.three.CubemapToEquirectangular", "engine.external.holoplay", "engine.external.webxr-polyfill", "engine.materials", "engine.geometries", //"ui.select", //"ui.slider", 'elements.base', ], function() { elation.requireCSS('engine.systems.render'); elation.extend("engine.systems.render", function(args) { elation.implement(this, elation.engine.systems.system); this.views = {}; this.forcerefresh = false; this.view_init = function(viewname, viewargs) { let newview = elation.elements.create('engine-systems-render-view', viewargs); return this.view_add(viewname, newview); } this.view_add = function(viewname, view) { this.views[viewname] = view; elation.events.fire({type: 'render_view_add', element: this, data: this.views[viewname]}); return this.views[viewname]; } this.view_remove = function(viewname) { if (viewname in this.views) { this.views[viewname].destroy(); delete this.views[viewname]; } } this.system_attach = function(ev) { console.log('INIT: render'); let webglmode = 'webgl'; let rendererargs = { antialias: true, logarithmicDepthBuffer: false, alpha: true, preserveDrawingBuffer: true, enableWebXR: false, stencil: false, powerPreference: 'high-performance', //precision: 'lowp', }; if (webglmode == 'webgl2') { rendererargs.canvas = document.createElement( 'canvas' ); rendererargs.context = rendererargs.canvas.getContext( 'webgl2', { antialias: true } ); } this.renderer = new THREE.WebGLRenderer(rendererargs); this.cssrenderer = new THREE.CSS3DRenderer(); /* this.holorenderer = new HoloPlay.Renderer(rendererargs); this.holorenderer.quiltResolution = 3360; this.holorenderer.tileCount.set(8, 6); document.body.appendChild(this.holorenderer.domElement); */ //this.renderer.autoClear = true; this.renderer.setClearColor(0x000000, 1); this.renderer.shadowMap.enabled = true; this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; this.renderer.xr.enabled = false; //this.renderer.xr.manageCameraPosition = true; this.lastframetime = performance.now(); this.renderer.setAnimationLoop((time, frame) => { this.render(time - this.lastframetime, frame); this.lastframetime = time; }); //this.renderer.setAnimationLoop((ev) => { this.render(); }); //this.renderer.gammaInput = true; //this.renderer.gammaOutput = false; //this.renderer.outputEncoding = THREE.LinearEncoding; //this.renderer.outputEncoding = THREE.sRGBEncoding; this.renderer.outputEncoding = THREE.sRGBEncoding; this.renderer.gammaFactor = 1.3; /* this.renderer.toneMapping = THREE.CineonToneMapping; this.renderer.toneMappingExposure = 1; this.renderer.toneMappingWhitePoint = 1; */ this.renderer.debug.checkShaderErrors = false; this.lastframetime = 0; elation.events.add(this.engine.systems.world, 'world_change,world_thing_add,world_thing_remove,world_thing_change', this); // FIXME - globally-bound events are dirty, things should fire events when their properties change elation.events.add(null, 'physics_update,thing_drag_move,thing_rotate_move,engine_texture_load', elation.bind(this, this.setdirty)); // Hide the canvas from accessibility API this.renderer.domElement.setAttribute('aria-hidden', true); } this.setclearcolor = function(color, opacity) { if (typeof color == 'undefined') color = 0xffffff; if (typeof opacity == 'undefined') opacity = 1; this.renderer.setClearColor(color, opacity); } this.setdirty = function() { this.dirty = true; } this.engine_stop = function(ev) { console.log('SHUTDOWN: render'); for (var k in this.views) { this.views[k].destroy(); }; } this.engine_frame = function(ev) { // Disabled rendering on engine_frame, use Three's setAnimationLoop instead //this.lastframetime += ev.data.delta; //this.render(); } this.render = function(time, frame) { for (var k in this.views) { this.views[k].updatePickingObject(); if (this.views[k].stats) { this.views[k].stats.update(); } } this.engine.advance(); if (this.forcerefresh || this.dirty) { //console.log('FRAME: render'); this.dirty = false; //this.renderer.clear(); for (var k in this.views) { this.views[k].render(time, frame); } //this.lastframetime = 0; } } this.textureSampleMipmapLevel = (function() { let scene = new THREE.Scene(); let plane = new THREE.PlaneGeometry(2, 2); let material = new THREE.MeshBasicMaterial({color: 0xffffff}); let mesh = new THREE.Mesh(plane, material); mesh.position.set(0,0,-1); scene.add(mesh); let camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 1, 1000); scene.add(camera); let rendertarget = new THREE.WebGLRenderTarget(1, 1); let oldviewport = new THREE.Vector4(); let viewport = new THREE.Vector4(0, 0, 1, 1); return function(texture, level=0) { material.map = texture; material.map.needsUpdate = true; let size = Math.pow(2, level); rendertarget.setSize(size, size); let renderer = this.renderer; let pixeldata = new Uint8Array(4 * size * size); let oldrendertarget = renderer.getRenderTarget(); let oldencoding = texture.encoding; texture.encoding = THREE.LinearEncoding; renderer.getViewport(oldviewport); renderer.setViewport(viewport); renderer.setRenderTarget(rendertarget); let isXR = renderer.xr.enabled; renderer.xr.enabled = false; renderer.render(scene, camera); renderer.readRenderTargetPixels(rendertarget, 0, 0, size, size, pixeldata); renderer.setRenderTarget(oldrendertarget); renderer.setViewport(oldviewport); renderer.xr.enabled = isXR; texture.encoding = oldencoding; return pixeldata; } })(); this.textureHasAlpha = (function() { let scene = new THREE.Scene(); let plane = new THREE.PlaneGeometry(2, 2); //let material = new THREE.MeshBasicMaterial({color: 0xff0000}); let material = new THREE.ShaderMaterial({ vertexShader: ` uniform sampler2D map; varying vec2 vUv; void main() { mat4 modelViewProjectionMatrix = projectionMatrix * modelViewMatrix; vec3 transformed = vec3( position ); // Transform our new vertex position into clip space vec4 pos = modelViewProjectionMatrix * vec4(transformed, 1.0); gl_Position = pos; vUv = uv; } `, fragmentShader: ` uniform sampler2D map; varying vec2 vUv; void main(){ vec4 sampledDiffuseColor = texture2D( map, vUv ); gl_FragColor = vec4(sampledDiffuseColor.rgb, floor(sampledDiffuseColor.a)); //gl_FragColor = vec4(sampledDiffuseColor.rgb, 0.0); } `, uniforms: { map: { type: 't' }, }, }); let mesh = new THREE.Mesh(plane, material); mesh.position.set(0,0,-1); scene.add(mesh); let camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 1, 1000); scene.add(camera); let rendertarget = new THREE.WebGLRenderTarget(1, 1); let oldviewport = new THREE.Vector4(); let viewport = new THREE.Vector4(0, 0, 1, 1); return function(texture) { material.uniforms.map.value = texture; texture.needsUpdate = true; let renderer = this.renderer; let pixeldata = new Uint8Array(4); let oldrendertarget = renderer.getRenderTarget(); renderer.getViewport(oldviewport); renderer.setViewport(viewport); renderer.setRenderTarget(rendertarget); renderer.render(scene, camera); renderer.readRenderTargetPixels(rendertarget, 0, 0, 1, 1, pixeldata); renderer.setRenderTarget(oldrendertarget); renderer.setViewport(oldviewport); return pixeldata[3] < 255; } })() this.world_thing_add = function(ev) { this.setdirty(); } this.world_thing_remove = function(ev) { this.setdirty(); } this.world_thing_change = function(ev) { this.setdirty(); } this.world_change = function(ev) { this.setdirty(); } }); elation.elements.define("engine.systems.render.view", class extends elation.elements.base { //elation.implement(this, elation.engine.systems.system); init() { super.init(); this.effects = {}; this.defineAttributes({ picking: { type: 'boolean', default: false }, useWebXRPolyfill: { type: 'boolean', default: true }, size: { type: 'object', default: [0, 0] }, showstats: { type: 'boolean', default: false }, fullsize: { type: 'boolean', default: false }, rendermode: { type: 'string', default: 'default' }, resolution: { type: 'object' }, enabled: { type: 'boolean', default: true }, crosshair: { type: 'boolean', default: false }, engine: { type: 'string', default: 'default' }, xrsession: { type: 'object' }, enablepostprocessing: { type: 'boolean', default: true }, }); //elation.html.addclass(this, "engine_view"); //this.picking = this.args.picking || false; //this.useWebXRPolyfill = elation.utils.any(this.args.useWebXRPolyfill, true); //this.size = [0, 0]; this.size_old = [0, 0]; this.scale = 100;// * devicePixelRatio; //this.showstats = this.args.showstats || false; this.fullscreen = false; this.renderpasses = {}; this.aspectscale = 1; this.renderinfo = {render: {}, memory: {}}; // Used by various render pass shaders this.sizevec = new THREE.Vector2(); this.sizevecinverse = new THREE.Vector2(); //this.rendermode = this.args.rendermode || 'default'; /* if (this.fullsize) { elation.html.addclass(this, "engine_view_fullsize"); } if (this.resolution) { elation.html.addclass(this, "engine_view_fixedsize"); } if (this.crosshair) { elation.html.create({tag: 'div', classname: 'engine_view_crosshair', append: this}); } */ } create() { super.create(); if (!this.engine) { console.log("ERROR: couldn't create view, missing engine parameter"); } else if (typeof elation.engine.instances[this.engine] == 'undefined') { console.log("ERROR: couldn't create view, engine '" + this.engine + "' doesn't exist"); } else { this.engine = elation.engine.instances[this.engine]; //this.createView(); } this.rendersystem = this.engine.systems.render; this.tabIndex = 1; this.canvas = this.rendersystem.renderer.domElement; if (!this.fullsize) { this.style.width = '100%'; this.style.height = '100%'; this.canvas.style.width = '100%'; this.canvas.style.height = '100%'; } elation.events.add(window, "resize", this); elation.events.add(window, "orientationchange", ev => this.resize(true)); elation.events.add(document.body, "mouseenter,mouseleave", this); elation.events.add(this.canvas, "mouseover,mousedown,mousemove,mouseup,click", this); elation.events.add(this.canvas, "mousewheel,touchstart,touchmove,touchend", this); elation.events.add(document, "pointerlockchange,mozpointerlockchange", elation.bind(this, this.pointerlockchange)); elation.events.add(this, "dragover,drag,dragenter,dragleave,dragstart,dragend,drop", elation.bind(this, this.proxyEvent)); if (this.rendersystem.renderer.domElement && !this.rendersystem.renderer.domElement.parentNode) { this.appendChild(this.rendersystem.renderer.domElement); // Handle context loss and restore elation.events.add(this.rendersystem.renderer.domElement, 'webglcontextlost', elation.bind(this, function(ev) { console.warn('WebGL context lost'); ev.preventDefault(); //this.engine.stop(); })); elation.events.add(this.rendersystem.renderer.domElement, 'webglcontextrestored', elation.bind(this, function(ev) { console.log('context restored'); ev.preventDefault(); //this.engine.start(); this.create(); this.rendersystem.setdirty(); })); } if (this.rendersystem.cssrenderer && !this.rendersystem.cssrenderer.domElement.parentNode) { this.appendChild(this.rendersystem.cssrenderer.domElement); elation.html.addclass(this.rendersystem.cssrenderer.domElement, 'engine_systems_render_css3d'); } if (this.xrsession) { this.setXRSession(this.xrsession); } this.rendersystem.view_add(this.name, this); if (!this.actualcamera) { var cam = new THREE.PerspectiveCamera(75, 4/3, 1e-2, 1e4); this.actualcamera = cam; } this.setcamera(this.actualcamera); if (this.pickingdebug) { this.setscene(this.engine.systems.world.scene['colliders']); } else { this.setscene(this.engine.systems.world.scene['world-3d']); } if (this.engine.systems.world.scene['sky']) { this.setskyscene(this.engine.systems.world.scene['sky']); } //console.log(this.engine.systems.world.scene['world-3d']); // Depth shader, used for SSAO, god rays, etc if (false && !this.depthTarget) { var depthShader = THREE.ShaderLib[ "depth" ]; var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms ); this.depthMaterial = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms } ); this.depthMaterial.blending = THREE.NoBlending; this.depthTarget = new THREE.WebGLRenderTarget( this.size[0], this.size[1], { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter, }); } //this.composer = this.createRenderPath([this.rendermode]); this.rendertarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, //type: THREE.FloatType, //type: THREE.UnsignedShortType, stencilBuffer: true, depthBuffer: true, }); this.rendertarget.texture.encoding = THREE.sRGBEncoding; this.rendertarget.depthTexture = new THREE.DepthTexture(); this.rendertarget.depthTexture.type = THREE.UnsignedInt248Type; this.rendertarget.depthTexture.format = THREE.DepthStencilFormat; //this.composer = this.createRenderPath(['clear', /*'portals', 'masktest',*/ this.rendermode, 'fxaa'/*, 'msaa'*/, 'bloom', 'maskclear', 'recording'], this.rendertarget); if (this.enablepostprocessing) { this.composer = this.createRenderPath(['clear', this.rendermode,/* 'tonemapping',*/ 'unrealbloom', 'fxaa', 'gamma'], this.rendertarget); //this.composer = this.createRenderPath(['clear', this.rendermode, 'fxaa'/*, 'msaa'*/, 'bloom', 'maskclear'], this.rendertarget); //this.effects['msaa'].enabled = false; //this.composer = this.createRenderPath([this.rendermode, 'ssao', 'recording']); if (this.showstats) { // FIXME - not smart! elation.events.add(this.composer.passes[3], 'render', elation.bind(this, this.updateRenderStats)); } } this.getsize(); if (this.showstats) { this.stats = new Stats(); this.stats.domElement.style.position = 'absolute'; this.stats.domElement.style.top = '0px'; this.stats.domElement.style.right = '0px'; this.appendChild(this.stats.domElement); this.renderstats = new THREEx.RendererStats() this.renderstats.domElement.style.position = 'absolute' this.renderstats.domElement.style.right = '0px' this.renderstats.domElement.style.top = '50px' this.appendChild( this.renderstats.domElement ) } this.glcontext = this.rendersystem.renderer.getContext(); if (this.picking) { this.mousepos = [0, 0, document.body.scrollTop]; this.lastmousepos = [-1, -1, -1]; this.initPicking(); this.pickingdebug = false; this.engine.systems.controls.addCommands('view', { 'picking_debug': elation.bind(this, function(ev) { if (ev.value == 1) { this.pickingdebug = !this.pickingdebug; this.rendersystem.dirty = true; if (this.pickingdebug) { this.engine.systems.world.enableDebug(); } else { this.engine.systems.world.disableDebug(); } } }), 'picking_select': elation.bind(this, function(ev) { if (ev.value == 1) { this.click({clientX: this.mousepos[0], clientY: this.mousepos[1]}); } }) }); //this.engine.systems.controls.addBindings('view', {'keyboard_f7': 'picking_debug'}); this.engine.systems.controls.addBindings('view', {'gamepad_any_button_0': 'picking_select'}); this.engine.systems.controls.activateContext('view'); } } destroy() { // TODO - deeallocate resources } createRenderPath(passes, target) { // this.createRenderPath(['picking', 'oculus_deform'], depthTarget) // this.createRenderPath(['depth', 'oculus_deform'], pickingTarget) // this.createRenderPath(['sky', 'default', 'FXAA', 'oculus_deform', 'oculus_colorshift']) var composer = new THREE.EffectComposer(this.rendersystem.renderer, target); var renderToScreen = false; for (var i = 0; i < passes.length; i++) { var pass = this.createRenderPass(passes[i]); //console.log('NEW PASS:', i, target, passes[i], pass); if (pass) { //if (i == 0) pass.clear = true; composer.addPass(pass); renderToScreen = renderToScreen || pass.renderToScreen; // Only store pass data for the main path if (!target) { this.renderpasses[passes[i]] = pass; } } } //if (!target && !renderToScreen) { var pass = this.createRenderPass('screenout'); composer.addPass(pass); //} return composer; } createRenderPass(name, args) { var pass = false; switch (name) { case 'default': pass = new THREE.RenderPass(this.scene, this.actualcamera, null, null, 1); pass.clear = false; break; case 'normalmap': pass = new THREE.RenderPass(this.scene, this.actualcamera, new THREE.MeshNormalMaterial(), null, 1); pass.clear = false; break; case 'depth': pass = new THREE.RenderPass(this.scene, this.actualcamera, new THREE.MeshDepthMaterial(), null, 1); pass.clear = false; break; case 'portals': pass = new THREE.PortalRenderPass(this.actualcamera); pass.clear = false; this.portalpass = pass; break; case 'clear': var pass = new THREE.ClearPass(); break; case 'oculus': pass = new THREE.OculusRenderPass(this.scene, this.actualcamera, null, null, 0); pass.setOculusParameters({ HMD: { hResolution: window.innerWidth, vResolution: window.innerHeight, hScreenSize: 0.14976, vScreenSize: 0.0936, interpupillaryDistance: 0.064, lensSeparationDistance: 0.064, eyeToScreenDistance: 0.041, distortionK : [1.0, 0.0, 0.0, 0.0], chromaAbParameter: [ 1, 0, 1, 0.0] } }); break; case '3dtvsbs': pass = new THREE.OculusRenderPass(this.scene, this.actualcamera, null, null, 0); pass.setOculusParameters({ HMD: { hResolution: window.innerWidth, vResolution: window.innerHeight, hScreenSize: 0.14976, vScreenSize: 0.0936, interpupillaryDistance: 0.064, lensSeparationDistance: 0.064, eyeToScreenDistance: 0.041, distortionK : [1.0, 0.0, 0.0, 0.0], chromaAbParameter: [ 1, 0, 1, 0.0] } }); break; case 'sky': pass = new THREE.RenderPass(this.skyscene, this.skycamera, null, null, 0); pass.clear = false; break; case 'film': pass = new THREE.FilmPass( 0.35, .75, 2048, false ); break; case 'recording': pass = new THREEcapRenderPass('/scripts/engine/external/threecap/'); this.recorder = pass; break; case 'sepia': pass = new THREE.ShaderPass( THREE.SepiaShader ); break; case 'bleach': pass = new THREE.ShaderPass( THREE.BleachBypassShader ); break; case 'copy': pass = new THREE.ShaderPass( THREE.CopyShader ); break; case 'screenout': pass = new THREE.ShaderPass( THREE.CopyShader ); pass.renderToScreen = true; break; case 'bloom': pass = new THREE.BloomPass(0.4, 25, 5); break; case 'unrealbloom': pass = new THREE.UnrealBloomPass(this.size, 0, 0, 0); pass.renderTargetsHorizontal.forEach(element => { element.texture.type = THREE.FloatType; }); pass.renderTargetsVertical.forEach(element => { element.texture.type = THREE.FloatType; }); break; case 'fxaa': pass = new THREE.ShaderPass( THREE.FXAAShader ); pass.uniforms[ 'resolution' ].value = this.sizevecinverse; break; case 'msaa': pass = new THREE.ManualMSAARenderPass(this.scene, this.actualcamera); pass.unbiased = true; pass.sampleLevel = 1; break; case 'masktest': this.maskscene = new THREE.Scene(); var maskobj = new THREE.Mesh(new THREE.SphereGeometry(1000)); maskobj.scale.y = -1; maskobj.position.set(0,0,0); window.maskobj = maskobj; this.maskscene.add(maskobj); pass = new THREE.MaskPass(this.maskscene, this.actualcamera); pass.clear = false; break; case 'maskclear': pass = new THREE.ClearMaskPass(); break; case 'ssao': pass = new THREE.SSAOPass( this.scene, this.actualcamera, this.size[0], this.size[1] ); pass.kernelRadius = 16; //pass.clear = true; break; case 'gamma': pass = new THREE.ShaderPass( THREE.GammaCorrectionShader ); break; case 'tonemapping': pass = new THREE.AdaptiveToneMappingPass(true, 256); break; } if (pass) this.effects[name] = pass; return pass; } setRenderMode(mode) { // Supported values: 'default', 'oculus' var lastpass = this.renderpasses[this.rendermode]; var pass = this.renderpasses[mode]; if (!pass) { pass = this.createRenderPass(mode); this.renderpasses[mode] = pass; } var passidx = this.composer.passes.indexOf(lastpass); console.log('toggle render mode: ' + this.rendermode + ' => ' + mode, passidx, lastpass, pass, this.renderpasses); this.composer.passes[passidx] = pass; if (this.pickingcomposer) this.pickingcomposer.passes[passidx] = pass; pass.camera = this.actualcamera; elation.html.removeclass(this, "engine_view_rendermode_" + this.rendermode); this.rendermode = mode; elation.html.addclass(this, "engine_view_rendermode_" + this.rendermode); this.rendersystem.setdirty(); } isFullscreen() { var fsel = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement; if (fsel) { return true; } return false; } toggleFullscreen(fullscreen) { if (typeof fullscreen == 'undefined') { fullscreen = !this.isFullscreen(); } else if (typeof fullscreen.data != 'undefined') { fullscreen = fullscreen.data; } if (fullscreen) { //var c = this.container; var c = document.documentElement; c.requestFullscreen = c.requestFullscreen || c.webkitRequestFullscreen || c.mozRequestFullScreen; if (typeof c.requestFullscreen == 'function') { c.requestFullscreen(); } } else { var fsel = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement; if (fsel) { document.exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen || document.mozExitFullScreen; document.exitFullscreen(); } } this.fullscreen = fullscreen; } startXR(mode='immersive-vr') { } async setXRSession(session) { this.xrsession = session; await this.rendersystem.renderer.xr.setSession(session); this.rendersystem.renderer.xr.enabled = true; this.rendersystem.renderer.outputEncoding = THREE.LinearEncoding; this.xrlayer = this.getXRBaseLayer(session); if (false && !this.xrscene) { // Set up a scene with an ortho camera to clone our XR framebuffer to, for display on the main screen this.xrscene = new THREE.Scene(); this.xrscene.background = new THREE.Color(0x000000); // Set up texture to copy the framebuffer into let w = this.xrlayer.framebufferWidth, h = this.xrlayer.framebufferHeight; let data = new Uint8Array( w * h * 3 ); this.xrscenetexture = new THREE.DataTexture( data, w, h, THREE.RGBFormat ); this.xrscenetexture.minFilter = THREE.NearestFilter; this.xrscenetexture.magFilter = THREE.NearestFilter; // Set up the plane to render only one eye, centered in the middle of the screen this.xrsceneplane = new THREE.Mesh(new THREE.PlaneGeometry(w, h), new THREE.MeshBasicMaterial({map: this.xrscenetexture, side: THREE.DoubleSide, color: 0xffffff})); this.xrsceneplane.position.set(w / 4, 0, -10); //this.xrsceneplane.rotation.set(Math.PI/4, 0, 0); this.xrscene.add(this.xrsceneplane); this.xrscenecam = new THREE.OrthographicCamera(-w / 4, w, h, -h / 2, -1000, 1000); //this.xrscenecam = new THREE.PerspectiveCamera(); this.xrscene.add(this.xrscenecam); } } handleXRFrame(session, frame) { if (session) { this.xrsession = session; if (this.activething && this.activething.updateXR) { this.activething.updateXR(frame); } let xrReferenceSpace = this.engine.systems.render.renderer.xr.getReferenceSpace(); elation.events.fire({ element: this.engine, type: 'xrframe', data: { frame, session, xrReferenceSpace } }); } } stopXR() { if (this.xrsession) { this.xrsession.end(); } } handleXRend(ev) { this.rendersystem.renderer.xr.setSession(null); this.rendersystem.renderer.xr.enabled = false; this.enabled = false; elation.html.removeclass(this, 'webxr_session_active'); this.xrsession = false; this.rendersystem.renderer.outputEncoding = THREE.sRGBEncoding; setTimeout(() => { elation.events.fire({type: 'resize', element: window, data: true }); }, 100); } getXRBaseLayer(session) { if (session.renderState.layers && session.renderState.layers.length > 0) { return session.renderState.layers[session.renderState.layers.length - 1]; } return session.renderState.baseLayer; } updateCameras() { // Closure scratch variables var _position = new THREE.Vector3(), _quaternion = new THREE.Quaternion(), _scale = new THREE.Vector3(); // Make sure the parent's matrixWorld is up to date if (this.camera.parent) { this.camera.parent.updateMatrix(true); this.camera.parent.updateMatrixWorld(true); } if (this.actualcamera) { // Copy this.camera's position/orientation/scale/parent to our actual camera if (this.actualcamera.parent && this.actualcamera.parent != this.camera.parent) { this.actualcamera.parent.remove(this.actualcamera); } if (this.camera.parent && this.actualcamera.parent != this.camera.parent) { this.camera.parent.add(this.actualcamera); } this.actualcamera.position.copy(this.camera.position); this.actualcamera.scale.copy(this.camera.scale); this.actualcamera.rotation.copy(this.camera.rotation); this.actualcamera.quaternion.copy(this.camera.quaternion); if (this.actualcamera.fov != this.camera.fov || this.actualcamera.near != this.camera.near || this.actualcamera.far != this.camera.far || this.actualcamera.aspect != this.camera.aspect) { this.actualcamera.fov = this.camera.fov; this.actualcamera.near = this.camera.near || 0.001; this.actualcamera.far = this.camera.far || 10000; this.actualcamera.aspect = this.camera.aspect; this.actualcamera.updateProjectionMatrix(); } this.actualcamera.layers.mask = this.camera.layers.mask; } if (this.skycamera) { // Sky camera needs to use our camera's world rotation, and nothing else this.camera.matrixWorld.decompose( _position, _quaternion, _scale ); this.skycamera.quaternion.copy(_quaternion); if (this.skycamera.fov != this.camera.fov || this.skycamera.aspect != this.camera.aspect) { this.skycamera.fov = this.camera.fov; this.skycamera.aspect = this.camera.aspect; this.skycamera.updateProjectionMatrix(); } } } renderloop() { // Disable custom element renderloop } render(delta, xrframe) { if (this.enabled && this.scene && this.camera) { if (xrframe && this.xrsession) { this.handleXRFrame(this.xrsession, xrframe); } if (this.size[0] != this.size_old[0] || this.size[1] != this.size_old[1] || this.scale != this.scale_old) { this.setrendersize(this.size[0], this.size[1]); } this.updateCameras(); //this.setcameranearfar(); elation.events.fire({type: 'render_view_prerender', element: this}); if (this.picking && this.pickingactive) { //if (this.pickingdebug || this.picknum++ % 3 == 0 || delta > 0.05) { this.updatePickingTarget(); //} } //this.scene.overrideMaterial = this.depthMaterial; //this.rendersystem.renderer.render(this.scene, this.actualcamera, this.depthTarget, true); //this.scene.overrideMaterial = null; //this.rendersystem.renderer.render(this.scene, this.actualcamera); let colliderscene = this.engine.systems.world.scene['colliders'], worldscene = this.engine.systems.world.scene['world-3d']; if (this.pickingdebug) { if (colliderscene.parent !== worldscene) { worldscene.add(colliderscene); } } else if (colliderscene.parent === worldscene) { worldscene.remove(colliderscene); } if (this.enablepostprocessing) { this.effects[this.rendermode].camera = this.actualcamera; this.composer.render(delta); } else { if (this.xrsession) { //this.rendersystem.renderer.xr.enabled = true; //this.rendersystem.renderer.xr.setSession(this.xrsession); let layer = this.getXRBaseLayer(this.xrsession); if (layer) { this.xrlayer = layer; let renderer = this.rendersystem.renderer; renderer.clear(); renderer.render(this.scene, this.camera); if (false && document.visibilityState == 'visible') { if (true) { //console.log('try to clone framebuffer onto screen', this.xrscene, this.xrscenecam, this.xrsceneplane.material.map); /* if (!this.xrsceneplane.material.map) { console.log('update framebuffer texture'); this.xrsceneplane.material.map = new THREE.Texture(layer.framebuffer); this.xrsceneplane.material.needsUpdate = true; } */ renderer.xr.enabled = false; renderer.copyFramebufferToTexture( V(layer.framebufferWidth / 2, 0, 0), this.xrscenetexture ); //renderer.setFramebuffer(null); renderer.state.bindXRFramebuffer(null); renderer.setRenderTarget( renderer.getRenderTarget() ); let oldOutputEncoding = renderer.outputEncoding; renderer.outputEncoding = THREE.sRGBEncoding; renderer.setViewport(0, 0, layer.framebufferWidth, layer.framebufferHeight); renderer.render(this.xrscene, this.xrscenecam); renderer.xr.enabled = true; renderer.outputEncoding = oldOutputEncoding; } else { // FIXME - cloning the framebuffer to the main canvas output isn't working right as implemented, so in this codepath we just double-render //this.rendersystem.renderer.setFramebuffer(null); //renderer.state.bindXRFramebuffer(null); //renderer.setRenderTarget( renderer.getRenderTarget() ); this.rendersystem.renderer.render(this.scene, this.camera); //this.rendersystem.renderer.setFramebuffer(layer.framebuffer); renderer.state.bindXRFramebuffer(layer.framebuffer); renderer.setRenderTarget( renderer.getRenderTarget() ); } } } else { console.log('no XR layer found', this.xrsession.renderState); } } else { this.rendersystem.renderer.xr.enabled = false; //this.rendersystem.renderer.xr.setSession(null); //this.rendersystem.renderer.setFramebuffer(null); //this.rendersystem.renderer.state.bindXRFramebuffer(null); //this.rendersystem.renderer.setRenderTarget( renderer.getRenderTarget() ); this.rendersystem.renderer.render(this.scene, this.camera); //this.holocamera.position.setFromMatrixPosition(this.camera.matrixWorld); //this.holocamera.quaternion.setFromRotationMatrix(this.camera.matrixWorld); //this.holocamera.position.set(0, 10, 10).applyMatrix4(this.camera.matrixWorld); //this.holocamera.target.set(0, 0, 0).applyMatrix4(this.camera.matrixWorld); //this.rendersystem.holorenderer.render(this.scene, this.holocamera); } } if (this.rendersystem.cssrenderer && !this.xrsession) { this.rendersystem.cssrenderer.render(this.scene, this.actualcamera); } } /* if (this.stats) { this.stats.update(); } */ elation.events.fire({type: 'render_view_postrender', element: this}); this.size_old[0] = this.size[0]; this.size_old[1] = this.size[1]; this.scale_old = this.scale; //this.camera.rotation.y += Math.PI/32 * delta; } updateRenderStats() { this.renderstats.update(this.rendersystem.renderer); var renderinfo = this.rendersystem.renderer.info; elation.utils.merge(renderinfo.render, this.renderinfo.render); elation.utils.merge(renderinfo.memory, this.renderinfo.memory); //this.renderinfo.render.faces = renderinfo.render.faces; } toggleStats() { if (this.showstats) { if (this.renderstats) { this.removeChild(this.renderstats.domElement) } if (this.stats) { this.removeChild(this.stats.domElement); } this.showstats = false; } else { if (this.renderstats) { this.appendChild(this.renderstats.domElement) } if (this.stats) { this.appendChild(this.stats.domElement); } this.showstats = true; } } setactivething(thing) { if (thing.camera) { this.setcamera(thing.camera); } this.activething = thing; } setcamera(camera) { if (camera instanceof elation.component.base && camera.type == 'camera') { camera = camera.objects['3d']; } this.camera = camera; /* this.holocamera = new HoloPlay.Camera(); this.holocamera.fov = 90; console.log('my holocamera', this.holocamera, this.holocamera.fov); //camera.add(this.holocamera); this.holocamera.position.set(0,0,0); this.holocamera.target.set(0,0,1); this.holocamera.quaternion.set(0,0,0,1); */ this.setscene(this.getscene(camera)); this.updateCameras(); if (this.size[0] > 0 && this.size[1] > 0) { this.setrendersize(this.size[0], this.size[1]); } /* if (this.composer) { this.composer.passes[0].camera = this.camera; } if (this.pickingcomposer) { this.pickingcomposer.passes[0].camera = this.camera; } */ } setscene(scene) { var oldscene = this.scene; this.scene = scene; if (this.composer) { for (var i = 0; i < this.composer.passes.length; i++) { var pass = this.composer.passes[i]; if (pass.scene && pass.scene === oldscene) { pass.scene = this.scene; } } } this.rendersystem.setdirty(); } setcameranearfar(near, far) { /* if (!this.camdebug) { this.camdebug = elation.ui.window('camdebug', elation.html.create({append: document.body}), {title: 'Camera Debug'}); } */ if (!near || !far) { near = Infinity, far = 0; var nearradius = 0, farradius = 0; var campos = new THREE.Vector3().setFromMatrixPosition(this.camera.matrixWorld); var objpos = new THREE.Vector3(); var frustum = new THREE.Frustum(); var frustmat = new THREE.Matrix4().makePerspective( this.camera.fov, this.camera.aspect, 0.00001, 9e99).multiply(this.camera.matrixWorldInverse); //frustum.setFromMatrix( new THREE.Matrix4().multiplyMatrices( this.camera.projectionMatrix, this.camera.matrixWorldInverse ) ); frustum.setFromProjectionMatrix(frustmat); var within = [], nearnode = null, farnode = null; this.scene.traverse(elation.bind(this, function(node) { objpos.setFromMatrixPosition(node.matrixWorld); if (!node.isBoundingSphere && node.geometry && node.geometry.boundingSphere && frustum.intersectsSphere({center: objpos, radius: node.geometry.boundingSphere.radius})) { var distsq = objpos.distanceToSquared(campos); var rsq = node.geometry.boundingSphere.radius * node.geometry.boundingSphere.radius; var tdist = distsq - rsq; if (tdist <= 0) { within.push(node); } else { near = distsq; nearnode = node; } if (distsq + rsq > far) { far = distsq + rsq; farradius = node.geometry.boundingSphere.radius; farnode = node; } } })); if (nearnode) { within.push(nearnode); } if (within.length > 0) { var vpos = new THREE.Vector3(); for (var n = 0; n < within.length; n++) { if (within[n].geometry instanceof THREE.BufferGeometry) { let positions = within[n].geometry.attributes.position; for (var i = 0; i < positions.count; i++) { vpos.set(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]); within[n].localToWorld(vpos); if (true) { //frustum.containsPoint(vpos)) { var dsq = vpos.distanceToSquared(campos); if (dsq < near) { near = dsq; nearnode = within[n]; } } } } } } near = Math.max(Math.sqrt(near), 0.00001); far = Math.max(Math.sqrt(far), 10); } //console.log('set near/far:', near, far, (nearnode && nearnode.userData.thing ? nearnode.userData.thing.name : nearnode), (farnode && farnode.userData.thing ? farnode.userData.thing.name : farnode), nearradius, farradius); //var nearthing = this.getParentThing(nearnode); //this.camdebug.setcontent("<ul><li>Near: " + near + "</li><li>Far: " + far + "</li><li>Nearest Object: " + (nearthing ? nearthing.name : '(unknown:' + (nearnode ? nearnode.name || nearnode.id : "") + ')') + "</li></ul>"); if (near != Infinity && far != 0) { //this.camera.near = near * .5; this.camera.far = far * 1.2; this.camera.updateProjectionMatrix(); } } setskyscene(scene) { this.skyscene = scene || new THREE.Scene(); this.skycamera = new THREE.PerspectiveCamera(this.camera.fov, this.camera.aspect, 0.1, 10000); //this.skycamera.rotation = this.camera.rotation; //this.skycamera.quaternion = this.camera.quaternion; this.skyscene.add(this.skycamera); if (!this.skypass) { this.skypass = this.createRenderPass('sky'); this.composer.passes[0].clear = false; this.composer.passes.unshift(this.skypass); } } getscene(obj) { var scene = obj; while ( scene.parent ) { scene = scene.parent; } if ( scene !== undefined && scene instanceof THREE.Scene ) { return scene; } return false; } getsize(force) { //this.size = [this.offsetWidth, this.offsetHeight]; var s = (this.fullsize ? {w: window.innerWidth, h: window.innerHeight, bar: 2} : (this.resolution ? {w: this.resolution[0], h: this.resolution[1], foo: 1} : elation.html.dimensions(this) )); if (this.xrsession) { let xrlayer = this.getXRBaseLayer(this.xrsession); if (xrlayer) { s = { w: xrlayer.framebufferWidth, h: xrlayer.framebufferHeight, }; } } var domel = this.rendersystem.renderer.domElement; if (domel && (force || ((s.w != this.size[0] || s.h != this.size[1]) && (s.w > 0 && s.h > 0)))) { this.size = [s.w, s.h]; this.setrendersize(this.size[0], this.size[1]); } this.actualcamera.zoom = (Math.abs(window.orientation) == 90 ? 2 : 1); this.actualcamera.updateProjectionMatrix(); this.rendersystem.setdirty(); return this.size; } setrendersize(width, height) { if (this.xrsession) return; var scale = this.scale / 100, invscale = 100 / this.scale, scaledwidth = width * scale, scaledheight = height * scale; if (scaledwidth == 0 || scaledheight == 0) { console.warn('Renderer was told to resize to ' + scaledwidth + 'x' + scaledheight); return; } this.rendersystem.renderer.domElement.style.transformOrigin = '0 0'; this.rendersystem.renderer.domElement.style.transform = 'scale3d(' + [invscale, invscale, invscale].join(',') + ')'; this.sizevec.set(scaledwidth, scaledheight); this.sizevecinverse.set(1/scaledwidth, 1/scaledheight); var pixelratio = 1; //(window.devicePixelRatio ? window.devicePixelRatio : 1); if (pixelratio != this.rendersystem.renderer.getPixelRatio()) { this.rendersystem.renderer.setPixelRatio(pixelratio); } this.rendersystem.renderer.setSize(scaledwidth, scaledheight, false); if (this.composer) { this.composer.setSize(scaledwidth, scaledheight); } if (this.rendersystem.cssrenderer) { this.rendersystem.cssrenderer.setSize(width, height); //this.rendersystem.cssrenderer.setPixelRatio(pixelratio); } //this.composer.setSize(scaledwidth, scaledheight); if (this.pickingcomposer) { this.pickingcomposer.setSize(scaledwidth, scaledheight); } if (this.depthTarget) { this.depthTarget.setSize(scaledwidth, scaledheight); } if (this.effects['SSAO']) { this.effects['SSAO'].uniforms[ 'size' ].value.set( width, height); this.effects['SSAO'].uniforms[ 'tDepth' ].value = this.depthTarget; } if (this.skycamera) { this.skycamera.aspect = width / height / this.aspectscale; this.skycamera.updateProjectionMatrix() / this.aspectscale; } if (this.camera) { this.camera.aspect = width / height / this.aspectscale; this.camera.updateProjectionMatrix(); } if (this.actualcamera) { this.actualcamera.aspect = width / height; this.actualcamera.updateProjectionMatrix(); } } setscale(scale) { this.scale = scale; } system_attach(ev) { console.log('INIT: view (' + this.id + ')'); } engine_start(ev) { } engine_frame(ev) { //var scene = this.engine.systems.world.scene['world-3d']; //console.log('FRAME: view (' + this.id + ")"); } engine_stop(ev) { console.log('SHUTDOWN: view (' + this.id + ')'); } handleEvent(ev) { if (typeof this[ev.type] == 'function') return this[ev.type](ev); } resize(ev) { this.getsize(ev.data); } mouseover(ev) { if (!this.pickingactive) { //elation.events.add(this, 'mousemove,mouseout', this); this.pickingactive = true; } this.mousepos = [ev.clientX, ev.clientY, document.body.scrollTop]; } mousedown(ev) { if (this.pickingactive && this.picker.pickingobject) { this.cancelclick = false; var newev = {type: 'mousedown', element: this.getParentThing(this.picker.pickingobject), data: this.getPickingData(this.picker.pickingobject, [ev.clientX, ev.clientY]), clientX: ev.clientX, clientY: ev.clientY, button: ev.button, shiftKey: ev.shiftKey, altKey: ev.altKey, ctrlKey: ev.ctrlKey, metaKey: ev.metaKey}; /* var fired = elation.events.fire(newev); for (var i = 0; i < fired.length; i++) { if (fired[i].cancelBubble === true || ev.cancelBubble === true) { ev.stopPropagation(); } if (fired[i].returnValue === false || ev.returnValue === false) { ev.preventDefault(); } } */ this.proxyEvent(newev); } } mousewheel(ev) { //this.mousepos[0] = ev.clientX; //this.mousepos[1] = ev.clientY; this.mousepos[2] = document.body.scrollTop; var newev = {type: 'wheel', element: this.getParentThing(this.picker.pickingobject), data: this.getPickingData(this.picker.pickingobject, [ev.clientX, ev.clientY]), clientX: ev.clientX, clientY: ev.clientY, deltaX: ev.deltaX, deltaY: ev.deltaY, deltaZ: ev.deltaZ, deltaMode: ev.deltaMode, shiftKey: ev.shiftKey, altKey: ev.altKey, ctrlKey: ev.ctrlKey, metaKey: ev.metaKey, preventDefault: () => ev.preventDefault(), stopPropagation: () => ev.stopPropagation()}; this.proxyEvent(newev); if (newev.cancelBubble) { ev.stopPropagation(); } if (elation.events.wasDefaultPrevented(newev)) { ev.preventDefault(); return false; } } mousemove(ev, ignorePointerLock) { var el = document.pointerLockElement || document.mozPointerLockElement; if (el && !ignorePointerLock) { var dims = elation.html.dimensions(el); this.mousepos[0] = Math.round(dims.w / 2); this.mousepos[1] = Math.round(dims.h / 2); this.mousepos[2] = document.body.scrollTop; if (this.rendermode == 'oculus') { this.mousepos[0] /= 2; } } else if (this.mousepos && this.mousepos[0] != ev.clientX || this.mousepos[1] != ev.clientY) { this.mousepos[0] = ev.clientX; this.mousepos[1] = ev.clientY; this.mousepos[2] = document.body.scrollTop; //this.cancelclick = true; } } mouseenter(ev) { this.rendersystem.setdirty(); } mouseleave(ev) { this.rendersystem.setdirty(); } mouseout(ev) { if (this.pickingactive) { elation.events.remove(this, 'mousemove,mouseout', this); this.pickingactive = false; if (this.picker.pickingobject) { var newev = {type: "mouseout", element: this.getParentThing(this.picker.pickingobject), data: this.getPickingData(this.picker.pickingobject, [ev.clientX, ev.clientY]), client