import { BuffersGeometry } from './BuffersGeometry' ; import { Camera } from './Camera' ; import { CameraOrbitControls } from './CameraOrbitControls' ; import { GeometriesManager } from './GeometriesManager' ; import { Inspector } from './Inspector' ; import { mat4 , glMatrix } from 'gl-matrix' ; import { Program } from './Program' ; import { RendererState } from './RendererState' ; import { ShaderVariableType , DrawMode , LanguageVersion } from './_constants' ; import { TexturesManager } from './TexturesManager' ; import { UniformEditor } from './UniformEditor' ; import { UniformsCache , UniformState } from './UniformsCache' ; import { VertexAttribute , Uniform } from './ShaderInputs' ; import { VertexAttributeBuffer } from './Buffers' ; export interface Model { name : string ; attributes : { positions : boolean ; normals : boolean ; textureCoordinates : boolean ; } ; } export class Renderer { private canvas : HTMLCanvasElement ; private gl : WebGLRenderingContext ; private state : RendererState ; private inspector : Inspector ; private program : Program ; private lastCompilationTime : number ; private defaultUniforms : Map < string , { type : ShaderVariableType ; getValue : ( ) => mat4 | number ; } > ; private uniformsCache : UniformsCache ; private uniformsEditors : Map < string , UniformEditor > ; private camera : Camera ; private cameraOrbitControls : CameraOrbitControls ; private model : BuffersGeometry ; private geometriesManager : GeometriesManager ; private texturesManager : TexturesManager ; constructor ( canvas , onGeometriesLoaded , onTexturesLoaded ) { this . canvas = canvas ; this . gl = this . canvas . getContext ( 'webgl2' ) as WebGLRenderingContext ; this . state = new RendererState ( this . gl ) ; this . lastCompilationTime = performance . now ( ) ; this . geometriesManager = new GeometriesManager ( onGeometriesLoaded ) ; this . texturesManager = new TexturesManager ( this . gl , onTexturesLoaded ) ; this . program = new Program ( this . gl ) ; this . model = new BuffersGeometry ( this . gl ) ; this . camera = new Camera ( ) . setPosition ( 5 , 5 , 5 ) ; this . cameraOrbitControls = new CameraOrbitControls ( this . camera , this . canvas ) ; this . defaultUniforms = new Map ( [ [ 'modelMatrix' , { type : ShaderVariableType . mat4 , getValue : ( ) => this . model . modelMatrix } ] , [ 'modelViewMatrix' , { type : ShaderVariableType . mat4 , getValue : ( ) => this . model . modelViewMatrix } ] , [ 'normalMatrix' , { type : ShaderVariableType . mat4 , getValue : ( ) => this . model . normalMatrix } ] , [ 'viewMatrix' , { type : ShaderVariableType . mat4 , getValue : ( ) => this . camera . viewMatrix } ] , [ 'projectionMatrix' , { type : ShaderVariableType . mat4 , getValue : ( ) => this . camera . projectionMatrix } ] , [ 'time' , { type : ShaderVariableType . float , getValue : ( ) => ( performance . now ( ) - this . lastCompilationTime ) * 0.001 } ] ] ) ; this . uniformsCache = new UniformsCache ( ) ; this . inspector = new Inspector ( ) ; this . inspector . setDefaultUniforms ( this . defaultUniforms ) ; this . uniformsEditors = new Map ( ) ; this . setModel ( 'cube' ) ; } public setShaderProgram ( vertexSaderSource , fragmentShaderSource ) { const programWasUsable = this . program . usable ; this . program . setShadersSourceAndLink ( vertexSaderSource , fragmentShaderSource ) ; this . inspector . inspect ( this . program ) ; this . lastCompilationTime = performance . now ( ) ; if ( this . program . usable ) { this . program . use ( ) ; this . updateUniformsCacheAndEditors ( this . inspector . definedUniforms ) ; this . state . attributeBuffersNeedUpdate = true ; if ( ! programWasUsable ) { this . loop ( ) ; this . cameraOrbitControls . enabled = true ; } } else { this . cameraOrbitControls . enabled = false ; } return this . program . usable ; } public setLanguageVersion ( version ) { this . inspector . languageVersion = version ; this . inspector . inspect ( this . program ) ; } public setModel ( name ) { const geometry = this . geometriesManager . getGeometry ( name ) ; if ( geometry !== undefined ) { this . model . updateBuffersFromGeometry ( geometry ) ; this . inspector . updateAvailableVertexAttributesFromBuffers ( this . model . vertexAttributesBuffers ) ; this . inspector . checkVertexShaderAttributes ( ) ; this . state . drawBufferNeedsUpdate = true ; this . state . attributeBuffersNeedUpdate = true ; return true ; } else { return false ; } } public setTextureForUnit ( name , unit ) { return this . texturesManager . setTextureForUnit ( name , unit ) ; } public setWireframe ( enabled ) { this . state . drawMode = enabled ? DrawMode . Lines : DrawMode . Triangles ; this . state . drawBufferNeedsUpdate = true ; } public setAnimation ( enabled ) { this . state . animationEnabled = enabled ; } public setUniformsState ( uniforms ) { for ( let uniform of uniforms ) { const name = uniform . name ; const type = uniform . type ; const value = uniform . value instanceof Array ? new Float32Array ( uniform . value ) : uniform . value ; this . uniformsCache . add ( name , type , value ) ; } } public getErrorsAndWarnings ( ) { return this . inspector . getErrorsAndWarnings ( ) ; } public getUniformsEditors ( ) { return this . program . usable ? Array . from ( this . uniformsEditors . values ( ) ) : [ ] ; } public getUniformsState ( ) { const uniformsState = [ ] ; if ( this . program . usable ) { for ( let [ name , editor ] of this . uniformsEditors ) { const type = editor . type ; const rawValue = editor . getValue ( ) ; const value = rawValue instanceof Float32Array ? Array . from ( rawValue ) : rawValue ; uniformsState . push ( { name , type , value } ) ; } } return uniformsState ; } public getAvailableModels ( ) { const geometries = this . geometriesManager . getAvailableGeometries ( ) ; const models = [ ] ; for ( let [ name , geometry ] of geometries ) { models . push ( { name , attributes : { positions : geometry . hasPositions ( ) , normals : geometry . hasNormals ( ) , textureCoordinates : geometry . hasTextureCoordinates ( ) } } ) ; } return models ; } public getAvailableTextures ( ) { return this . texturesManager . getAvailableTextures ( ) ; } public getTexturesAssignedToTextureUnits ( ) { return this . texturesManager . getTexturesAssignedToTextureUnits ( ) ; } public getAvailableTextureUnitsCount ( ) { return this . texturesManager . getAvailableTextureUnitsCount ( ) ; } private loop ( ) { this . state . lastDrawTime = performance . now ( ) ; requestAnimationFrame ( now => this . render ( now ) ) ; } private render ( now ) { this . updateViewSize ( ) ; const timeSinceLastFrame = Math . max ( 0 , ( now - this . state . lastDrawTime ) * 0.001 ) ; if ( this . state . animationEnabled ) { const rotationSpeed = - 360 / 30 ; const rotationDelta = ( rotationSpeed * timeSinceLastFrame ) % 360 ; mat4 . rotateY ( this . model . modelMatrix , this . model . modelMatrix , glMatrix . toRadian ( rotationDelta ) ) ; } if ( this . program . usable ) { this . drawModel ( this . model ) ; this . state . lastDrawTime = performance . now ( ) ; requestAnimationFrame ( now => this . render ( now ) ) ; } } private drawModel ( model ) { this . state . clearBuffers ( ) ; this . updateMatrices ( model , this . camera ) ; this . updateUniformValues ( this . program . activeUniforms , this . uniformsCache ) ; if ( this . state . attributeBuffersNeedUpdate ) { this . updateVertexAttributes ( this . program . activeVertexAttributes , model . vertexAttributesBuffers ) ; this . state . attributeBuffersNeedUpdate = false ; } if ( this . state . drawBufferNeedsUpdate ) { this . updateDrawBuffer ( model ) ; this . state . drawBufferNeedsUpdate = false ; } this . state . drawBuffer . draw ( this . state . drawMode ) ; } private updateMatrices ( model , camera ) { mat4 . multiply ( model . modelViewMatrix , camera . viewMatrix , model . modelMatrix ) ; mat4 . invert ( model . normalMatrix , model . modelViewMatrix ) ; mat4 . transpose ( model . normalMatrix , model . normalMatrix ) ; } private updateUniformValues ( uniforms , cache ) { for ( const [ name , info ] of uniforms ) { const defaultUniform = this . defaultUniforms . get ( name ) ; const value = ( defaultUniform && defaultUniform . type === info . type ) ? defaultUniform . getValue ( ) : cache . get ( name , info . type ) ; if ( value !== undefined ) { info . setValue ( value ) ; } } } private updateVertexAttributes ( vertexAttributes , buffers ) { this . state . clearEnabledVertexAttributes ( ) ; for ( const [ name , info ] of vertexAttributes ) { const buffer = buffers . get ( name ) ; if ( buffer !== undefined ) { buffer . bind ( ) ; this . state . enableVertexAttribute ( info . location ) ; this . gl . vertexAttribPointer ( info . location , buffer . itemSize , buffer . itemType , false , 0 , 0 ) ; } } this . state . disableUnusedVertexAttributes ( ) ; } private updateDrawBuffer ( model ) { switch ( this . state . drawMode ) { case DrawMode . Lines : this . state . drawBuffer = model . linesIndexBuffer ; break ; case DrawMode . Triangles : this . state . drawBuffer = model . trianglesIndexBuffer ; break ; } this . state . drawBuffer . bind ( ) ; } private updateUniformsCacheAndEditors ( uniformsInfo ) { this . clearUniformsCacheAndEditors ( ) ; for ( const [ name , type ] of uniformsInfo ) { if ( type in ShaderVariableType && ! this . inspector . defaultUniforms . has ( name ) ) { const cachedValue = this . uniformsCache . add ( name , type as ShaderVariableType ) ; const uniformEditor = new UniformEditor ( name , type as ShaderVariableType , cachedValue ) ; this . uniformsEditors . set ( name , uniformEditor ) ; } } } private updateViewSize ( ) { const displayWidth = Math . floor ( this . canvas . clientWidth * window . devicePixelRatio ) ; const displayHeight = Math . floor ( this . canvas . clientHeight * window . devicePixelRatio ) ; if ( this . canvas . width !== displayWidth || this . canvas . height !== displayHeight ) { this . canvas . width = displayWidth ; this . canvas . height = displayHeight ; this . camera . setAspect ( displayWidth / displayHeight ) ; this . state . setViewport ( displayWidth , displayHeight ) ; } } private clearUniformsCacheAndEditors ( ) { this . uniformsCache . clear ( ) ; this . uniformsEditors . clear ( ) ; } }