import { Material } from "../Material" ; import { vec3 , quat } from "gl-matrix" ; import { Renderer , IUniformBindingEntry } from "../Renderer" ; import { GetShaderAttributeComponentSize , IBindGroupResource , RenderPipelineGenerator } from "../Material/RenderPipelineGenerator" ; import { IRayTriangleIntersection , Ray } from "../Ray" ; import { Container , IContainerOptions } from "../Container" ; import { AABB , IAABBBounding } from "../AABB" ; import { Buffer } from "../Buffer" ; import { Sampler } from "../Sampler" ; import { Texture } from "../Texture" ; export * from "./StaticMesh" ; export interface IMeshOptions extends IContainerOptions { name ? : string ; material : Material ; translation ? : vec3 ; rotation ? : quat ; scale ? : vec3 ; indices ? : ArrayBufferView ; attributes ? : ArrayBufferView ; freeAfterUpload ? : boolean ; } export const MESH_DEFAULT_OPTIONS = { name : null , material : null , translation : null , rotation : null , scale : null , indices : null , attributes : null , freeAfterUpload : false , } ; export class Mesh extends Container { private _material : Material = null ; private _materialRebuildCount : number = 0 ; private _indices : ArrayBufferView = null ; private _attributes : ArrayBufferView = null ; private _indexCount : number = 0 ; private _vertexBoundings : AABB = null ; private _bindGroup : GPUBindGroup = null ; private _bindGroupResources : IBindGroupResource [ ] = [ ] ; private _uniformBindingQueue : IUniformBindingEntry [ ] = [ ] ; private _uniformBindings : Map < string , ( Buffer | Sampler | Texture ) > = new Map ( ) ; private _indexBuffer : GPUBuffer = null ; private _attributeBuffer : GPUBuffer = null ; private _freeAfterUpload : boolean = false ; private _needsRebuildState : boolean = false ; param options public constructor ( options ? ) { super ( options ) ; options = Object . assign ( { } , MESH_DEFAULT_OPTIONS , options ) ; this . setName ( options . name ) ; this . setMaterial ( options . material ) ; this . _indices = options . indices ; this . _attributes = options . attributes ; this . _freeAfterUpload = options . freeAfterUpload ; if ( this . doTransformAutoUpdate ( ) ) { this . updateTransform ( ) ; this . updateBoundings ( ) ; } } /**
     * The mesh's assigned material
     */ public getMaterial ( ) { return this . _material ; } param value public setMaterial ( value ) { this . _material = value ; this . _triggerRebuild ( ) ; } /**
     * The mesh's count
     */ public getIndexCount ( ) { return this . _indexCount ; } /**
     * The object-space vertex boundings of the mesh
     */ public getVertexBoundings ( ) { return this . _vertexBoundings ; } param ray public intersectRay ( ray ) { if ( this . isDestroyed ( ) || this . _freeAfterUpload ) return null ; const indices = this . _indices as Uint32Array ; const attributes = this . _attributes as Float32Array ; const material = this . getMaterial ( ) ; const attributeLayout = material . getAttributes ( ) ; const mModel = this . getModelMatrix ( ) ; const positionLayout = attributeLayout . find ( l => l . name === "Position" ) ; const positionByteOffset = positionLayout . byteOffset ; const positionAttributeSize = GetShaderAttributeComponentSize ( positionLayout . type ) ; const normalLayout = attributeLayout . find ( l => l . name === "Normal" ) ; const normalByteOffset = normalLayout . byteOffset ; const normalAttributeSize = GetShaderAttributeComponentSize ( normalLayout . type ) ; const attributeView = new DataView ( attributes . buffer , attributes . byteOffset , attributes . byteLength ) ; const attributeByteStride = material . getAttributeLayoutByteStride ( ) ; const v0 = vec3 . create ( ) ; const v1 = vec3 . create ( ) ; const v2 = vec3 . create ( ) ; let out = null ; for ( let ii = 0 ; ii < indices . length / 3 ; ++ ii ) { const i0 = ( ( indices [ ( ii * 3 ) + 0 ] ) ) * attributeByteStride ; const i1 = ( ( indices [ ( ii * 3 ) + 1 ] ) ) * attributeByteStride ; const i2 = ( ( indices [ ( ii * 3 ) + 2 ] ) ) * attributeByteStride ; v0 [ 0 ] = attributeView . getFloat32 ( positionByteOffset + i0 + ( positionAttributeSize * 0 ) , true ) ; v0 [ 1 ] = attributeView . getFloat32 ( positionByteOffset + i0 + ( positionAttributeSize * 1 ) , true ) ; v0 [ 2 ] = attributeView . getFloat32 ( positionByteOffset + i0 + ( positionAttributeSize * 2 ) , true ) ; v1 [ 0 ] = attributeView . getFloat32 ( positionByteOffset + i1 + ( positionAttributeSize * 0 ) , true ) ; v1 [ 1 ] = attributeView . getFloat32 ( positionByteOffset + i1 + ( positionAttributeSize * 1 ) , true ) ; v1 [ 2 ] = attributeView . getFloat32 ( positionByteOffset + i1 + ( positionAttributeSize * 2 ) , true ) ; v2 [ 0 ] = attributeView . getFloat32 ( positionByteOffset + i2 + ( positionAttributeSize * 0 ) , true ) ; v2 [ 1 ] = attributeView . getFloat32 ( positionByteOffset + i2 + ( positionAttributeSize * 1 ) , true ) ; v2 [ 2 ] = attributeView . getFloat32 ( positionByteOffset + i2 + ( positionAttributeSize * 2 ) , true ) ; vec3 . transformMat4 ( v0 , v0 , mModel ) ; vec3 . transformMat4 ( v1 , v1 , mModel ) ; vec3 . transformMat4 ( v2 , v2 , mModel ) ; const intersection = ray . intersectsTriangle ( v0 , v1 , v2 ) ; if ( intersection !== null ) { const normal = vec3 . create ( ) ; normal [ 0 ] = attributeView . getFloat32 ( normalByteOffset + i0 + ( normalAttributeSize * 0 ) , true ) ; normal [ 1 ] = attributeView . getFloat32 ( normalByteOffset + i0 + ( normalAttributeSize * 1 ) , true ) ; normal [ 2 ] = attributeView . getFloat32 ( normalByteOffset + i0 + ( normalAttributeSize * 2 ) , true ) ; intersection . normal = normal ; if ( out !== null ) { if ( intersection . t < out . t ) { out = intersection ; } } else { out = intersection ; } } } return out ; } param name param data public bindUniform ( name , data ) { if ( this . isDestroyed ( ) ) return ; const uniform = this . getMaterial ( ) . getUniformByName ( name ) ; if ( uniform === null ) throw new ReferenceError ( `Failed to resolve material uniform for '${ name }'` ) ; if ( uniform . isShared ) throw new Error ( `Uniform '${ name }' is declared as shared and must be accessed through its material` ) ; if ( data !== null ) this . _uniformBindingQueue . push ( { id : uniform . id , data } ) ; const bindings = this . _uniformBindings ; if ( bindings . has ( name ) && bindings . get ( name ) !== data ) { this . _triggerRebuild ( ) ; } bindings . set ( name , data ) ; } param data public updateIndices ( data ) { this . _indices = data ; this . _indexBuffer = null ; } param data public updateAttributes ( data ) { this . _attributes = data ; this . _attributeBuffer = null ; } param renderer public build ( renderer ) { if ( this . isDestroyed ( ) ) return ; if ( ! this . _needsRebuild ( ) ) return ; const material = this . getMaterial ( ) ; material . build ( renderer ) ; this . _bindGroupResources = RenderPipelineGenerator . generateBindGroupResources ( material , false , renderer . getDevice ( ) ) ; renderer . processUniformBindingQueue ( this . _uniformBindingQueue , this . _bindGroupResources ) ; this . _bindGroup = RenderPipelineGenerator . generateBindGroup ( material , this . _bindGroupResources , renderer . getDevice ( ) ) ; this . _resetRebuild ( ) ; this . update ( renderer ) ; } param renderer public update ( renderer ) { super . update ( renderer ) ; if ( this . isDestroyed ( ) ) return ; const material = this . getMaterial ( ) ; if ( material . getRebuildCount ( ) !== this . _materialRebuildCount ) { this . _triggerRebuild ( ) ; this . _materialRebuildCount = material . getRebuildCount ( ) ; } this . build ( renderer ) ; material ?. update ( renderer ) ; renderer . processUniformBindingQueue ( this . _uniformBindingQueue , this . _bindGroupResources ) ; renderer . processUniformBindings ( this . _uniformBindings ) ; if ( this . _indices !== null ) { if ( this . _indexBuffer === null ) { this . _indexBuffer = renderer . getDevice ( ) . createBuffer ( { size : this . _indices . byteLength , usage : GPUBufferUsage . INDEX | GPUBufferUsage . COPY_DST } ) ; this . _indexCount = ( this . _indices as Uint32Array ) . length ; renderer . getQueueCommander ( ) . transferDataToBuffer ( this . _indexBuffer , this . _indices , 0x0 ) ; if ( this . _freeAfterUpload ) this . _indices = null ; } } if ( this . _attributes !== null ) { if ( this . _attributeBuffer === null ) { this . _attributeBuffer = renderer . getDevice ( ) . createBuffer ( { size : this . _attributes . byteLength , usage : GPUBufferUsage . VERTEX | GPUBufferUsage . COPY_DST } ) ; renderer . getQueueCommander ( ) . transferDataToBuffer ( this . _attributeBuffer , this . _attributes , 0x0 ) ; if ( this . _freeAfterUpload ) this . _attributes = null ; } } } param encoder public render ( encoder ) { super . render ( encoder ) ; if ( this . isDestroyed ( ) || ! this . isVisible ( ) ) return ; const material = this . getMaterial ( ) ; if ( material ?. getRenderPipeline ( ) !== null ) { const { pipeline } = material . getRenderPipeline ( ) ; encoder . setPipeline ( pipeline ) ; encoder . setBindGroup ( 0 , this . _bindGroup ) ; encoder . setVertexBuffer ( 0 , this . _attributeBuffer ) ; encoder . setIndexBuffer ( this . _indexBuffer , "uint32" , 0x0 , this . getIndexCount ( ) * Uint32Array . BYTES_PER_ELEMENT ) ; encoder . drawIndexed ( this . getIndexCount ( ) , 1 , 0 , 0 , 0 ) ; } } /**
     * Compute the vertex boundings of the mesh
     */ public computeVertexBoundings ( ) { if ( this . isDestroyed ( ) || this . _freeAfterUpload ) { return { min : vec3 . create ( ) , max : vec3 . create ( ) } ; } const indices = this . _indices as Uint32Array ; const attributes = this . _attributes as Float32Array ; const material = this . getMaterial ( ) ; const attributeLayout = material . getAttributes ( ) ; const positionLayout = attributeLayout . find ( l => l . name === "Position" ) ; const positionByteOffset = positionLayout . byteOffset ; const attributeView = new DataView ( attributes . buffer , attributes . byteOffset , attributes . byteLength ) ; const attributeComponentSize = GetShaderAttributeComponentSize ( positionLayout . type ) ; const attributeByteStride = material . getAttributeLayoutByteStride ( ) ; const vertex = vec3 . create ( ) ; const min = vec3 . fromValues ( Infinity , Infinity , Infinity ) ; const max = vec3 . fromValues ( - Infinity , - Infinity , - Infinity ) ; for ( let ii = 0 ; ii < indices . length ; ++ ii ) { const index = positionByteOffset + ( indices [ ( ii * 3 ) ] * attributeByteStride ) ; vertex [ 0 ] = attributeView . getFloat32 ( index + ( attributeComponentSize * 0 ) , true ) ; vertex [ 1 ] = attributeView . getFloat32 ( index + ( attributeComponentSize * 1 ) , true ) ; vertex [ 2 ] = attributeView . getFloat32 ( index + ( attributeComponentSize * 2 ) , true ) ; if ( vertex [ 0 ] < min [ 0 ] ) min [ 0 ] = vertex [ 0 ] ; if ( vertex [ 1 ] < min [ 1 ] ) min [ 1 ] = vertex [ 1 ] ; if ( vertex [ 2 ] < min [ 2 ] ) min [ 2 ] = vertex [ 2 ] ; if ( vertex [ 0 ] > max [ 0 ] ) max [ 0 ] = vertex [ 0 ] ; if ( vertex [ 1 ] > max [ 1 ] ) max [ 1 ] = vertex [ 1 ] ; if ( vertex [ 2 ] > max [ 2 ] ) max [ 2 ] = vertex [ 2 ] ; } return { min , max } ; } /**
     * Compute the boundings of this container
     */ public computeBoundings ( ) { if ( this . isDestroyed ( ) || this . _freeAfterUpload ) { return { min : vec3 . create ( ) , max : vec3 . create ( ) } ; } if ( this . getVertexBoundings ( ) === null ) { const vertexBoundings = new AABB ( ) ; const { min , max } = this . computeVertexBoundings ( ) ; vec3 . copy ( vertexBoundings . getMin ( ) , min ) ; vec3 . copy ( vertexBoundings . getMax ( ) , max ) ; this . _vertexBoundings = vertexBoundings ; } const vertexBoundings = this . getVertexBoundings ( ) ; const min = vec3 . copy ( vec3 . create ( ) , vertexBoundings . getMin ( ) ) ; const max = vec3 . copy ( vec3 . create ( ) , vertexBoundings . getMax ( ) ) ; const mModel = this . getModelMatrix ( ) ; vec3 . transformMat4 ( min , min , mModel ) ; vec3 . transformMat4 ( max , max , mModel ) ; return { min , max } ; } /**
     * Update the boundings of this container
     */ public updateBoundings ( ) { const boundings = this . getBoundings ( ) ; const vertexBoundings = this . computeBoundings ( ) ; const min = boundings . getMin ( ) ; const max = boundings . getMax ( ) ; vec3 . copy ( min , vertexBoundings . min ) ; vec3 . copy ( max , vertexBoundings . max ) ; { const childBoundings = super . computeBoundings ( ) ; const childMin = childBoundings . min ; const childMax = childBoundings . max ; if ( childMin [ 0 ] < min [ 0 ] ) min [ 0 ] = childMin [ 0 ] ; if ( childMin [ 1 ] < min [ 1 ] ) min [ 1 ] = childMin [ 1 ] ; if ( childMin [ 2 ] < min [ 2 ] ) min [ 2 ] = childMin [ 2 ] ; if ( childMax [ 0 ] > max [ 0 ] ) max [ 0 ] = childMax [ 0 ] ; if ( childMax [ 1 ] > max [ 1 ] ) max [ 1 ] = childMax [ 1 ] ; if ( childMax [ 2 ] > max [ 2 ] ) max [ 2 ] = childMax [ 2 ] ; } } /**
     * Update the transforms of this container
     */ public updateTransform ( ) { if ( ! this . _material ) return ; super . updateTransform ( ) ; } /**
     * Destroy this Object
     */ public destroy ( ) { super . destroy ( ) ; this . _material = null ; this . _indices = null ; this . _attributes = null ; this . _bindGroup = null ; this . _bindGroupResources = null ; this . _uniformBindingQueue = null ; this . _uniformBindings = null ; this . _indexBuffer = null ; this . _attributeBuffer = null ; this . _vertexBoundings = null ; } /**
     * Determines if the mesh has to be rebuilt
     */ private _needsRebuild ( ) { return this . _needsRebuildState ; } /**
     * Triggers a rebuild of the mesh
     */ private _triggerRebuild ( ) { this . _needsRebuildState = true ; } /**
     * Disables the rebuild trigger
     */ private _resetRebuild ( ) { this . _needsRebuildState = false ; } }