import Shader from "../../framework/WebGpu.js" import Matrix4 from "../../framework/Matrix4.js" import Vector3 from "../../framework/Vector3.js" import Camera from "../../framework/Camera.js"; import EventManager from "../../framework/eventManager.js"; import ShaderInpector from "../../framework/ShaderInpector.js"; export class ParticleSimulation { canvas; device; camera; useLocalSort = true; eventManager = new EventManager(); frameCount = 0; setCanvas( canvas ) { this.canvas = canvas; this.eventManager.setCanvas( canvas ); } async loadImagesFromFile( filePaths ) { var imageBitmaps = new Array(); for (var i = 0; i < filePaths.length; i++) { var filePath = filePaths[i] const imageBitmap = await this.loadImageBitmap( filePath ); imageBitmaps.push( imageBitmap ); } return this.createTextureArray(this.device, 512, 512, imageBitmaps.length, imageBitmaps); } async createTextureArray(device, width, height, layerCount, imageBitmaps) { const texture = device.createTexture({ size: { width: width, height: height, depthOrArrayLayers: layerCount, }, format: "rgba8unorm", usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, }); for (let layer = 0; layer < layerCount; layer++) { const imageBitmap = imageBitmaps[layer]; device.queue.copyExternalImageToTexture( { source: imageBitmap }, { texture: texture, origin: { x: 0, y: 0, z: layer } }, [ imageBitmap.width, imageBitmap.height, 1 ] ); } const textureView = texture.createView({ dimension: "2d-array", baseArrayLayer: 0, arrayLayerCount: layerCount, }); return { texture, textureView }; } createTextureFromImageBitmap( device, imageBitmap ) { const texture = device.createTexture( { size: [ imageBitmap.width, imageBitmap.height, 1 ], format: 'rgba8unorm', usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT } ); device.queue.copyExternalImageToTexture( { source: imageBitmap }, { texture: texture }, [ imageBitmap.width, imageBitmap.height, 1 ] ); return texture; } async loadImageBitmap(url) { const response = await fetch( url ); const blob = await response.blob(); const imageBitmap = await createImageBitmap( blob ); return imageBitmap; } async loadTexture( url ) { const imageBitmap = await this.loadImageBitmap( url ); const texture = this.createTextureFromImageBitmap( this.device, imageBitmap ); return texture; } createPlane(width, height, repeatU, repeatV) { const vertices = new Float32Array( 18 ); // 6 vertices (2 triangles) * 3 coords const normals = new Float32Array( 18 ); // same count as vertices const uvs = new Float32Array( 12 ); // 6 vertices * 2 coords // Positions (two triangles forming a plane on XY plane at z=0) // Large plane from (-width/2, -height/2) to (width/2, height/2) vertices.set([ -width / 2, -height / 2, 0, width / 2, -height / 2, 0, -width / 2, height / 2, 0, -width / 2, height / 2, 0, width / 2, -height / 2, 0, width / 2, height / 2, 0 ]); // Normals all pointing +Z for (let i = 0; i < 6; i++) { normals[i * 3 + 0] = 0; normals[i * 3 + 1] = 0; normals[i * 3 + 2] = 1; } // UVs scaled by repeatU, repeatV to repeat texture over the plane uvs.set([ 0, 0, repeatU, 0, 0, repeatV, 0, repeatV, repeatU, 0, repeatU, repeatV ]); return { vertices, normals, uvs }; } async setup( offscreenCanvas, width, height ) { offscreenCanvas.width = width; offscreenCanvas.height = height; this.canvas = offscreenCanvas; const context = offscreenCanvas.getContext("webgpu"); this.camera = new Camera( [0, 0, 1115], [0, -.3, 0], [0, 1, 0] ); this.eventManager.setup( offscreenCanvas, this.camera ); const adapter = await self.navigator.gpu.requestAdapter(); if ( !adapter ) { throw new Error("Failed to get GPU adapter"); } const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); this.device = await adapter.requestDevice(); this.renderShader = new Shader( this.device ); context.configure({ device: this.device, format: presentationFormat, alphaMode: "opaque" }); const instanceCount = 100; const instancePositions = new Float32Array(instanceCount * 4); // vec4 per instance for (let i = 0; i < instanceCount; i++) { const x = (i % 10) * 300.0; const y = Math.floor(i / 10) * 350.0; instancePositions[i * 4 + 0] = x - 1000; instancePositions[i * 4 + 1] = 0; instancePositions[i * 4 + 2] = y - 1000; instancePositions[i * 4 + 3] = 0; } var model = await this.loadJSON("demo.json"); var mesh = model.meshes[0]; this.renderShader.setCanvas( this.canvas ); this.renderShader.topology = "triangle-list"; await this.renderShader.setup( "../../shaders/triangle-list-texture-array.wgsl"); this.renderShader.setAttribute( "position", mesh.vertices ); this.renderShader.setAttribute( "normal", mesh.normals ); this.renderShader.setAttribute( "uv", mesh.texturecoords[0] ); var faces = mesh.faces; const indexArray = new Uint32Array(faces.length * 3); for (let i = 0; i < faces.length; i++) { indexArray[i * 3 + 0] = faces[i][0]; indexArray[i * 3 + 1] = faces[i][1]; indexArray[i * 3 + 2] = faces[i][2]; } this.renderShader.setIndices( indexArray ); this.renderShader.setCanvas( this.canvas ); this.renderShader.topology = "triangle-list"; await this.renderShader.setup( "../../shaders/triangle-list-texture.wgsl"); /* const { vertices, normals, uvs } = this.createPlane( 1000, 1000, 4, 4 ); this.renderShader.setAttribute( "position", vertices ); this.renderShader.setAttribute( "normal", normals ); this.renderShader.setAttribute( "uv", uvs ); this.vertexCount = vertices.length / 3 */ this.renderShader.setVariable( "instancePositions", instancePositions ); //var texture = await this.loadTexture("./textures/defaultnouvs.png"); const sampler = this.device.createSampler({ minFilter: 'linear', magFilter: 'linear', mipmapFilter: 'linear', addressModeU: 'repeat', addressModeV: 'repeat', }); this.renderShader.setVariable( "mySampler", sampler ); //this.renderShader.setVariable( "myTextureArray", texture ); //var myTextureArray = this.loadImagesFromFile( ["./textures/defaultnouvs.png", "./textures/0_floorTiles_diff.png"] ); //this.renderShader.setVariable( "myTextureArray", texture ); this.render(); } updateTimeDelta() { const now = performance.now(); this.deltaTimeValue = ( now - this.lastFrameTime ) / 1000; this.lastFrameTime = now; } async render() { this.updateTimeDelta(); const viewMatrixData = this.camera.getViewMatrix(); const projectionMatrixData = Matrix4.createProjectionMatrix( this.camera, this.canvas ) const viewProjectionMatrix = Matrix4.multiply( projectionMatrixData, viewMatrixData ); const cameraWorldMatrix = Matrix4.invert( viewMatrixData ); const cameraPosition = Matrix4.getColumn( cameraWorldMatrix, 3 ); this.renderShader.setVariable( "viewProjectionMatrix", viewProjectionMatrix ); this.renderShader.setVariable( "cameraPosition", cameraPosition ); this.renderShader.renderToCanvas( this.vertexCount, 74, 0 ); this.frameCount++; requestAnimationFrame( this.render.bind( this ) ); } async loadJSON( pathName ) { const response = await fetch( pathName ); if ( !response.ok ){ throw new Error( `Failed to load shader: ${ pathName }` ); } return await response.json(); } }