import { mat4 } from 'gl-matrix' ; import { assert } from '../auxiliaries' ; import { GLclampf4 , GLfloat2 } from '../tuples' ; import { Camera } from '../camera' ; import { ChangeLookup } from '../changelookup' ; import { Context } from '../context' ; import { Framebuffer } from '../framebuffer' ; import { Geometry } from '../geometry' ; import { Initializable } from '../initializable' ; import { Program } from '../program' ; import { GeometryComponent } from './geometrycomponent' ; import { Material } from './material' ; import { SceneNode } from './scenenode' ; import { SceneRenderPass } from './scenerenderpass' ; import { TransformComponent } from './transformcomponent' ; /**
 * This class renders a SceneNode hierarchy. It uses one single program for rendering the whole scene.
 * If different programs are necessary to render a scene, multiple SceneNodes should be used for each
 * program that is used.
 * This renderpass calls callbacks such as `updateModelTransform`, which have to be set by the renderer
 * using this renderpass.
 */ export class ForwardSceneRenderPass extends SceneRenderPass { /**
     * Alterable auxiliary object for tracking changes on render pass inputs and lazy updates.
     */ protected readonly _altered = Object . assign ( new ChangeLookup ( ) , { any : false , camera : false , } ) ; see target protected _target : Framebuffer ; see camera protected _camera : Camera ; see ndcOffset protected _ndcOffset : GLfloat2 ; see clearColor protected _clearColor : GLclampf4 ; see program protected _program : Program ; /**
     * These maps are used to map from a material to all geometries using this material.
     * Alongside the geometry a transform is saved, that is generated during a preprocessing traverse.
     */ protected _opaqueGeometryMap : Map < Material , Array < [ Geometry , mat4 ] > > ; protected _transparentGeometryMap : Map < Material , Array < [ Geometry , mat4 ] > > ; updateModelTransform : ( matrix : mat4 ) => void ; updateViewProjectionTransform : ( matrix : mat4 ) => void ; bindMaterial : ( material : Material ) => void ; bindGeometry : ( geometry : Geometry ) => void ; bindUniforms : ( ) => void ; param context todo constructor ( context ) { super ( ) ; this . _context = context ; this . _opaqueGeometryMap = new Map ( ) ; this . _transparentGeometryMap = new Map ( ) ; } /**
     * Sort all geometries by their material and save their transform given by a scene traversal.
     * With this information, rendering can be sped up later on by avoiding material changes
     * during rendering of the scene.
     */ protected preprocessScene ( ) { assert ( this . _scene !== undefined , 'Scene was undefined during preprocessing.' ) ; if ( this . _scene === undefined ) { return ; } this . _opaqueGeometryMap . clear ( ) ; this . _transparentGeometryMap . clear ( ) ; this . preprocessNode ( this . _scene ! , mat4 . create ( ) ) ; } /**
     * Handle a single node during preprocessing. Each GeometryComponent of the node will be added
     * to the preprocessing maps.
     * Afterwards all children of the node will also be processed recursively.
     */ protected preprocessNode ( node , transform ) { const nodeTransform = mat4 . clone ( transform ) ; const transformComponents = node . componentsOfType ( 'TransformComponent' ) ; assert ( transformComponents . length <= 1 , `SceneNode can not have more than one transform component` ) ; if ( transformComponents . length === 1 ) { const transformComponent = transformComponents [ 0 ] as TransformComponent ; mat4 . mul ( nodeTransform , nodeTransform , transformComponent . transform ) ; } const geometryComponents = node . componentsOfType ( 'GeometryComponent' ) ; for ( const geometryComponent of geometryComponents ) { const currentComponent = geometryComponent as GeometryComponent ; const material = currentComponent . material ; const geometry = currentComponent . geometry ; if ( material . isTransparent ) { let map = this . _transparentGeometryMap . get ( material ) ; if ( map === undefined ) { map = [ ] ; } map . push ( [ geometry , nodeTransform ] ) ; this . _transparentGeometryMap . set ( material , map ) ; } else { let map = this . _opaqueGeometryMap . get ( material ) ; if ( map === undefined ) { map = [ ] ; } map . push ( [ geometry , nodeTransform ] ) ; this . _opaqueGeometryMap . set ( material , map ) ; } } if ( node . nodes === undefined ) { return ; } for ( const child of node . nodes ) { this . preprocessNode ( child , nodeTransform ) ; } } /**
     * Render a preprocessed map, where geometries are already sorted by material.
     * Thus, each material only needs to be bound once.
     */ protected renderGeometryMap ( map ) { for ( const material of Array . from ( map . keys ( ) ) ) { this . bindMaterial ( material ) ; const geometryTuples = map . get ( material ) ! ; for ( const [ geometry , transform ] of geometryTuples ) { geometry . bind ( ) ; if ( this . bindGeometry !== undefined ) { this . bindGeometry ( geometry ) ; } this . updateModelTransform ( transform ) ; geometry . draw ( ) ; geometry . unbind ( ) ; } } } @ Initializable . initialize ( ) initialize ( ) { return true ; } @ Initializable . uninitialize ( ) uninitialize ( ) { } prepare ( ) { } param override @ Initializable . assert_initialized ( ) update ( override = false ) { } /**
     * Triggers rendering a frame of the given hierarchy. All nodes in the hierarchy will be visited recursively
     * and rendered. If nodes contain transformations, they are applied and used for the whole subtree.
     */ @ Initializable . assert_initialized ( ) frame ( ) { assert ( this . _target && this . _target . valid , `valid target expected` ) ; assert ( this . _program && this . _program . valid , `valid program expected` ) ; assert ( this . updateModelTransform !== undefined , `Model transform function needs to be initialized.` ) ; assert ( this . updateViewProjectionTransform !== undefined , `View Projection transform function needs to be initialized.` ) ; assert ( this . bindMaterial !== undefined , `Material binding function needs to be initialized.` ) ; if ( this . _scene === undefined ) { return ; } const gl = this . _context . gl ; gl . enable ( gl . DEPTH_TEST ) ; const size = this . _target . size ; gl . viewport ( 0 , 0 , size [ 0 ] , size [ 1 ] ) ; const c = this . _clearColor ; gl . clearColor ( c [ 0 ] , c [ 1 ] , c [ 2 ] , c [ 3 ] ) ; this . _target . clear ( gl . COLOR_BUFFER_BIT | gl . DEPTH_BUFFER_BIT , true , false ) ; this . _program . bind ( ) ; if ( this . bindUniforms !== undefined ) { this . bindUniforms ( ) ; } this . updateViewProjectionTransform ( this . _camera . viewProjection ) ; this . drawCalls ( ) ; this . _program . unbind ( ) ; gl . disable ( gl . BLEND ) ; } /**
     * Encapsulates the draw calls made to webgl. This is useful if state should not be changed before rendering,
     * e.g. for shadow mapping.
     */ @ Initializable . assert_initialized ( ) drawCalls ( renderTransparentMaterials = true ) { if ( this . _scene === undefined ) { return ; } /**
         * Render geometries by material.
         * First render opaque materials, then transparent ones.
         */ this . renderGeometryMap ( this . _opaqueGeometryMap ) ; if ( renderTransparentMaterials ) { this . renderGeometryMap ( this . _transparentGeometryMap ) ; } } param target set target ( target ) { this . assertInitialized ( ) ; this . _target = target ; } param offset set ndcOffset ( offset ) { this . assertInitialized ( ) ; this . _ndcOffset = offset ; } /**
     * The camera's viewProjection is used for 3D label placement calculation.
     */ set camera ( camera ) { this . assertInitialized ( ) ; if ( this . _camera === camera ) { return ; } this . _camera = camera ; this . _altered . alter ( 'camera' ) ; } /**
     * Sets the clear color for rendering.
     */ set clearColor ( color ) { this . _clearColor = color ; } set program ( program ) { this . _program = program ; } }