UNPKG

matrix-engine-wgpu

Version:

Networking implemented - based on kurento openvidu server. fix arcball camera,instanced draws added also effect pipeline blend with instancing option.Normalmap added, Fixed shadows casting vs camera/video texture, webGPU powered pwa application. Crazy fas

1,596 lines (1,544 loc) 1.02 MB
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ "use strict"; var _cameraTexture = require("./examples/camera-texture.js"); var _glbLoader = require("./examples/glb-loader.js"); var _loadObjFile = require("./examples/load-obj-file.js"); var _loadObjsSequence = require("./examples/load-objs-sequence.js"); var _unlitTextures = require("./examples/unlit-textures.js"); var _videoTexture = require("./examples/video-texture.js"); var _utils = require("./src/engine/utils.js"); /** * @examples * MATRIX_ENGINE_WGPU EXAMPLE WORKSPACE * Nikola Lukic 2024 */ // import {loadJamb} from "./examples/load-jamb.js"; function destroyJambDoms() { if ((0, _utils.byId)('hud')) (0, _utils.byId)('hud').remove(); if ((0, _utils.byId)('jambTable')) (0, _utils.byId)('jambTable').remove(); if ((0, _utils.byId)('topTitleDOM')) (0, _utils.byId)('topTitleDOM').remove(); } (0, _utils.byId)('loadObjFile').addEventListener("click", () => { // byId('loadObjFile').setAttribute('disabled', true) // byId('unlitTextures').removeAttribute('disabled') if (typeof app !== "undefined") app.destroyProgram(); destroyJambDoms(); (0, _loadObjFile.loadObjFile)(); }); (0, _utils.byId)('unlitTextures').addEventListener("click", () => { // byId('unlitTextures').setAttribute('disabled', true) // byId('loadObjFile').removeAttribute('disabled') if (typeof app !== "undefined") app.destroyProgram(); destroyJambDoms(); (0, _unlitTextures.unlitTextures)(); }); (0, _utils.byId)('camera-texture').addEventListener("click", () => { if (typeof app !== "undefined") app.destroyProgram(); destroyJambDoms(); (0, _cameraTexture.loadCameraTexture)(); }); (0, _utils.byId)('video-texture').addEventListener("click", () => { if (typeof app !== "undefined") app.destroyProgram(); destroyJambDoms(); (0, _videoTexture.loadVideoTexture)(); }); (0, _utils.byId)('glb-loader').addEventListener("click", () => { if (typeof app !== "undefined") app.destroyProgram(); // destroyJambDoms(); (0, _glbLoader.loadGLBLoader)(); }); (0, _utils.byId)('jamb').addEventListener("click", () => { open("https://maximumroulette.com/apps/webgpu/"); }); (0, _utils.byId)('objs-anim').addEventListener("click", () => { // byId('unlitTextures').setAttribute('disabled', true) // byId('loadObjFile').setAttribute('disabled', true) // byId('jamb').removeAttribute('disabled') if (typeof app !== "undefined") app.destroyProgram(); destroyJambDoms(); (0, _loadObjsSequence.loadObjsSequence)(); }); },{"./examples/camera-texture.js":2,"./examples/glb-loader.js":3,"./examples/load-obj-file.js":4,"./examples/load-objs-sequence.js":5,"./examples/unlit-textures.js":6,"./examples/video-texture.js":7,"./src/engine/utils.js":48}],2:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadCameraTexture = void 0; var _world = _interopRequireDefault(require("../src/world.js")); var _loaderObj = require("../src/engine/loader-obj.js"); var _utils = require("../src/engine/utils.js"); var _raycast = require("../src/engine/raycast.js"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } var loadCameraTexture = function () { let cameraTexture = new _world.default({ useSingleRenderPass: true, canvasSize: 'fullscreen', mainCameraParams: { type: 'WASD', responseCoef: 1000 }, clearColor: { r: 0, b: 0.122, g: 0.122, a: 1 } }, () => { cameraTexture.addLight(); addEventListener('AmmoReady', () => { (0, _loaderObj.downloadMeshes)({ welcomeText: "./res/meshes/blender/piramyd.obj", armor: "./res/meshes/obj/armor.obj", sphere: "./res/meshes/blender/sphere.obj", cube: "./res/meshes/blender/cube.obj" }, onLoadObj, { scale: [1, 1, 1] }); }); function onLoadObj(m) { cameraTexture.myLoadedMeshes = m; for (var key in m) { console.log(`%c Loaded objs: ${key} `, _utils.LOG_MATRIX); } cameraTexture.addMeshObj({ position: { x: 0, y: 2, z: -10 }, rotation: { x: 0, y: 0, z: 0 }, rotationSpeed: { x: 0, y: 0, z: 0 }, texturesPaths: ['./res/meshes/blender/cube.png'], name: 'MyVideoTex', mesh: m.cube, physics: { enabled: true, geometry: "Cube" } // raycast: { enabled: true , radius: 2 } }); var TEST = cameraTexture.getSceneObjectByName('MyVideoTex'); setTimeout(() => { console.log(`%c Test video-texture...`, _utils.LOG_MATRIX); TEST.loadVideoTexture({ type: 'camera' }); }, 4000); } }); window.app = cameraTexture; }; exports.loadCameraTexture = loadCameraTexture; },{"../src/engine/loader-obj.js":40,"../src/engine/raycast.js":47,"../src/engine/utils.js":48,"../src/world.js":71}],3:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadGLBLoader = loadGLBLoader; var _world = _interopRequireDefault(require("../src/world.js")); var _loaderObj = require("../src/engine/loader-obj.js"); var _utils = require("../src/engine/utils.js"); var _bvh = require("../src/engine/loaders/bvh.js"); var _webgpuGltf = require("../src/engine/loaders/webgpu-gltf.js"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /** * @Note * “Character and animation assets from Mixamo, * used under Adobe’s royalty‑free license. * Redistribution of raw assets is not permitted.” **/ function loadGLBLoader() { let TEST_ANIM = new _world.default({ useSingleRenderPass: true, canvasSize: 'fullscreen', mainCameraParams: { type: 'WASD', responseCoef: 1000 }, clearColor: { r: 0, b: 0.122, g: 0.122, a: 1 } }, () => { addEventListener('AmmoReady', async () => { setTimeout(() => { app.cameras.WASD.yaw = -0.03; app.cameras.WASD.pitch = -0.49; app.cameras.WASD.position[2] = 0; app.cameras.WASD.position[1] = 23; }, 2000); (0, _loaderObj.downloadMeshes)({ cube: "./res/meshes/blender/cube.obj" }, onGround, { scale: [120, 0.5, 120] }); // // Monster1 var glbFile01 = await fetch("res/meshes/glb/monster.glb").then(res => res.arrayBuffer().then(buf => (0, _webgpuGltf.uploadGLBModel)(buf, TEST_ANIM.device))); TEST_ANIM.addGlbObj({ material: { type: 'standard', useTextureFromGlb: true }, scale: [20, 20, 20], position: { x: 0, y: -4, z: -70 }, name: 'firstGlb', texturesPaths: ['./res/meshes/glb/textures/mutant_origin.png'] }, null, glbFile01); var glbFile02 = await fetch("res/meshes/glb/monster.glb").then(res => res.arrayBuffer().then(buf => (0, _webgpuGltf.uploadGLBModel)(buf, TEST_ANIM.device))); TEST_ANIM.addGlbObj({ material: { type: 'power', useTextureFromGlb: true }, scale: [20, 20, 20], position: { x: -40, y: -4, z: -70 }, name: 'firstGlb', texturesPaths: ['./res/meshes/glb/textures/mutant_origin.png'] }, null, glbFile02); var glbFile03 = await fetch("res/meshes/glb/monster.glb").then(res => res.arrayBuffer().then(buf => (0, _webgpuGltf.uploadGLBModel)(buf, TEST_ANIM.device))); TEST_ANIM.addGlbObj({ material: { type: 'pong', useTextureFromGlb: true }, scale: [20, 20, 20], position: { x: 40, y: -4, z: -70 }, name: 'firstGlb', texturesPaths: ['./res/meshes/glb/textures/mutant_origin.png'] }, null, glbFile03); // woman var glbFile11 = await fetch("res/meshes/glb/woman1.glb").then(res => res.arrayBuffer().then(buf => (0, _webgpuGltf.uploadGLBModel)(buf, TEST_ANIM.device))); TEST_ANIM.addGlbObj({ material: { type: 'normalmap', useTextureFromGlb: true }, scale: [20, 20, 20], position: { x: 0, y: -4, z: -20 }, name: 'woman1', texturesPaths: ['./res/meshes/glb/textures/mutant_origin.png'] }, null, glbFile11); var glbFile02 = await fetch("res/meshes/glb/woman1.glb").then(res => res.arrayBuffer().then(buf => (0, _webgpuGltf.uploadGLBModel)(buf, TEST_ANIM.device))); TEST_ANIM.addGlbObj({ material: { type: 'power', useTextureFromGlb: true }, scale: [20, 20, 20], position: { x: -40, y: -4, z: -20 }, name: 'woman1', texturesPaths: ['./res/meshes/glb/textures/mutant_origin.png'] }, null, glbFile02); var glbFile03 = await fetch("res/meshes/glb/woman1.glb").then(res => res.arrayBuffer().then(buf => (0, _webgpuGltf.uploadGLBModel)(buf, TEST_ANIM.device))); TEST_ANIM.addGlbObj({ material: { type: 'pong', useTextureFromGlb: true }, scale: [20, 20, 20], position: { x: 40, y: -4, z: -20 }, name: 'woman1', texturesPaths: ['./res/meshes/glb/textures/mutant_origin.png'] }, null, glbFile03); var glbFileWhouse = await fetch("res/meshes/glb/wood-house-1.glb").then(res => res.arrayBuffer().then(buf => (0, _webgpuGltf.uploadGLBModel)(buf, TEST_ANIM.device))); TEST_ANIM.addGlbObj({ material: { type: 'pong', useTextureFromGlb: true }, scale: [20, 20, 20], position: { x: 40, y: -4, z: -20 }, name: 'glbFileWhouse', texturesPaths: ['./res/meshes/glb/textures/mutant_origin.png'] }, null, glbFileWhouse); // this is future load and replace skeletal anim. // const path = 'https://raw.githubusercontent.com/zlatnaspirala/Matrix-Engine-BVH-test/main/javascript-bvh/example.bvh'; // const path = 'res/meshes/glb/glb-test1.bvh'; // loadBVH(path).then(async (BVHANIM) => { // var glbFile = await fetch( // "res/meshes/glb/test.glb") // .then(res => res.arrayBuffer().then(buf => uploadGLBModel(buf, TEST_ANIM.device))); // TEST_ANIM.addGlbObj({ // // scale: [1,1,1], // scale: [10, 10, 10], // name: 'firstGlb', // texturesPaths: ['./res/textures/rust.jpg'], // }, BVHANIM, glbFile); // }); }); function onGround(m) { TEST_ANIM.addLight(); TEST_ANIM.addMeshObj({ position: { x: 0, y: -5, z: -10 }, rotation: { x: 0, y: 0, z: 0 }, rotationSpeed: { x: 0, y: 0, z: 0 }, texturesPaths: ['./res/meshes/blender/cube.png'], name: 'ground', mesh: m.cube, physics: { enabled: false, mass: 0, geometry: "Cube" } }); app.lightContainer[0].position[1] = 25; } }); // just for dev window.app = TEST_ANIM; } // loadGLBLoader() },{"../src/engine/loader-obj.js":40,"../src/engine/loaders/bvh.js":42,"../src/engine/loaders/webgpu-gltf.js":43,"../src/engine/utils.js":48,"../src/world.js":71}],4:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadObjFile = void 0; var _world = _interopRequireDefault(require("../src/world.js")); var _loaderObj = require("../src/engine/loader-obj.js"); var _utils = require("../src/engine/utils.js"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } // import {addRaycastsAABBListener} from "../src/engine/raycast.js"; var loadObjFile = function () { let loadObjFile = new _world.default({ useSingleRenderPass: true, canvasSize: 'fullscreen', mainCameraParams: { type: 'WASD', responseCoef: 1000 }, clearColor: { r: 0, b: 0.122, g: 0.122, a: 1 } }, () => { addEventListener('AmmoReady', () => { (0, _loaderObj.downloadMeshes)({ ball: "./res/meshes/blender/sphere.obj", cube: "./res/meshes/blender/cube.obj" }, onLoadObj, { scale: [1, 1, 1] }); (0, _loaderObj.downloadMeshes)({ cube: "./res/meshes/blender/cube.obj" }, onGround, { scale: [20, 1, 20] }); }); function onGround(m) { setTimeout(() => { app.cameras.WASD.yaw = -0.03; app.cameras.WASD.pitch = -0.49; app.cameras.WASD.position[2] = 0; app.cameras.WASD.position[1] = 3.76; }, 600); loadObjFile.addMeshObj({ position: { x: 0, y: -5, z: -10 }, rotation: { x: 0, y: 0, z: 0 }, rotationSpeed: { x: 0, y: 0, z: 0 }, texturesPaths: ['./res/meshes/blender/cube.png'], name: 'ground', mesh: m.cube, physics: { enabled: false, mass: 0, geometry: "Cube" } // raycast: { enabled: true , radius: 2 } }); } function onLoadObj(m) { loadObjFile.myLoadedMeshes = m; loadObjFile.addMeshObj({ material: { type: 'standard' }, position: { x: 0, y: 2, z: -20 }, rotation: { x: 0, y: 0, z: 0 }, rotationSpeed: { x: 0, y: 0, z: 0 }, texturesPaths: ['./res/meshes/blender/cube.png'], name: 'cube1', mesh: m.cube, physics: { enabled: false, geometry: "Cube" } // raycast: { enabled: true , radius: 2 } }); loadObjFile.addMeshObj({ material: { type: 'standard' }, position: { x: 0, y: -1, z: -20 }, rotation: { x: 0, y: 0, z: 0 }, rotationSpeed: { x: 0, y: 111, z: 0 }, texturesPaths: ['./res/meshes/blender/cube.png'], name: 'ball1', mesh: m.ball, physics: { enabled: false, geometry: "Sphere" } }); var TEST = loadObjFile.getSceneObjectByName('cube2'); console.log(`%c Test access scene ${TEST} object.`, _utils.LOG_MATRIX); loadObjFile.addLight(); loadObjFile.lightContainer[0].behavior.setOsc0(-1, 1, 0.1); loadObjFile.lightContainer[0].behavior.value_ = -1; loadObjFile.lightContainer[0].updater.push(light => { light.position[0] = light.behavior.setPath0(); }); loadObjFile.lightContainer[0].position[1] = 9; } }); // just for dev window.app = loadObjFile; }; exports.loadObjFile = loadObjFile; },{"../src/engine/loader-obj.js":40,"../src/engine/utils.js":48,"../src/world.js":71}],5:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadObjsSequence = void 0; var _world = _interopRequireDefault(require("../src/world.js")); var _loaderObj = require("../src/engine/loader-obj.js"); var _utils = require("../src/engine/utils.js"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } var loadObjsSequence = function () { let loadObjFile = new _world.default({ useSingleRenderPass: true, canvasSize: 'fullscreen', mainCameraParams: { type: 'WASD', responseCoef: 1000 } }, () => { addEventListener('AmmoReady', () => { // requied now loadObjFile.addLight(); // adapt app.lightContainer[0].position[2] = -20; app.lightContainer[0].position[1] = 25; app.lightContainer[0].intensity = 10; (0, _loaderObj.downloadMeshes)({ cube: "./res/meshes/blender/cube.obj" }, onGround, { scale: [20, 1, 20] }); (0, _loaderObj.downloadMeshes)((0, _loaderObj.makeObjSeqArg)({ id: "swat-walk-pistol", path: "res/meshes/objs-sequence/swat-walk-pistol", from: 1, to: 20 }), onLoadObj, { scale: [0.1, 0.1, 0.1] }); }); function onLoadObj(m) { console.log(`%c Loaded objs: ${m} `, _utils.LOG_MATRIX); var objAnim = { id: "swat-walk-pistol", meshList: m, currentAni: 1, animations: { active: 'walk', walk: { from: 1, to: 20, speed: 3 }, walkPistol: { from: 36, to: 60, speed: 3 } } }; loadObjFile.addMeshObj({ position: { x: 0, y: 0, z: -20 }, rotation: { x: 0, y: 0, z: 0 }, rotationSpeed: { x: 0, y: 0, z: 0 }, scale: [100, 100, 100], texturesPaths: ['./res/meshes/blender/swat.png'], name: 'swat', mesh: m['swat-walk-pistol'], physics: { enabled: false, geometry: "Cube" }, objAnim: objAnim }); setTimeout(() => { app.cameras.WASD.pitch = -0.2605728267949113; app.cameras.WASD.yaw = -0.0580; app.cameras.WASD.position[1] = 15; app.cameras.WASD.position[2] = 11; app.getSceneObjectByName('swat').objAnim.play('walk'); }, 200); } function onGround(m) { setTimeout(() => { app.cameras.WASD.yaw = -0.03; app.cameras.WASD.pitch = -0.49; app.cameras.WASD.position[2] = 0; app.cameras.WASD.position[1] = 3.76; }, 500); loadObjFile.addMeshObj({ position: { x: 0, y: -1, z: -10 }, rotation: { x: 0, y: 0, z: 0 }, rotationSpeed: { x: 0, y: 0, z: 0 }, texturesPaths: ['./res/meshes/blender/cube.png'], name: 'ground', mesh: m.cube, physics: { enabled: false, mass: 0, geometry: "Cube" } // raycast: { enabled: true , radius: 2 } }); } }); // Just for dev - easy console access window.app = loadObjFile; }; exports.loadObjsSequence = loadObjsSequence; },{"../src/engine/loader-obj.js":40,"../src/engine/utils.js":48,"../src/world.js":71}],6:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.unlitTextures = void 0; var _world = _interopRequireDefault(require("../src/world.js")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } var unlitTextures = function () { let unlitTextures = new _world.default({ useSingleRenderPass: false, canvasSize: 'fullscreen' }, () => { addEventListener('AmmoReady', () => { let c = { scale: 2, position: { x: -3, y: 0, z: -10 }, rotation: { x: 0, y: 0, z: 0 }, rotationSpeed: { x: 10, y: 0, z: 0 }, texturesPaths: ['./res/textures/rust.jpg'] }; let o = { scale: 2, position: { x: 3, y: 0, z: -10 }, rotation: { x: 0, y: 0, z: 0 }, rotationSpeed: { x: 10, y: 0, z: 0 }, texturesPaths: ['./res/textures/default.png'] }; unlitTextures.addCube(o); unlitTextures.addBall(c); // just to fix warns unlitTextures.addLight(); }); }); window.app = unlitTextures; }; exports.unlitTextures = unlitTextures; },{"../src/world.js":71}],7:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadVideoTexture = void 0; var _world = _interopRequireDefault(require("../src/world.js")); var _loaderObj = require("../src/engine/loader-obj.js"); var _utils = require("../src/engine/utils.js"); var _raycast = require("../src/engine/raycast.js"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } // @group(0) @binding(5) var<uniform> postFXMode: u32; var loadVideoTexture = function () { let videoTexture = new _world.default({ useSingleRenderPass: true, canvasSize: 'fullscreen', mainCameraParams: { type: 'WASD', responseCoef: 1000 }, clearColor: { r: 0, b: 0.122, g: 0.122, a: 1 } }, () => { // For now one light perscene must be added. // if you dont wanna light just use intesity = 0 // videoTexture is app main instance videoTexture.addLight(); (0, _raycast.addRaycastsAABBListener)(); videoTexture.canvas.addEventListener("ray.hit.event", e => { console.log('test ray after shadows merge'); }); addEventListener('AmmoReady', () => { (0, _loaderObj.downloadMeshes)({ welcomeText: "./res/meshes/blender/piramyd.obj", armor: "./res/meshes/obj/armor.obj", sphere: "./res/meshes/blender/sphere.obj", cube: "./res/meshes/blender/cube.obj" }, onLoadObj, { scale: [1, 1, 1] }); }); function onLoadObj(m) { videoTexture.myLoadedMeshes = m; for (var key in m) { console.log(`%c Loaded objs: ${key} `, _utils.LOG_MATRIX); } videoTexture.addMeshObj({ position: { x: 0, y: 2, z: -10 }, rotation: { x: 0, y: 0, z: 0 }, rotationSpeed: { x: 0, y: 0, z: 0 }, texturesPaths: ['./res/meshes/blender/cube.png'], name: 'MyVideoTex', mesh: m.cube, physics: { enabled: true, geometry: "Cube" }, raycast: { enabled: true, radius: 12 } }); var TEST = videoTexture.getSceneObjectByName('MyVideoTex'); console.log(`%c Test video-texture...`, _utils.LOG_MATRIX); TEST.loadVideoTexture({ type: 'video', src: 'res/videos/tunel.mp4' }); } }); window.app = videoTexture; }; exports.loadVideoTexture = loadVideoTexture; },{"../src/engine/loader-obj.js":40,"../src/engine/raycast.js":47,"../src/engine/utils.js":48,"../src/world.js":71}],8:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _bvhLoader = require("./module/bvh-loader"); var _default = exports.default = _bvhLoader.MEBvh; },{"./module/bvh-loader":9}],9:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MEBvhJoint = exports.MEBvh = void 0; exports.degToRad = degToRad; exports.dot3vs1 = dot3vs1; exports.euler2mat = euler2mat; exports.mat2euler = mat2euler; exports.multiply = multiply; var _webgpuMatrix = require("webgpu-matrix"); /** * @description Manual convert python script BVH * from https://github.com/dabeschte/npybvh to the JS. * @author Nikola Lukic * @license GPL-V3 */ function degToRad(degrees) { return degrees * Math.PI / 180; } ; function arraySum3(a, b) { var rez1 = a[0] + b[0]; var rez2 = a[1] + b[1]; var rez3 = a[2] + b[2]; return [rez1, rez2, rez3]; } function deg2rad(degrees) { return degrees * (Math.PI / 180); } function npdeg2rad(degrees) { return [degrees[0] * (Math.PI / 180), degrees[1] * (Math.PI / 180), degrees[2] * (Math.PI / 180)]; } function rad2deg(radians) { return radians * (180 / Math.PI); } function byId(id) { return document.getElementById(id); } // fix for .dot N-dim vs 1D-dim Array function dot3vs1(a, b) { var aNumRows = a.length, aNumCols = a[0].length, bNumRows = b.length; var REZ1 = 0, REZ2 = 0, REZ3 = 0; if (aNumRows == 3 && aNumCols == 3 && bNumRows == 3) { for (var j = 0; j < a.length; j++) { // First root of 3x3 a. REZ1 += a[0][j] * b[j]; REZ2 += a[1][j] * b[j]; REZ3 += a[2][j] * b[j]; } var finalRez = [REZ1, REZ2, REZ3]; return finalRez; } else { console.error("Bad arguments for dot3vs1"); } } function multiply(a, b) { var aNumRows = a.length, aNumCols = a[0].length, bNumRows = b.length, bNumCols = b[0].length, m = new Array(aNumRows); for (var r = 0; r < aNumRows; ++r) { m[r] = new Array(bNumCols); for (var c = 0; c < bNumCols; ++c) { m[r][c] = 0; for (var i = 0; i < aNumCols; ++i) { m[r][c] += a[r][i] * b[i][c]; } } } return m; } /** * @description * Euler's rotation theorem tells us that any rotation in 3D can be described by 3 * angles. Let's call the 3 angles the *Euler angle vector* and call the angles * in the vector :Math:`alpha`, :Math:`beta` and :Math:`gamma`. The vector is [ * :Math:`alpha`, :Math:`beta`. :Math:`gamma` ] and, in this description, the * order of the parameters specifies the order in which the rotations occur (so * the rotation corresponding to :Math:`alpha` is applied first). * @source https://github.com/matthew-brett/transforms3d/blob/master/transforms3d/euler.py */ // map axes strings to/from tuples of inner axis, parity, repetition, frame var _AXES2TUPLE = { 'sxyz': [0, 0, 0, 0], 'sxyx': [0, 0, 1, 0], 'sxzy': [0, 1, 0, 0], 'sxzx': [0, 1, 1, 0], 'syzx': [1, 0, 0, 0], 'syzy': [1, 0, 1, 0], 'syxz': [1, 1, 0, 0], 'syxy': [1, 1, 1, 0], 'szxy': [2, 0, 0, 0], 'szxz': [2, 0, 1, 0], 'szyx': [2, 1, 0, 0], 'szyz': [2, 1, 1, 0], 'rzyx': [0, 0, 0, 1], 'rxyx': [0, 0, 1, 1], 'ryzx': [0, 1, 0, 1], 'rxzx': [0, 1, 1, 1], 'rxzy': [1, 0, 0, 1], 'ryzy': [1, 0, 1, 1], 'rzxy': [1, 1, 0, 1], 'ryxy': [1, 1, 1, 1], 'ryxz': [2, 0, 0, 1], 'rzxz': [2, 0, 1, 1], 'rxyz': [2, 1, 0, 1], 'rzyz': [2, 1, 1, 1] }; // axis sequences for Euler angles var _NEXT_AXIS = [1, 2, 0, 1]; function euler2mat(ai, aj, ak, axes) { if (typeof axes === 'undefined') var axes = 'sxyz'; // Return rotation matrix from Euler angles and axis sequence. // Parameters /* ai : float First rotation angle (according to `axes`). aj : float Second rotation angle (according to `axes`). ak : float Third rotation angle (according to `axes`). axes : str, optional Axis specification; one of 24 axis sequences as string or encoded tuple - e.g. ``sxyz`` (the default). Returns ------- mat : array (3, 3) Rotation matrix or affine. Examples -------- >>> R = euler2mat(1, 2, 3, 'syxz') >>> np.allclose(np.sum(R[0]), -1.34786452) True >>> R = euler2mat(1, 2, 3, (0, 1, 0, 1)) >>> np.allclose(np.sum(R[0]), -0.383436184) True */ try { var firstaxis = _AXES2TUPLE[axes][0], parity = _AXES2TUPLE[axes][1], repetition = _AXES2TUPLE[axes][2], frame = _AXES2TUPLE[axes][3]; } catch (AttributeError) { // _TUPLE2AXES[axes] # validation // firstaxis, parity, repetition, frame = axes console.error("AttributeError: ", AttributeError); } var i = firstaxis; var j = _NEXT_AXIS[i + parity]; var k = _NEXT_AXIS[i - parity + 1]; if (frame) { ai = ak; ak = ai; } if (parity) { ai = -ai; aj = -aj; ak = -ak; } var si = Math.sin(ai); var sj = Math.sin(aj); var sk = Math.sin(ak); var ci = Math.cos(ai); var cj = Math.cos(aj); var ck = Math.cos(ak); var cc = ci * ck; var cs = ci * sk; var sc = si * ck; var ss = si * sk; // M = np.eye(3) var M = [[1., 0., 0], [0., 1., 0], [0., 0., 1]]; if (repetition) { M[i][i] = cj; M[i][j] = sj * si; M[i][k] = sj * ci; M[j][i] = sj * sk; M[j][j] = -cj * ss + cc; M[j][k] = -cj * cs - sc; M[k][i] = -sj * ck; M[k][j] = cj * sc + cs; M[k][k] = cj * cc - ss; } else { M[i][i] = cj * ck; M[i][j] = sj * sc - cs; M[i][k] = sj * cc + ss; M[j][i] = cj * sk; M[j][j] = sj * ss + cc; M[j][k] = sj * cs - sc; M[k][i] = -sj; M[k][j] = cj * si; M[k][k] = cj * ci; } return M; } /** * @description * How to calculate the angle from rotation matrix. */ function mat2euler(M, rad2deg_flag) { var pitch_1, pitch_2, roll_1, roll_2, yaw_1, yaw_2, pitch, roll, yaw; if (M[2][0] != 1 & M[2][0] != -1) { pitch_1 = -1 * Math.asin(M[2][0]); pitch_2 = Math.PI - pitch_1; roll_1 = Math.atan2(M[2][1] / Math.cos(pitch_1), M[2][2] / Math.cos(pitch_1)); roll_2 = Math.atan2(M[2][1] / Math.cos(pitch_2), M[2][2] / Math.cos(pitch_2)); yaw_1 = Math.atan2(M[1][0] / Math.cos(pitch_1), M[0][0] / Math.cos(pitch_1)); yaw_2 = Math.atan2(M[1][0] / Math.cos(pitch_2), M[0][0] / Math.cos(pitch_2)); pitch = pitch_1; roll = roll_1; yaw = yaw_1; } else { yaw = 0; if (M[2][0] == -1) { pitch = Math.PI / 2; roll = yaw + Math.atan2(M[0][1], M[0][2]); } else { pitch = -Math.PI / 2; roll = -1 * yaw + Math.atan2(-1 * M[0][1], -1 * M[0][2]); } } if (typeof rad2deg_flag !== "undefined") { // convert from radians to degrees roll = roll * 180 / Math.PI; pitch = pitch * 180 / Math.PI; yaw = yaw * 180 / Math.PI; } return [roll, pitch, yaw]; } class MEBvhJoint { constructor(name, parent) { this.name = name; this.parent = parent; this.offset = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]; this.channels = []; this.children = []; // New: where in the frame array this joint’s channels start ??? this.channelOffset = 0; } add_child(child) { this.children.push(child); } __repr__() { return this.name; } position_animated() { var detFlag = false; for (const item in this.channels) { if (this.channels[item].endsWith("position") == true) { detFlag = true; } } return detFlag; } rotation_animated() { var detFlag = false; for (const item in this.channels) { if (this.channels[item].endsWith("rotation") == true) { detFlag = true; } } return detFlag; } createIdentityMatrix() { // Returns a flat Float32Array of length 16 (column-major) return new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); } matrixFromKeyframe(frameData) { const m = this.createIdentityMatrix(); let t = [0, 0, 0]; let r = [0, 0, 0]; for (let i = 0; i < this.channels.length; i++) { const channel = this.channels[i]; const value = frameData[this.channelOffset + i]; // channelOffset = index into frameData where this joint’s values start switch (channel) { case 'Xposition': t[0] = value; break; case 'Yposition': t[1] = value; break; case 'Zposition': t[2] = value; break; case 'Xrotation': r[0] = degToRad(value); break; case 'Yrotation': r[1] = degToRad(value); break; case 'Zrotation': r[2] = degToRad(value); break; } } // Apply translation _webgpuMatrix.mat4.translate(m, t, m); // Apply rotations in BVH order (important!) _webgpuMatrix.mat4.rotateX(m, r[0], m); _webgpuMatrix.mat4.rotateY(m, r[1], m); _webgpuMatrix.mat4.rotateZ(m, r[2], m); return m; } } exports.MEBvhJoint = MEBvhJoint; class MEBvh { constructor() { this.joints = {}; this.root = null; this.keyframes = null; this.frames = 0; this.fps = 0; this.myName = "MATRIX-ENGINE-BVH"; // new this.jointOrder = []; // array to store joints in order } computeJointOrder() { this.jointOrder = []; const traverse = joint => { this.jointOrder.push(joint.name); for (const child of joint.children) { traverse(child); } }; traverse(this.root); // root is your MEBvhJoint } computeChannelOffsets() { let offset = 0; const walk = joint => { joint.channelOffset = offset; // assign offset += joint.channels.length; // advance for (const child of joint.children) { walk(child); } }; if (this.root) walk(this.root); this.totalChannels = offset; // store total for frame allocation } async parse_file(link) { return new Promise((resolve, reject) => { fetch(link).then(event => { event.text().then(text => { var hierarchy = text.split("MOTION")[0]; var motion = text.split("MOTION")[1]; var newLog = document.createElement("div"); newLog.innerHTML += '<h2>Hierarchy</h2>'; newLog.innerHTML += '<p>' + hierarchy + '</p>'; var newLog2 = document.createElement("span"); newLog2.innerHTML += '<h2>Motion</h2>'; newLog2.innerHTML += '<p class="paragraf fixHeight" >' + motion + '</p>'; if (byId && byId('log') !== null) { byId('log').appendChild(newLog2); byId('log').appendChild(newLog); } this._parse_hierarchy(hierarchy); this.computeJointOrder(); this.computeChannelOffsets(); // <— must do this here NEW this.parse_motion(motion); resolve(); }); }); }); } _parse_hierarchy(text) { var lines = text.split(/\s*\n+\s*/); var joint_stack = []; for (var key in lines) { var line = lines[key]; var words = line.split(/\s+/); var instruction = words[0]; var parent = null; if (instruction == "JOINT" || instruction == "ROOT") { if (instruction == "JOINT") { // -1 py -> last item parent = joint_stack[joint_stack.length - 1]; } else { parent = null; } var joint = new MEBvhJoint(words[1], parent); this.joints[joint.name] = joint; if (parent != null) { parent.add_child(joint); } joint_stack.push(joint); if (instruction == "ROOT") { this.root = joint; } } else if (instruction == "CHANNELS") { for (var j = 2; j < words.length; j++) { joint_stack[joint_stack.length - 1].channels.push(words[j]); } } else if (instruction == "OFFSET") { for (var j = 1; j < words.length; j++) { joint_stack[joint_stack.length - 1].offset[j - 1] = parseFloat(words[j]); } } else if (instruction == "End") { var joint = new MEBvhJoint(joint_stack[joint_stack.length - 1].name + "_end", joint_stack[joint_stack.length - 1]); joint_stack[joint_stack.length - 1].add_child(joint); joint_stack.push(joint); this.joints[joint.name] = joint; } else if (instruction == "}") { joint_stack.pop(); } } } _add_pose_recursive(joint, offset, poses) { var newLog1 = document.createElement("span"); newLog1.innerHTML += '<h2>add_pose_recursive</h2>'; newLog1.innerHTML += '<p class="paragraf" >Joint Name: ' + joint.name + '</p>'; newLog1.innerHTML += '<p>joint.parent : ' + (joint.parent != null ? joint.parent.name : 'null') + '</p>'; newLog1.innerHTML += '<p>joint.offset : ' + joint.offset + '</p>'; newLog1.innerHTML += '<p>joint.children.length : ' + joint.children.length + '</p>'; joint.children.length != 0 ? newLog1.innerHTML += '<p> Childrens: ' : newLog1.innerHTML += 'No Childrens '; joint.children.forEach(iJoint => { newLog1.innerHTML += ' ' + iJoint['name'] + ' , '; }); newLog1.innerHTML += '</p>'; newLog1.innerHTML += '<p>Argument offset : ' + offset + '</p>'; byId('log').appendChild(newLog1); var pose = arraySum3(joint.offset, offset); poses.push(pose); for (var c in joint.children) { this._add_pose_recursive(joint.children[c], pose, poses); } } plot_hierarchy() { // import matplotlib.pyplot as plt // from mpl_toolkits.mplot3d import axes3d, Axes3D var poses = []; this._add_pose_recursive(this.root, [0, 0, 0], poses); // pos = np.array(poses); /* Draw staff DISABLED fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(pos[:, 0], pos[:, 2], pos[:, 1]) ax.set_xlim(-30, 30) ax.set_ylim(-30, 30) ax.set_zlim(-30, 30) plt.show() */ } parse_motion(text) { var lines = text.split(/\s*\n+\s*/); var frame = 0; for (var key in lines) { var line = lines[key]; if (line == "") { continue; } var words = line.split(/\s+/); if (line.startsWith("Frame Time:")) { this.fps = Math.round(1 / parseFloat(words[2])); continue; } if (line.startsWith("Frames:")) { this.frames = parseInt(words[1]); continue; } if (this.keyframes == null) { // OK this is just costruction (define) with random values. var localArr = Array.from(Array(this.frames), () => new Array(words.length)); this.keyframes = localArr; } for (var angle_index = 0; angle_index < words.length; angle_index++) { this.keyframes[frame][angle_index] = parseFloat(words[angle_index]); } frame += 1; } } _extract_rotation(frame_pose, index_offset, joint) { var local_rotation = [0, 0, 0], M_rotation; for (var key in joint.channels) { var channel = joint.channels[key]; if (channel.endsWith("position")) { continue; } if (channel == "Xrotation") { local_rotation[0] = frame_pose[index_offset]; } else if (channel == "Yrotation") { local_rotation[1] = frame_pose[index_offset]; } else if (channel == "Zrotation") { local_rotation[2] = frame_pose[index_offset]; } else { console.warn("Unknown channel {channel}"); // raise Exception(f"Unknown channel {channel}"); } index_offset += 1; } local_rotation = npdeg2rad(local_rotation); M_rotation = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; for (key in joint.channels) { var channel = joint.channels[key]; if (channel.endsWith("position")) { continue; } var euler_rot; if (channel == "Xrotation") { // console.warn("local_rotation " + local_rotation); euler_rot = [local_rotation[0], 0., 0.]; } else if (channel == "Yrotation") { euler_rot = [0., local_rotation[1], 0.]; } else if (channel == "Zrotation") { euler_rot = [0., 0., local_rotation[2]]; } else { console.warn("Unknown channel {channel}"); } var M_channel = euler2mat(euler_rot[0], euler_rot[1], euler_rot[2], euler_rot[3]); var M_rotation = multiply(M_rotation, M_channel); } return [M_rotation, index_offset]; } _extract_position(joint, frame_pose, index_offset) { var offset_position = [0, 0, 0]; for (var key in joint.channels) { var channel = joint.channels[key]; if (channel.endsWith("rotation")) { continue; } if (channel == "Xposition") { offset_position[0] = frame_pose[index_offset]; } else if (channel == "Yposition") { offset_position[1] = frame_pose[index_offset]; } else if (channel == "Zposition") { offset_position[2] = frame_pose[index_offset]; } else { console.warn("Unknown channel {channel}"); // raise Exception(f"Unknown channel {channel}") } index_offset += 1; } return [offset_position, index_offset]; } _recursive_apply_frame(joint, frame_pose, index_offset, p, r, M_parent, p_parent) { var joint_index; if (joint.position_animated()) { var local = this._extract_position(joint, frame_pose, index_offset); var offset_position = local[0], index_offset = local[1]; } else { var offset_position = [0, 0, 0]; } if (joint.channels.length == 0) { var local2 = 0; for (var item in this.joints) { if (joint.name == item) { joint_index = local2; } local2++; } p[joint_index] = arraySum3(p_parent, dot3vs1(M_parent, joint.offset)); r[joint_index] = mat2euler(M_parent); return index_offset; } if (joint.rotation_animated()) { var local2 = this._extract_rotation(frame_pose, index_offset, joint); var M_rotation = local2[0]; index_offset = local2[1]; } else { var M_rotation = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; } var M = multiply(M_parent, M_rotation); // https://www.khanacademy.org/math/precalculus/x9e81a4f98389efdf:matrices/x9e81a4f98389efdf:adding-and-subtracting-matrices/e/matrix_addition_and_subtraction var position = arraySum3(p_parent, dot3vs1(M_parent, joint.offset)); position = arraySum3(position, offset_position); var rotation = mat2euler(M, "rad2deg"); // just find by id var local = 0; for (const item in this.joints) { if (joint.name == item) { joint_index = local; } local++; } p[joint_index] = position; r[joint_index] = rotation; for (var c in joint.children) { index_offset = this._recursive_apply_frame(joint.children[c], frame_pose, index_offset, p, r, M, position); } return index_offset; } frame_pose(frame) { var jointLength = 0; for (var x in this.joints) { jointLength++; } var p = Array.from(Array(jointLength), () => [0, 0, 0]); var r = Array.from(Array(jointLength), () => [0, 0, 0]); var frame_pose = this.keyframes[frame]; var M_parent = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]; M_parent[0][0] = 1; M_parent[1][1] = 1; M_parent[2][2] = 1; this._recursive_apply_frame(this.root, frame_pose, 0, p, r, M_parent, [0, 0, 0]); return [p, r]; } all_frame_poses() { var jointLength = 0; for (var x in this.joints) { jointLength++; } var p = Array.from({ length: this.frames }, () => Array.from({ length: jointLength }, () => [0, 0, 0])); var r = Array.from({ length: this.frames }, () => Array.from({ length: jointLength }, () => [0, 0, 0])); for (var frame = 0; frame < this.keyframes.length; frame++) { var local3 = this.frame_pose(frame); p[frame] = local3[0]; r[frame] = local3[1]; } return [p, r]; } _plot_pose(p, r, fig, ax) { /* _plot_pose(p, r, fig=None, ax=None) { import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import axes3d, Axes3D if fig is None: fig = plt.figure() if ax is None: ax = fig.add_subplot(111, projection='3d') ax.cla() ax.scatter(p[:, 0], p[:, 2], p[:, 1]) ax.set_xlim(-30, 30) ax.set_ylim(-30, 30) ax.set_zlim(-1, 59) plt.draw() plt.pause(0.001) */ } // Meybe helps for draw // plot_frame(frame, fig=None, ax=None) { plot_frame(frame, fig, ax) { // ???? // p, (r = this.frame_pose(frame)); // this._plot_pose(p, r, fig, ax); } joint_names() { var keys = []; for (var key in this.joints) { keys.push(key); } return keys; } plot_all_frames() { /* import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import axes3d, Axes3D fig = plt.figure() ax = fig.add_subplot(111, projection='3d') for i in range(self.frames) { self.plot_frame(i, fig, ax); } */ } __repr__() { return `BVH.JS ${this.joints.keys().length} joints, ${this.frames} frames`; } } exports.MEBvh = MEBvh; },{"webgpu-matrix":21}],10:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RANDOM = exports.EPSILON = exports.ARRAY_TYPE = exports.ANGLE_ORDER = void 0; exports.equals = equals; exports.round = round; exports.setMatrixArrayType = setMatrixArrayType; exports.toDegree = toDegree; exports.toRadian = toRadian; /** * Common utilities * @module glMatrix */ // Configuration Constants var EPSILON = exports.EPSILON = 0.000001; var ARRAY_TYPE = exports.ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array; var RANDOM = exports.RANDOM = Math.random; var ANGLE_ORDER = exports.ANGLE_ORDER = "zyx"; /** * Symmetric round * see https://www.npmjs.com/package/round-half-up-symmetric#user-content-detailed-background * * @param {Number} a value to round */ function round(a) { if (a >= 0) return Math.round(a); return a % 0.5 === 0 ? Math.floor(a) : Math.round(a); } /** * Sets the type of array used when creating new vectors and matrices * * @param {Float32ArrayConstructor | ArrayConstructor} type Array type, such as Float32Array or Array */ function setMatrixArrayType(type) { exports.ARRAY_TYPE = ARRAY_TYPE = type; } var degree = Math.PI / 180; var radian = 180 / Math.PI; /** * Convert Degree To Radian * * @param {Number} a Angle in Degrees */ function toRadian(a) { return a * degree; } /** * Convert Radian To Degree * * @param {Number} a Angle in Radians */ function toDegree(a) { return a * radian; } /** * Tests whether or not the arguments have approximately the same value, within an absolute * or relative tolerance of glMatrix.EPSILON (an absolute tolerance is used for values less * than or equal to 1.0, and a relative tolerance is used for larger values) * * @param {Number} a The first number to test. * @param {Number} b The second number to test. * @param {Number} tolerance Absolute or relative tolerance (default glMatrix.EPSILON) * @returns {Boolean} True if the numbers are approximately equal, false otherwise. */ function equals(a, b) { var tolerance = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : EPSILON; return Math.abs(a - b) <= tolerance * Math.max(1, Math.abs(a), Math.abs(b)); } },{}],11:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.vec4 = exports.vec3 = exports.vec2 = exports.quat2 = exports.quat = exports.mat4 = exports.mat3 = exports.mat2d = exports.mat2 = exports.glMatrix = void 0; var glMatrix = _interopRequireWildcard(require("./common.js")); exports.glMatrix = glMatrix; var mat2 = _interopRequireWildcard(require("./mat2.js")); exports.mat2 = mat2; var mat2d = _interopRequireWildcard(require("./mat2d.js")); exports.mat2d = mat2d; var mat3 = _interopRequireWildcard(require("./mat3.js")); exports.mat3 = mat3; var mat4 = _interopRequireWildcard(require("./mat4.js")); exports.mat4 = mat4; var quat = _interopRequireWildcard(require("./quat.js")); exports.quat = quat; var quat2 = _interopRequireWildcard(require("./quat2.js")); exports.quat2 = quat2; var vec2 = _interopRequireWildcard(require("./vec2.js")); exports.vec2 = vec2; var vec3 = _interopRequireWildcard(require("./vec3.js")); exports.vec3 = vec3; var vec4 = _interopRequireWildcard(require("./vec4.js")); exports.vec4 = vec4; function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnPr