UNPKG

matrix-engine-wgpu

Version:

obj sequence anim +HOTFIX raycast, webGPU powered pwa application. Crazy fast rendering with AmmoJS physics support. Simple raycaster hit object added.

399 lines (360 loc) 15.1 kB
import {vec3} from "wgpu-matrix"; import MEBall from "./engine/ball.js"; import MECube from './engine/cube.js'; import {ArcballCamera, WASDCamera} from "./engine/engine.js"; import MEMesh from "./engine/mesh.js"; import MEMeshObj from "./engine/mesh-obj.js"; import MatrixAmmo from "./physics/matrix-ammo.js"; import {LOG_WARN, genName, mb, scriptManager, urlQuery} from "./engine/utils.js"; import {MultiLang} from "./multilang/lang.js"; import {MatrixSounds} from "./sounds/sounds.js"; import {play} from "./engine/loader-obj.js"; export default class MatrixEngineWGPU { mainRenderBundle = []; rbContainer = []; frame = () => {}; entityHolder = []; entityArgPass = { loadOp: 'clear', storeOp: 'store', depthLoadOp: 'clear', depthStoreOp: 'store' } matrixAmmo = new MatrixAmmo(); matrixSounds = new MatrixSounds(); // The input handler constructor(options, callback) { // console.log('typeof options ', typeof options ) if(typeof options == 'undefined' || typeof options == "function") { this.options = { useSingleRenderPass: true, canvasSize: 'fullscreen', canvasId: 'canvas1', mainCameraParams: { type: 'WASD', responseCoef: 2000 }, clearColor: {r: 0.584, g: 0, b: 0.239, a: 1.0} } callback = options; } if(typeof options.clearColor === 'undefined') { options.clearColor = {r: 0.584, g: 0, b: 0.239, a: 1.0}; } if(typeof options.canvasId === 'undefined') { options.canvasId = 'canvas1'; } if(typeof options.mainCameraParams === 'undefined') { options.mainCameraParams = { type: 'WASD', responseCoef: 2000 } } this.options = options; this.mainCameraParams = options.mainCameraParams; const target = this.options.appendTo || document.body; var canvas = document.createElement('canvas') canvas.id = this.options.canvasId; if(this.options.canvasSize == 'fullscreen') { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } else { canvas.width = this.options.canvasSize.w; canvas.height = this.options.canvasSize.h; } target.append(canvas); // The camera types const initialCameraPosition = vec3.create(0, 0, 0); // console.log('passed : o.mainCameraParams.responseCoef ', o.mainCameraParams.responseCoef) this.mainCameraParams = { type: this.options.mainCameraParams.type, responseCoef: this.options.mainCameraParams.responseCoef } this.cameras = { arcball: new ArcballCamera({position: initialCameraPosition}), WASD: new WASDCamera({position: initialCameraPosition}), }; this.label = new MultiLang() if(urlQuery.lang != null) { this.label.loadMultilang(urlQuery.lang).then((r) => { this.label.get = r; }); } else { this.label.loadMultilang().then((r) => { this.label.get = r; }); } this.init({canvas, callback}) } init = async ({canvas, callback}) => { this.canvas = canvas; this.adapter = await navigator.gpu.requestAdapter(); this.device = await this.adapter.requestDevice({ extensions: ["ray_tracing"] }); // Maybe works in ssl with webworkers... // const adapterInfo = await this.adapter.requestAdapterInfo(); // var test = this.adapter.features() // console.log(adapterInfo.vendor); // console.log('test' + test); // console.log("FEATURES : " + this.adapter.features) this.context = canvas.getContext('webgpu'); const devicePixelRatio = window.devicePixelRatio; canvas.width = canvas.clientWidth * devicePixelRatio; canvas.height = canvas.clientHeight * devicePixelRatio; const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); this.context.configure({ device: this.device, format: presentationFormat, alphaMode: 'premultiplied', }); if(this.options.useSingleRenderPass == true) { this.frame = this.frameSinglePass; } else { this.frame = this.framePassPerObject; } this.run(callback) }; getSceneObjectByName(name) { return this.mainRenderBundle.find((sceneObject) => sceneObject.name === name ) } // Not in use for now addCube = (o) => { if(typeof o === 'undefined') { var o = { scale: 1, position: {x: 0, y: 0, z: -4}, texturesPaths: ['./res/textures/default.png'], rotation: {x: 0, y: 0, z: 0}, rotationSpeed: {x: 0, y: 0, z: 0}, entityArgPass: this.entityArgPass, cameras: this.cameras, mainCameraParams: this.mainCameraParams } } else { if(typeof o.position === 'undefined') {o.position = {x: 0, y: 0, z: -4}} if(typeof o.rotation === 'undefined') {o.rotation = {x: 0, y: 0, z: 0}} if(typeof o.rotationSpeed === 'undefined') {o.rotationSpeed = {x: 0, y: 0, z: 0}} if(typeof o.texturesPaths === 'undefined') {o.texturesPaths = ['./res/textures/default.png']} if(typeof o.scale === 'undefined') {o.scale = 1;} if(typeof o.mainCameraParams === 'undefined') {o.mainCameraParams = this.mainCameraParams} o.entityArgPass = this.entityArgPass; o.cameras = this.cameras; } if(typeof o.physics === 'undefined') { o.physics = { scale: [1, 1, 1], enabled: true, geometry: "Sphere", radius: o.scale, name: o.name, rotation: o.rotation } } if(typeof o.position !== 'undefined') {o.physics.position = o.position;} if(typeof o.physics.enabled === 'undefined') {o.physics.enabled = true} if(typeof o.physics.geometry === 'undefined') {o.physics.geometry = "Sphere"} if(typeof o.physics.radius === 'undefined') {o.physics.radius = o.scale} if(typeof o.physics.mass === 'undefined') {o.physics.mass = 1;} if(typeof o.physics.name === 'undefined') {o.physics.name = o.name;} if(typeof o.physics.scale === 'undefined') {o.physics.scale = o.scale;} if(typeof o.physics.rotation === 'undefined') {o.physics.rotation = o.rotation;} let myCube1 = new MECube(this.canvas, this.device, this.context, o) if(o.physics.enabled == true) { this.matrixAmmo.addPhysics(myCube1, o.physics); } this.mainRenderBundle.push(myCube1); } // Not in use for now addBall = (o) => { if(typeof o === 'undefined') { var o = { scale: 1, position: {x: 0, y: 0, z: -4}, texturesPaths: ['./res/textures/default.png'], rotation: {x: 0, y: 0, z: 0}, rotationSpeed: {x: 0, y: 0, z: 0}, entityArgPass: this.entityArgPass, cameras: this.cameras, mainCameraParams: this.mainCameraParams } } else { if(typeof o.position === 'undefined') {o.position = {x: 0, y: 0, z: -4}} if(typeof o.rotation === 'undefined') {o.rotation = {x: 0, y: 0, z: 0}} if(typeof o.rotationSpeed === 'undefined') {o.rotationSpeed = {x: 0, y: 0, z: 0}} if(typeof o.texturesPaths === 'undefined') {o.texturesPaths = ['./res/textures/default.png']} if(typeof o.mainCameraParams === 'undefined') {o.mainCameraParams = this.mainCameraParams} if(typeof o.scale === 'undefined') {o.scale = 1;} o.entityArgPass = this.entityArgPass; o.cameras = this.cameras; } if(typeof o.physics === 'undefined') { o.physics = { scale: [1, 1, 1], enabled: true, geometry: "Sphere", radius: o.scale, name: o.name, rotation: o.rotation } } if(typeof o.position !== 'undefined') {o.physics.position = o.position;} if(typeof o.physics.enabled === 'undefined') {o.physics.enabled = true} if(typeof o.physics.geometry === 'undefined') {o.physics.geometry = "Sphere"} if(typeof o.physics.radius === 'undefined') {o.physics.radius = o.scale} if(typeof o.physics.mass === 'undefined') {o.physics.mass = 1;} if(typeof o.physics.name === 'undefined') {o.physics.name = o.name;} if(typeof o.physics.scale === 'undefined') {o.physics.scale = o.scale;} if(typeof o.physics.rotation === 'undefined') {o.physics.rotation = o.rotation;} let myBall1 = new MEBall(this.canvas, this.device, this.context, o); if(o.physics.enabled == true) { this.matrixAmmo.addPhysics(myBall1, o.physics) } this.mainRenderBundle.push(myBall1); } // Not in use for now addMesh = (o) => { if(typeof o.position === 'undefined') {o.position = {x: 0, y: 0, z: -4}} if(typeof o.rotation === 'undefined') {o.rotation = {x: 0, y: 0, z: 0}} if(typeof o.rotationSpeed === 'undefined') {o.rotationSpeed = {x: 0, y: 0, z: 0}} if(typeof o.texturesPaths === 'undefined') {o.texturesPaths = ['./res/textures/default.png']} if(typeof o.mainCameraParams === 'undefined') {o.mainCameraParams = this.mainCameraParams} if(typeof o.scale === 'undefined') {o.scale = 1;} o.entityArgPass = this.entityArgPass; o.cameras = this.cameras; if(typeof o.name === 'undefined') {o.name = 'random' + Math.random();} if(typeof o.mesh === 'undefined') { throw console.error('arg mesh is empty...'); return; } console.log('Mesh procedure', o) let myMesh1 = new MEMesh(this.canvas, this.device, this.context, o) this.mainRenderBundle.push(myMesh1); } addMeshObj = (o , clearColor=this.options.clearColor) => { if(typeof o.name === 'undefined') {o.name = genName(9)} if(typeof o.position === 'undefined') {o.position = {x: 0, y: 0, z: -4}} if(typeof o.rotation === 'undefined') {o.rotation = {x: 0, y: 0, z: 0}} if(typeof o.rotationSpeed === 'undefined') {o.rotationSpeed = {x: 0, y: 0, z: 0}} if(typeof o.texturesPaths === 'undefined') {o.texturesPaths = ['./res/textures/default.png']} if(typeof o.mainCameraParams === 'undefined') {o.mainCameraParams = this.mainCameraParams} if(typeof o.scale === 'undefined') {o.scale = [1, 1, 1];} if(typeof o.raycast === 'undefined') {o.raycast = {enabled: false, radius: 2}} o.entityArgPass = this.entityArgPass; o.cameras = this.cameras; if(typeof o.physics === 'undefined') { o.physics = { scale: [1, 1, 1], enabled: true, geometry: "Sphere",// must be fixed<< radius: (typeof o.scale == Number ? o.scale : o.scale[0]), name: o.name, rotation: o.rotation } } if(typeof o.physics.enabled === 'undefined') {o.physics.enabled = true} if(typeof o.physics.geometry === 'undefined') {o.physics.geometry = "Cube"} if(typeof o.physics.radius === 'undefined') {o.physics.radius = o.scale} if(typeof o.physics.mass === 'undefined') {o.physics.mass = 1;} if(typeof o.physics.name === 'undefined') {o.physics.name = o.name;} if(typeof o.physics.scale === 'undefined') {o.physics.scale = o.scale;} if(typeof o.physics.rotation === 'undefined') {o.physics.rotation = o.rotation;} o.physics.position = o.position; // console.log('Mesh procedure', o) // TEST OBJS SEQ ANIMS if(typeof o.objAnim == 'undefined' || typeof o.objAnim == null) { o.objAnim = null; } else { // console.log('o.anim', o.objAnim) if(typeof o.objAnim.animations !== 'undefined') { o.objAnim.play = play; } // no need for single test it in future o.objAnim.meshList = o.objAnim.meshList; if(typeof o.mesh === 'undefined') { o.mesh = o.objAnim.meshList[0]; console.info('objSeq animation is active.'); } // scale for all second option! o.objAnim.scaleAll = function(s) { for(var k in this.meshList) { console.log('SCALE'); this.meshList[k].setScale(s); } } } let myMesh1 = new MEMeshObj(this.canvas, this.device, this.context, o); myMesh1.clearColor = clearColor; if(o.physics.enabled == true) { this.matrixAmmo.addPhysics(myMesh1, o.physics) } this.mainRenderBundle.push(myMesh1); } run(callback) { setTimeout(() => {requestAnimationFrame(this.frame)}, 500) setTimeout(() => {callback(this)}, 20) } destroyProgram = () => { this.mainRenderBundle = []; this.canvas.remove(); } frameSinglePass = () => { if(typeof this.mainRenderBundle == 'undefined') return; try { let shadowPass = null; let renderPass; let commandEncoder = this.device.createCommandEncoder(); this.mainRenderBundle.forEach((meItem, index) => { meItem.position.update(); }) if (this.matrixAmmo) this.matrixAmmo.updatePhysics(); this.mainRenderBundle.forEach((meItem, index) => { meItem.draw(commandEncoder); shadowPass = commandEncoder.beginRenderPass(meItem.shadowPassDescriptor); shadowPass.setPipeline(meItem.shadowPipeline); meItem.drawShadows(shadowPass); shadowPass.end(); }) this.mainRenderBundle.forEach((meItem, index) => { if(index == 0) { renderPass = commandEncoder.beginRenderPass(meItem.renderPassDescriptor); renderPass.setPipeline(meItem.pipeline); } }) this.mainRenderBundle.forEach((meItem, index) => { meItem.drawElements(renderPass); }) if(renderPass) renderPass.end(); this.device.queue.submit([commandEncoder.finish()]); requestAnimationFrame(this.frame); } catch(err) { console.log('%cDraw func (err):' + err, LOG_WARN) requestAnimationFrame(this.frame); } } framePassPerObject = () => { let commandEncoder = this.device.createCommandEncoder(); this.matrixAmmo.updatePhysics(); this.mainRenderBundle.forEach((meItem, index) => { if(index === 0) { if(meItem.renderPassDescriptor) meItem.renderPassDescriptor.colorAttachments[0].loadOp = 'clear'; } else { if(meItem.renderPassDescriptor) meItem.renderPassDescriptor.colorAttachments[0].loadOp = 'load'; } // Update transforms, physics, etc. (optional) meItem.draw(commandEncoder); // optional: if this does per-frame updates if(meItem.renderBundle) { // Set up view per object meItem.renderPassDescriptor.colorAttachments[0].view = this.context.getCurrentTexture().createView(); const passEncoder = commandEncoder.beginRenderPass(meItem.renderPassDescriptor); passEncoder.executeBundles([meItem.renderBundle]); // ✅ Use only this bundle passEncoder.end(); } else { meItem.draw(commandEncoder); // fallback if no renderBundle } }); this.device.queue.submit([commandEncoder.finish()]); requestAnimationFrame(this.frame); } }