import vertexShader from './shaders/wormholeVertex.glsl' import fragmentShader from './shaders/wormholeFragment.glsl' import { Math as MathExtra, Scene, OrthographicCamera, Mesh, PlaneBufferGeometry, ShaderMaterial, Vector3, Vector4, Quaternion, Matrix4, RepeatWrapping, LinearFilter, WebGLRenderer, TextureLoader } from 'three' import EffectComposer from './postprocessing/EffectComposer' import RenderPass from './postprocessing/RenderPass' import BloomPass from './postprocessing/UnrealBloomPass' const textureLoader = new TextureLoader() export default class SimulationRenderer { constructor (config, player) { this.loadConfig(config) this.player = player this.zoom = 1 this.pixelSize = 4 this.width = window.innerWidth this.height = window.innerHeight this.renderer = new WebGLRenderer() this.renderer.setSize(this.width, this.height) this.renderer.sortObjects = false this.renderer.autoClear = false this.domElement = this.renderer.domElement const material = new ShaderMaterial( { uniforms: this.uniforms, vertexShader, fragmentShader, extensions: { shaderTextureLOD: true } } ) this.scene = new Scene() this.camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1) this.quad = new Mesh(new PlaneBufferGeometry(2, 2), material) this.quad.frustumCulled = false this.scene.add(this.quad) this.renderPasses = [ new RenderPass(this.scene, this.camera), new BloomPass(1024, 2.7, 0.7, 0.8) ] this.renderPasses[this.renderPasses.length - 1].renderToScreen = true this.composer = new EffectComposer(this.renderer) this.renderPasses.forEach(pass => { this.composer.addPass(pass) }) } loadConfig (config) { this.wormholePositionSize = new Vector4( config.wormhole.position.x, config.wormhole.position.y, config.wormhole.position.z, config.wormhole.radius ) this.blackholePositionSize = new Vector4( config.blackhole.position.x, config.blackhole.position.y, config.blackhole.position.z, config.blackhole.radius ) this.saturnPositionSize = new Vector4( config.saturn.position.x, config.saturn.position.y, config.saturn.position.z, config.saturn.radius ) this.planetPositionSize = new Vector4( config.planet.position.x, config.planet.position.y, config.planet.position.z, config.planet.radius ) this.blackholeDisk = config.blackhole.disk this.saturnRings = config.saturn.rings this.wormholeGravityRatio = config.wormhole.gravityRatio this.uniforms = { wormhole: { type: 'v4', value: this.wormholePositionSize }, wormholeGravityRatio: { type: 'f', value: this.wormholeGravityRatio }, blackhole: { type: 'v4', value: this.blackholePositionSize }, saturn: { type: 'v4', value: this.saturnPositionSize }, planet: { type: 'v4', value: this.planetPositionSize }, blackholeDisk: { type: 'v4', value: this.blackholeDisk }, saturnRings: { type: 'v4', value: this.saturnRings }, planetDiffuse: { type: 'v3', value: config.planet.diffuse }, planetSpecular: { type: 'v3', value: config.planet.specular }, texSaturn: { type: 't', value: this.loadTexture(config.saturn.texture) }, texSaturnRings: { type: 't', value: this.loadTexture(config.saturn.ringsTexture) }, texGalaxy1: { type: 't', value: this.loadTexture(config.galaxy1.texture) }, texGalaxy2: { type: 't', value: this.loadTexture(config.galaxy2.texture) }, texAccretionDisk: { type: 't', value: this.loadTexture(config.blackhole.diskTexture) }, lightDirection: { type: 'v3', value: config.saturn.lightDirection }, cameraMatrix: { type: 'm4', value: new Matrix4() }, cameraGalaxy: { type: 'i', value: 0 } } this.uniforms.texSaturnRings.value.wrapS = RepeatWrapping this.uniforms.texAccretionDisk.value.wrapS = RepeatWrapping this.uniforms.texSaturn.value.minFilter = LinearFilter this.uniforms.texGalaxy1.value.minFilter = LinearFilter this.uniforms.texGalaxy2.value.minFilter = LinearFilter } loadTexture (path) { this._textureCount = (this._textureCount || 0) + 1 return textureLoader.load(path, () => { this._loadedTexturesCount = (this._loadedTexturesCount || 0) + 1 if (this._loadedTexturesCount === this._textureCount) { this.onTexturesLoaded && this.onTexturesLoaded() } }) } updateEffectComposer () { this.composer.setSize( Math.floor(this.width / this.pixelSize), Math.floor(this.height / this.pixelSize) ) } updateCamera () { let vx, vy if (this.width > this.height) { vx = 1 vy = this.height / this.width } else { vx = this.width / this.height vy = 1 } this.player.eyes.aspect = vx / vy this.player.eyes.fov = MathExtra.RAD2DEG * 2 * Math.atan(vy / this.zoom) } setPixelSize (pixelSize) { this.pixelSize = pixelSize this.updateEffectComposer() } setZoom (zoom) { this.zoom = zoom this.updateCamera() } setSize (width, height, pixelSize = null) { this.width = width this.height = height if (pixelSize) { this.pixelSize = pixelSize } this.renderer.setSize(this.width, this.height) this.updateCamera() this.updateEffectComposer() } render () { const rayScale = new Vector3( this.player.eyes.aspect, 1, 1 / Math.tan(MathExtra.DEG2RAD * this.player.eyes.fov / 2) ) const worldPosition = new Vector3() const worldQuaternion = new Quaternion() this.uniforms.cameraMatrix.value.compose( this.player.eyes.getWorldPosition(worldPosition), this.player.eyes.getWorldQuaternion(worldQuaternion), rayScale ) this.uniforms.cameraGalaxy.value = this.player.galaxy this.renderer.clear() this.composer.render() } }