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
800 lines (740 loc) • 31.1 kB
JavaScript
import {mat4, vec3} from "wgpu-matrix";
import MEBall from "./engine/ball.js";
import MECube from './engine/cube.js';
import {ArcballCamera, RPGCamera, WASDCamera} from "./engine/engine.js";
import {createInputHandler} from "./engine/engine.js";
import MEMeshObj from "./engine/mesh-obj.js";
import MatrixAmmo from "./physics/matrix-ammo.js";
import {LOG_FUNNY_SMALL, 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";
import {SpotLight} from "./engine/lights.js";
import {BVHPlayer} from "./engine/loaders/bvh.js";
import {BVHPlayerInstances} from "./engine/loaders/bvh-instaced.js";
import {Editor} from "./tools/editor/editor.js";
/**
* @description
* Main engine root class.
* @author Nikola Lukic 2025
* @email zlatnaspirala@gmail.com
* @web https://maximumroulette.com
* @github zlatnaspirala
*/
export default class MatrixEngineWGPU {
mainRenderBundle = [];
lightContainer = [];
frame = () => {};
entityHolder = [];
lastTime = 0;
entityArgPass = {
loadOp: 'clear',
storeOp: 'store',
depthLoadOp: 'clear',
depthStoreOp: 'store'
}
// matrixAmmo = new MatrixAmmo();
matrixSounds = new MatrixSounds();
constructor(options, callback) {
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
}
}
if(typeof options.dontUsePhysics == 'undefined') {
this.matrixAmmo = new MatrixAmmo();
}
this.editor = undefined;
if(typeof options.useEditor !== "undefined") {
this.editor = new Editor(this);
}
window.addEventListener('keydown', e => {
if(e.code == "F4") {
e.preventDefault();
mb.error(`Activated WebEditor, you can use it infly there is no saves for now.`);
app.activateEditor();
return false;
}
});
this.activateEditor = () => {
if(this.editor == null || typeof this.editor === 'undefined') {
this.editor = new Editor(this);
this.editor.editorHud.updateSceneContainer();
}
};
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);
this.mainCameraParams = {
type: this.options.mainCameraParams.type,
responseCoef: this.options.mainCameraParams.responseCoef
};
this.cameras = {
arcball: new ArcballCamera({position: initialCameraPosition}),
WASD: new WASDCamera({position: initialCameraPosition, canvas: canvas}),
RPG: new RPGCamera({position: initialCameraPosition, canvas: canvas}),
};
this.label = new MultiLang()
if(urlQuery.lang != null) {
this.label.loadMultilang(urlQuery.lang).then((r) => {
this.label.get = r;
}).catch((r) => {
this.label.get = r;
});
} else {
this.label.loadMultilang().then((r) => {
this.label.get = r;
}).catch((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"]
});
this.context = canvas.getContext('webgpu');
// this.context = canvas.getContext('webgpu', { alphaMode: 'opaque' });
// this.context = canvas.getContext('webgpu', { alphaMode: 'premultiplied' });
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.globalAmbient = vec3.create(0.5, 0.5, 0.5);
this.MAX_SPOTLIGHTS = 20;
this.inputHandler = createInputHandler(window, canvas);
this.createGlobalStuff();
this.run(callback)
};
createGlobalStuff() {
this.spotlightUniformBuffer = this.device.createBuffer({
label: 'spotlightUniformBufferGLOBAL',
size: this.MAX_SPOTLIGHTS * 144,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
this.SHADOW_RES = 1024;
this.createTexArrayForShadows()
this.mainDepthTexture = this.device.createTexture({
size: [this.canvas.width, this.canvas.height],
format: 'depth24plus',
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
});
this.mainDepthView = this.mainDepthTexture.createView();
this.mainRenderPassDesc = {
label: 'mainRenderPassDesc',
colorAttachments: [{
view: undefined, // set each frame
loadOp: 'clear',
storeOp: 'store',
clearValue: [0.02, 0.02, 0.02, 1],
}],
depthStencilAttachment: {
view: this.mainDepthView, // fixed
depthLoadOp: 'clear',
depthStoreOp: 'store',
depthClearValue: 1.0,
},
};
// pointer effect-not in use
const depthTexture = this.device.createTexture({
size: [this.canvas.width, this.canvas.height],
format: "depth24plus",
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
this.depthTextureViewTrail = depthTexture.createView();
}
createTexArrayForShadows() {
let numberOfLights = this.lightContainer.length;
if(this.lightContainer.length == 0) {
setTimeout(() => {
// console.info('Test light again...')
this.createMe();
}, 800);
}
this.createMe = () => {
Math.max(1, this.lightContainer.length);
if(this.lightContainer.length == 0) {
setTimeout(() => {
// console.info('Create now test...')
this.createMe();
}, 800);
return;
}
// console.warn('Create this.shadowTextureArray...')
this.shadowTextureArray = this.device.createTexture({
label: `shadowTextureArray[GLOBAL] num of light ${numberOfLights}`,
size: {
width: 1024,
height: 1024,
depthOrArrayLayers: numberOfLights, // at least 1
},
dimension: '2d',
format: 'depth32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
});
this.shadowArrayView = this.shadowTextureArray.createView({
dimension: '2d-array'
});
this.shadowVideoTexture = this.device.createTexture({
size: [1024, 1024],
format: "depth32float",
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
});
this.shadowVideoView = this.shadowVideoTexture.createView({
dimension: "2d",
});
}
this.createMe();
}
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);
}
addLight(o) {
const camera = this.cameras[this.mainCameraParams.type];
let newLight = new SpotLight(camera, this.inputHandler, this.device);
this.lightContainer.push(newLight);
this.createTexArrayForShadows();
console.log(`%cAdd light: ${newLight}`, LOG_FUNNY_SMALL);
}
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.material === 'undefined') {o.material = {type: 'standard'}}
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;
if(typeof o.objAnim == 'undefined' || typeof o.objAnim == null) {
o.objAnim = null;
} else {
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 meshList');
this.meshList[k].setScale(s);
}
}
}
let AM = this.globalAmbient.slice();
let myMesh1 = new MEMeshObj(this.canvas, this.device, this.context, o, this.inputHandler, AM);
myMesh1.spotlightUniformBuffer = this.spotlightUniformBuffer;
myMesh1.clearColor = clearColor;
if(o.physics.enabled == true) {
this.matrixAmmo.addPhysics(myMesh1, o.physics)
}
this.mainRenderBundle.push(myMesh1);
if(typeof this.editor !== 'undefined') {
this.editor.editorHud.updateSceneContainer();
}
}
run(callback) {
setTimeout(() => {requestAnimationFrame(this.frame)}, 500)
setTimeout(() => {callback(this)}, 20)
}
destroyProgram = () => {
this.mainRenderBundle = [];
this.canvas.remove();
}
updateLights() {
const floatsPerLight = 36; // not 20 anymore
const data = new Float32Array(this.MAX_SPOTLIGHTS * floatsPerLight);
for(let i = 0;i < this.MAX_SPOTLIGHTS;i++) {
if(i < this.lightContainer.length) {
const buf = this.lightContainer[i].getLightDataBuffer();
data.set(buf, i * floatsPerLight);
} else {
data.set(new Float32Array(floatsPerLight), i * floatsPerLight);
}
}
this.device.queue.writeBuffer(this.spotlightUniformBuffer, 0, data.buffer);
}
frameSinglePass = () => {
if(typeof this.mainRenderBundle == 'undefined' || this.mainRenderBundle.length == 0) {
setTimeout(() => {requestAnimationFrame(this.frame)}, 100);
return;
}
this.mainRenderBundle.forEach((meItem, index) => {
if(meItem.isVideo == true) {
if(!meItem.externalTexture) { // || meItem.video.readyState < 2) {
// console.log('no rendere for video not ready')
// this.externalTexture = this.device.importExternalTexture({source: this.video});
meItem.createBindGroupForRender();
setTimeout(() => {
requestAnimationFrame(this.frame)
}, 1000)
return;
}
}
})
try {
let commandEncoder = this.device.createCommandEncoder();
this.updateLights()
// 1️⃣ Update light data (position, direction, uniforms)
for(const light of this.lightContainer) {
light.update()
this.mainRenderBundle.forEach((meItem, index) => {
meItem.updateModelUniformBuffer()
meItem.getTransformationMatrix(this.mainRenderBundle, light, index)
})
}
if(this.matrixAmmo) this.matrixAmmo.updatePhysics();
let now, deltaTime;
for(let i = 0;i < this.lightContainer.length;i++) {
const light = this.lightContainer[i];
let ViewPerLightRenderShadowPass = this.shadowTextureArray.createView({
dimension: '2d',
baseArrayLayer: i,
arrayLayerCount: 1, // must be > 0
baseMipLevel: 0,
mipLevelCount: 1,
});
const shadowPass = commandEncoder.beginRenderPass({
label: "shadowPass",
colorAttachments: [],
depthStencilAttachment: {
view: ViewPerLightRenderShadowPass,
depthLoadOp: 'clear',
depthStoreOp: 'store',
depthClearValue: 1.0,
}
});
now = performance.now() / 1000;
// shadowPass.setPipeline(light.shadowPipeline);
for(const [meshIndex, mesh] of this.mainRenderBundle.entries()) {
if(mesh instanceof BVHPlayerInstances) {
mesh.updateInstanceData(mesh.getModelMatrix(mesh.position))
shadowPass.setPipeline(light.shadowPipelineInstanced);
} else {
// must be base meshObj
shadowPass.setPipeline(light.shadowPipeline);
}
if(mesh.videoIsReady == 'NONE') {
shadowPass.setBindGroup(0, light.getShadowBindGroup(mesh, meshIndex));
// if(mesh.glb && mesh.glb.skinnedMeshNodes) {
// shadowPass.setBindGroup(1, light.getShadowBindGroup_bones(meshIndex));
// } else {
if(mesh instanceof BVHPlayerInstances) {
shadowPass.setBindGroup(1, mesh.modelBindGroupInstanced);
} else {
shadowPass.setBindGroup(1, mesh.modelBindGroup);
}
// }
mesh.drawShadows(shadowPass, light);
}
}
shadowPass.end();
}
const currentTextureView = this.context.getCurrentTexture().createView();
this.mainRenderPassDesc.colorAttachments[0].view = currentTextureView;
let pass = commandEncoder.beginRenderPass(this.mainRenderPassDesc);
// Loop over each mesh
for(const mesh of this.mainRenderBundle) {
mesh.position.update()
if(mesh.update) {
now = performance.now() / 1000; // seconds
deltaTime = now - (this.lastTime || now);
this.lastTime = now;
mesh.update(deltaTime); // glb
}
pass.setPipeline(mesh.pipeline);
if(!mesh.sceneBindGroupForRender || (mesh.FINISH_VIDIO_INIT == false && mesh.isVideo == true)) {
for(const m of this.mainRenderBundle) {
if(m.isVideo == true) {
console.log('✅shadowVideoView', this.shadowVideoView)
m.shadowDepthTextureView = this.shadowVideoView;
m.FINISH_VIDIO_INIT = true;
m.setupPipeline();
} else {
m.shadowDepthTextureView = this.shadowArrayView;
m.setupPipeline();
}
}
}
mesh.drawElements(pass, this.lightContainer);
}
pass.end();
// 3) resolve collisions AFTER positions changed
if(this.collisionSystem) this.collisionSystem.update();
// 4) render / send network updates / animations etc
// transparent pointerEffect pass (load color, load depth)
const transPassDesc = {
colorAttachments: [{view: currentTextureView, loadOp: 'load', storeOp: 'store'}],
depthStencilAttachment: {
view: this.mainDepthView,
depthLoadOp: 'load',
depthStoreOp: 'store',
depthClearValue: 1.0,
}
};
const transPass = commandEncoder.beginRenderPass(transPassDesc);
const viewProjMatrix = mat4.multiply(this.cameras[this.mainCameraParams.type].projectionMatrix,
this.cameras[this.mainCameraParams.type].view, mat4.identity());
for(const mesh of this.mainRenderBundle) {
if(mesh.effects) Object.keys(mesh.effects).forEach(effect_ => {
const effect = mesh.effects[effect_];
if(effect.enabled == false) return;
let md = mesh.getModelMatrix(mesh.position);
if(effect.updateInstanceData) effect.updateInstanceData(md);
effect.render(transPass, mesh, viewProjMatrix)
});
}
transPass.end();
this.device.queue.submit([commandEncoder.finish()]);
requestAnimationFrame(this.frame);
} catch(err) {
// console.log('%cLoop(err):' + err + " info : " + err.stack, LOG_WARN)
requestAnimationFrame(this.frame);
}
}
framePassPerObject = () => {
let commandEncoder = this.device.createCommandEncoder();
if(this.matrixAmmo.rigidBodies && this.matrixAmmo.rigidBodies.length > 0) 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);
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);
}
});
this.device.queue.submit([commandEncoder.finish()]);
requestAnimationFrame(this.frame);
}
// ---------------------------------------
addGlbObj = (o, BVHANIM, glbFile, 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.material === 'undefined') {o.material = {type: 'standard'}}
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}}
if(typeof o.pointerEffect === 'undefined') {o.pointerEffect = {enabled: false};}
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;
if(typeof o.objAnim == 'undefined' || typeof o.objAnim == null) {
o.objAnim = null;
} else {
alert('GLB not use objAnim (it is only for obj sequence). GLB use BVH skeletal for animation');
}
let skinnedNodeIndex = 0;
for(const skinnedNode of glbFile.skinnedMeshNodes) {
let c = 0;
for(const primitive of skinnedNode.mesh.primitives) {
// console.log(`count: ${c} primitive-glb: ${primitive}`);
// primitive is mesh - probably with own material . material/texture per primitive
// create scene object for each skinnedNode
o.name = o.name + "-" + skinnedNode.name + '-' + c;
const bvhPlayer = new BVHPlayer(
o,
BVHANIM,
glbFile,
c,
skinnedNodeIndex,
this.canvas,
this.device,
this.context,
this.inputHandler,
this.globalAmbient.slice());
skinnedNodeIndex++;
// console.log(`bvhPlayer!!!!!: ${bvhPlayer}`);
bvhPlayer.spotlightUniformBuffer = this.spotlightUniformBuffer;
bvhPlayer.clearColor = clearColor;
// if(o.physics.enabled == true) {
// this.matrixAmmo.addPhysics(myMesh1, o.physics)
// }
// make it soft
setTimeout(() => {this.mainRenderBundle.push(bvhPlayer)}, 800);
// this.mainRenderBundle.push(bvhPlayer)
c++;
}
}
if(typeof this.editor !== 'undefined') {
this.editor.editorHud.updateSceneContainer();
}
}
// NEW TEST INSTANCED DRAWS
addGlbObjInctance = (o, BVHANIM, glbFile, 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.material === 'undefined') {o.material = {type: 'standard'}}
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}}
if(typeof o.pointerEffect === 'undefined') {
o.pointerEffect = {
enabled: false,
pointer: false,
ballEffect: false
};
}
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;
if(typeof o.objAnim == 'undefined' || typeof o.objAnim == null) {
o.objAnim = null;
} else {
console.warn('GLB not use objAnim (it is only for obj sequence). GLB use BVH skeletal for animation');
}
let skinnedNodeIndex = 0;
for(const skinnedNode of glbFile.skinnedMeshNodes) {
let c = 0;
for(const primitive of skinnedNode.mesh.primitives) {
// console.log(`count: ${c} primitive-glb: ${primitive}`);
// primitive is mesh - probably with own material . material/texture per primitive
// create scene object for each skinnedNode
o.name = o.name + "_" + skinnedNode.name;
// maybe later add logic from constructor
// always fisrt sub mesh(skinnedmeg-vert group how comes from loaders)
if(skinnedNodeIndex == 0) {} else {
o.pointerEffect = {enabled: false};
}
const bvhPlayer = new BVHPlayerInstances(
o,
BVHANIM,
glbFile,
c,
skinnedNodeIndex,
this.canvas,
this.device,
this.context,
this.inputHandler,
this.globalAmbient.slice());
// console.log(`bvhPlayer!!!!!: ${bvhPlayer}`);
bvhPlayer.spotlightUniformBuffer = this.spotlightUniformBuffer;
bvhPlayer.clearColor = clearColor;
// if(o.physics.enabled == true) {
// this.matrixAmmo.addPhysics(myMesh1, o.physics)
// }
// make it soft
setTimeout(() => {this.mainRenderBundle.push(bvhPlayer)}, 200)
c++;
}
skinnedNodeIndex++;
}
if(typeof this.editor !== 'undefined') {
this.editor.editorHud.updateSceneContainer();
}
}
}