import { mat4, vec3 } from 'gl-matrix'; import { System } from '@serpentity/serpentity'; import Drawable from '../nodes/drawable'; import Configurable from '../nodes/configurable'; import Cameras from '../nodes/cameras'; import { initializeShaderProgram, initializeBuffers } from '../webgl_utils'; const internals = { kDefaultLineLength: 1000, kCameraRadius: 5, kCameraAngularVelocity: Math.PI / 180, kFieldOfView: 45, // degrees kNearLimit: 0.1, kFarLimit: 100, kWidthRatio: 1.5, kHeightRatio: 1, kTargetVerticalResolution: 1024, kVertexShader: ` attribute vec4 aVertexPosition; attribute vec4 aColor; varying vec4 vColor; uniform mat4 uViewMatrix; uniform mat4 uProjectionMatrix; void main() { gl_Position = uProjectionMatrix * uViewMatrix * aVertexPosition; vColor = aColor; gl_PointSize = 10.0; // Set the point size } `, kFragmentShader: ` precision mediump float; varying vec4 vColor; void main() { gl_FragColor = vColor; } ` }; /** * Does all the WebGL rendering. I'm not super familiar with WebGL so I need * to revisit this in a while and see how I would restructure this. */ export default class WebGLRenderer extends System { constructor(canvas) { super(); this.canvas = canvas; } added(engine){ // Set up canvas const { canvas } = this; window.addEventListener('resize', () => this._resizeCanvas(canvas)); // Set up WebGL const gl = canvas.getContext('webgl', { preserveDrawingBuffer: true }); this.gl = gl; gl.clearColor(0.05882, 0.14902, 0.12157, 1); gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); this.colorBuffer = gl.createBuffer(); const shaderProgram = initializeShaderProgram( gl, internals.kVertexShader, internals.kFragmentShader ); this.programInfo = { program: shaderProgram, attribLocations: { vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'), vertexColor: gl.getAttribLocation(shaderProgram, 'aColor') }, uniformLocations: { projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'), viewMatrix: gl.getUniformLocation(shaderProgram, 'uViewMatrix') } }; this.buffers = initializeBuffers(gl); this._resizeCanvas(canvas); this.points = engine.getNodes(Drawable); this.positions = []; this.colors = []; this.configurations = engine.getNodes(Configurable); this.cameras = engine.getNodes(Cameras); } removed(engine){ delete this.gl; delete this.points; delete this.colorBuffer; delete this.buffers; delete this.positions; delete this.colors; delete this.configurations; delete this.cameras; } update(){ const {gl, programInfo, buffers} = this; gl.useProgram(programInfo.program); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); const fieldOfView = internals.kFieldOfView * Math.PI / 180; const aspectRatio = internals.kWidthRatio / internals.kHeightRatio; const projectionMatrix = mat4.create(); mat4.perspective( projectionMatrix, fieldOfView, aspectRatio, internals.kNearLimit, internals.kFarLimit ); gl.uniformMatrix4fv( programInfo.uniformLocations.projectionMatrix, false, projectionMatrix ); // We only support one camera for now. const camera = this.cameras.nodes[0]; if (camera != undefined) { const eye = vec3.fromValues(camera.position.x, camera.position.y, camera.position.z); const center = vec3.fromValues(0, 0, 0); const up = vec3.fromValues(camera.up.x, camera.up.y, camera.up.z); const viewMatrix = mat4.create(); mat4.lookAt(viewMatrix, eye, center, up); gl.uniformMatrix4fv( programInfo.uniformLocations.viewMatrix, false, viewMatrix ); } let i = 0; for (const point of this.points) { this.positions[i] = this.positions[i] || []; this.positions[i].push(point.position.x, point.position.y, point.position.z, 1); this.positions[i].push( point.position.prevX || point.position.x, point.position.prevY || point.position.y, point.position.prevZ || point.position.z, 1 ); point.position.prevX = point.position.x; point.position.prevY = point.position.y; point.position.prevZ = point.position.z; this.colors[i] = this.colors[i] || []; this.colors[i].push( 0.5 + point.position.z / 4 + point.position.x / 4, 0.5 + point.position.z / 4 + point.position.y / 4, 0.75 + point.position.z / 4, 1, 0.5 + point.position.prevZ / 4 + point.position.prevX / 4, 0.5 + point.position.prevZ / 4 + point.position.prevY / 4, 0.75 + point.position.prevZ / 4,1); ++i; } gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); const colors = this.colors.flat(); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); { const numberOfComponents = 4; const type = gl.FLOAT; const normalize = false; const stride = 0; const offset = 0; gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); gl.vertexAttribPointer( programInfo.attribLocations.vertexColor, numberOfComponents, type, normalize, stride, offset ); gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor); } gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position); const positions = this.positions.flat(); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); { const numberOfComponents = 4; const type = gl.FLOAT; const normalize = false; const stride = 0; const offset = 0; gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position); gl.vertexAttribPointer( programInfo.attribLocations.vertexPosition, numberOfComponents, type, normalize, stride, offset ); gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition); } { gl.lineWidth(2); gl.drawArrays(gl.LINES, 0, positions.length / 4); } this._cullLines(); } _resizeCanvas(canvas) { let width = window.innerWidth; let height = Math.round(width * internals.kHeightRatio / internals.kWidthRatio); if (window.innerHeight < height) { height = window.innerHeight; width = Math.round(height * internals.kWidthRatio / internals.kHeightRatio); } canvas.style.width = `${width}px`; canvas.style.height = `${height}px`; canvas.width = internals.kTargetVerticalResolution * internals.kWidthRatio; canvas.height = internals.kTargetVerticalResolution; this.gl.viewport(0, 0, canvas.width, canvas.height); } _cullLines() { const lineLength = this.configurations.nodes[0]?.configuration.lineLength || internals.kDefaultLineLength; for (const [i, position] of Object.entries(this.positions)) { this.positions[i] = position.slice(-lineLength * 8); } for (const [i, color] of Object.entries(this.colors)) { this.colors[i] = color.slice(-lineLength * 8); } } };