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
990 lines (916 loc) • 38.6 kB
JavaScript
import {mat4, vec3} from 'wgpu-matrix';
import {Position, Rotation} from "../matrix-class";
import {degToRad, genName, LOG_FUNNY_SMALL} from '../utils';
import {fragmentVideoWGSL} from '../../shaders/fragment.video.wgsl';
import {PointerEffect} from '../effects/pointerEffect';
import MaterialsInstanced from './materials-instanced';
import {vertexWGSLInstanced} from '../../shaders/instanced/vertex.instanced.wgsl';
import {BVHPlayerInstances} from '../loaders/bvh-instaced';
import {GenGeo} from '../effects/gen';
import {HPBarEffect} from '../effects/energy-bar';
import {MANABarEffect} from '../effects/mana-bar';
import {FlameEffect} from '../effects/flame';
import {FlameEmitter} from '../effects/flame-emmiter';
import {GenGeoTexture} from '../effects/gen-tex';
import {GenGeoTexture2} from '../effects/gen-tex2';
export default class MEMeshObjInstances extends MaterialsInstanced {
constructor(canvas, device, context, o, inputHandler, globalAmbient, _glbFile = null, primitiveIndex = null, skinnedNodeIndex = null) {
super(device, o.material, _glbFile);
if(typeof o.name === 'undefined') o.name = genName(3);
if(typeof o.raycast === 'undefined') {
this.raycast = {enabled: false, radius: 2};
} else {
this.raycast = o.raycast;
}
// console.info('WHAT IS [MEMeshObjInstances]', o.pointerEffect)
this.pointerEffect = o.pointerEffect;
this.name = o.name;
this.done = false;
this.canvas = canvas;
this.device = device;
this.context = context;
this.entityArgPass = o.entityArgPass;
this.clearColor = "red";
this.video = null;
this.FINISH_VIDIO_INIT = false;
this.globalAmbient = [...globalAmbient];
this.blendInstanced = false;
if(typeof o.material.useTextureFromGlb === 'undefined' ||
typeof o.material.useTextureFromGlb !== "boolean") {
o.material.useTextureFromGlb = false;
}
// console.log('Material class arg:', o.material)
this.material = o.material;
// Mesh stuff - for single mesh or t-posed (fiktive-first in loading order)
this.mesh = o.mesh;
if(_glbFile != null) {
if(typeof this.mesh == 'undefined') {
this.mesh = {};
this.mesh.feedFromRealGlb = true;
}
// V
const verView = _glbFile.skinnedMeshNodes[skinnedNodeIndex].mesh.primitives[primitiveIndex].positions.view;
const byteOffsetV = verView.byteOffset || 0;
const byteLengthV = verView.buffer.byteLength;
const vertices = new Float32Array(
verView.buffer.buffer,
byteOffsetV,
byteLengthV / 4
);
this.mesh.vertices = vertices;
//N
const norView = _glbFile.skinnedMeshNodes[skinnedNodeIndex].mesh.primitives[primitiveIndex].normals.view;
const normalsUint8 = norView.buffer;
const byteOffsetN = norView.byteOffset || 0; // if your loader provides it
const byteLengthN = normalsUint8.byteLength;
const normals = new Float32Array(
normalsUint8.buffer,
byteOffsetN,
byteLengthN / 4
);
this.mesh.vertexNormals = normals;
//UV
let accessor = _glbFile.skinnedMeshNodes[skinnedNodeIndex].mesh.primitives[primitiveIndex].texcoords[0];
const bufferView = accessor.view;
const byteOffset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
const count = accessor.count * 2; // VEC2 = 2 floats per vertex
const uvFloatArray = new Float32Array(bufferView.buffer.buffer, byteOffset, count);
this.mesh.uvs = uvFloatArray;
this.mesh.textures = uvFloatArray;
// I
let binaryI = _glbFile.skinnedMeshNodes[skinnedNodeIndex].mesh.primitives[primitiveIndex].indices;
const indicesView = binaryI.view;
const indicesUint8 = indicesView.buffer;
const byteOffsetI = indicesView.byteOffset || 0;
const byteLengthI = indicesUint8.byteLength;
// Decide on type from accessor.componentType
// (5121 = UNSIGNED_BYTE, 5123 = UNSIGNED_SHORT, 5125 = UNSIGNED_INT)
let indicesArray;
switch(binaryI.componentType) {
case 5121: // UNSIGNED_BYTE
indicesArray = new Uint8Array(indicesUint8.buffer, byteOffsetI, byteLengthI);
break;
case 5123: // UNSIGNED_SHORT
indicesArray = new Uint16Array(indicesUint8.buffer, byteOffsetI, byteLengthI / 2);
break;
case 5125: // UNSIGNED_INT
indicesArray = new Uint32Array(indicesUint8.buffer, byteOffsetI, byteLengthI / 4);
break;
default:
throw new Error("Unknown index componentType");
}
this.mesh.indices = indicesArray;
// W
let weightsView = _glbFile.skinnedMeshNodes[skinnedNodeIndex].mesh.primitives[primitiveIndex].weights.view;
this.mesh.weightsView = weightsView;
let primitive = _glbFile.skinnedMeshNodes[skinnedNodeIndex].mesh.primitives[primitiveIndex];
let finalRoundedWeights = this.getAccessorArray(_glbFile, primitive.weights.weightsAccessIndex);
const weightsArray = finalRoundedWeights;
// Normalize each group of 4
for(let i = 0;i < weightsArray.length;i += 4) {
const sum = weightsArray[i] + weightsArray[i + 1] + weightsArray[i + 2] + weightsArray[i + 3];
if(sum > 0) {
const inv = 1 / sum;
weightsArray[i] *= inv;
weightsArray[i + 1] *= inv;
weightsArray[i + 2] *= inv;
weightsArray[i + 3] *= inv;
} else {
weightsArray[i] = 1; weightsArray[i + 1] = 0;
weightsArray[i + 2] = 0; weightsArray[i + 3] = 0;
}
}
for(let i = 0;i < weightsArray.length;i += 4) {
const s = weightsArray[i] + weightsArray[i + 1] + weightsArray[i + 2] + weightsArray[i + 3];
if(Math.abs(s - 1.0) > 0.001) console.warn("Weight not normalized!", i, s);
}
this.mesh.weightsBuffer = this.device.createBuffer({
label: "weightsBuffer real data",
size: weightsArray.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true,
});
new Float32Array(this.mesh.weightsBuffer.getMappedRange()).set(weightsArray);
this.mesh.weightsBuffer.unmap();
let jointsView = _glbFile.skinnedMeshNodes[skinnedNodeIndex].mesh.primitives[primitiveIndex].joints.view;
this.mesh.jointsView = jointsView;
// Create typed array from the buffer (Uint16Array or Uint8Array depending on GLB)
let jointsArray16 = new Uint16Array(
jointsView.buffer,
jointsView.byteOffset || 0,
jointsView.byteLength / 2 // in Uint16 elements
);
const jointsArray32 = new Uint32Array(jointsArray16.length);
for(let i = 0;i < jointsArray16.length;i++) {
jointsArray32[i] = jointsArray16[i];
}
// Create GPU buffer for joints
this.mesh.jointsBuffer = this.device.createBuffer({
label: "jointsBuffer[real-data]",
size: jointsArray32.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true,
});
// Upload the data to GPU
new Uint32Array(this.mesh.jointsBuffer.getMappedRange()).set(jointsArray32);
this.mesh.jointsBuffer.unmap();
// TANGENTS
let tangentArray = null;
if(_glbFile.skinnedMeshNodes[skinnedNodeIndex].mesh.primitives[primitiveIndex].tangents) {
const tangentView = _glbFile.skinnedMeshNodes[skinnedNodeIndex].mesh.primitives[primitiveIndex].tangents.view;
const byteOffsetT = tangentView.byteOffset || 0;
const byteLengthT = tangentView.buffer.byteLength;
tangentArray = new Float32Array(
tangentView.buffer,
byteOffsetT,
byteLengthT / 4
);
this.mesh.tangents = tangentArray;
this.mesh.tangentsBuffer = this.device.createBuffer({
label: "tangentsBuffer[real-data]",
size: tangentArray.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true,
});
new Float32Array(this.mesh.tangentsBuffer.getMappedRange()).set(tangentArray);
this.mesh.tangentsBuffer.unmap();
} else {
// 🟢 dummy fallback
const dummyTangents = new Float32Array(this.mesh.vertices.length / 3 * 4);
for(let i = 0;i < dummyTangents.length;i += 4) {
dummyTangents[i + 0] = 1.0; // T = (1,0,0)
dummyTangents[i + 1] = 0.0;
dummyTangents[i + 2] = 0.0;
dummyTangents[i + 3] = 1.0; // handedness
}
this.mesh.tangentsBuffer = this.device.createBuffer({
label: "tangentsBuffer dummy",
size: dummyTangents.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true,
});
new Float32Array(this.mesh.tangentsBuffer.getMappedRange()).set(dummyTangents);
this.mesh.tangentsBuffer.unmap();
console.warn("GLTF primitive has no TANGENT attribute (normal map won’t work properly).");
}
} else {
this.mesh.uvs = this.mesh.textures;
}
// console.log(`%cMesh: ${o.name}`, LOG_FUNNY_SMALL);
// ObjSequence animation
if(typeof o.objAnim !== 'undefined' && o.objAnim != null) {
this.objAnim = o.objAnim;
for(var key in this.objAnim.animations) {
if(key != 'active') this.objAnim.animations[key].speedCounter = 0;
}
console.log(`%c Mesh objAnim exist: ${o.objAnim}`, LOG_FUNNY_SMALL);
this.drawElements = this.drawElementsAnim;
}
this.inputHandler = inputHandler;
this.cameras = o.cameras;
this.mainCameraParams = {
type: o.mainCameraParams.type,
responseCoef: o.mainCameraParams.responseCoef
};
this.lastFrameMS = 0;
this.texturesPaths = [];
o.texturesPaths.forEach((t) => {this.texturesPaths.push(t)})
this.presentationFormat = navigator.gpu.getPreferredCanvasFormat();
this.position = new Position(o.position.x, o.position.y, o.position.z);
this.rotation = new Rotation(o.rotation.x, o.rotation.y, o.rotation.z);
this.rotation.rotationSpeed.x = o.rotationSpeed.x;
this.rotation.rotationSpeed.y = o.rotationSpeed.y;
this.rotation.rotationSpeed.z = o.rotationSpeed.z;
this.scale = o.scale;
// new dummy for skin mesh
if(!this.joints) {
const jointsData = new Uint32Array((this.mesh.vertices.length / 3) * 4);
const jointsBuffer = this.device.createBuffer({
label: "jointsBuffer",
size: jointsData.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true,
});
new Uint32Array(jointsBuffer.getMappedRange()).set(jointsData);
jointsBuffer.unmap();
this.joints = {
data: jointsData,
buffer: jointsBuffer,
stride: 16, // vec4<u32>
};
const numVerts = this.mesh.vertices.length / 3;
// Weights data (vec4<f32>) – default all weight to bone 0
const weightsData = new Float32Array(numVerts * 4);
for(let i = 0;i < numVerts;i++) {
weightsData[i * 4 + 0] = 1.0; // 100% influence of bone 0
weightsData[i * 4 + 1] = 0.0;
weightsData[i * 4 + 2] = 0.0;
weightsData[i * 4 + 3] = 0.0;
}
// GPU buffer
const weightsBuffer = this.device.createBuffer({
label: "weightsBuffer dummy",
size: weightsData.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true,
});
new Float32Array(weightsBuffer.getMappedRange()).set(weightsData);
weightsBuffer.unmap();
this.weights = {
data: weightsData,
buffer: weightsBuffer,
stride: 16, // vec4<f32>
};
}
this.runProgram = () => {
return new Promise(async (resolve) => {
this.shadowDepthTextureSize = 1024;
this.modelViewProjectionMatrix = mat4.create();
this.loadTex0(this.texturesPaths).then(() => {
resolve()
})
})
}
this.runProgram().then(() => {
this.context.configure({
device: this.device,
format: this.presentationFormat,
alphaMode: 'premultiplied',
});
// Create the model vertex buffer.
this.vertexBuffer = this.device.createBuffer({
size: this.mesh.vertices.length * Float32Array.BYTES_PER_ELEMENT,
usage: GPUBufferUsage.VERTEX,
mappedAtCreation: true,
});
{
new Float32Array(this.vertexBuffer.getMappedRange()).set(this.mesh.vertices);
this.vertexBuffer.unmap();
}
// Create the model vertex buffer.
this.vertexNormalsBuffer = this.device.createBuffer({
size: this.mesh.vertexNormals.length * Float32Array.BYTES_PER_ELEMENT,
usage: GPUBufferUsage.VERTEX,
mappedAtCreation: true,
});
{
new Float32Array(this.vertexNormalsBuffer.getMappedRange()).set(this.mesh.vertexNormals);
this.vertexNormalsBuffer.unmap();
}
this.vertexTexCoordsBuffer = this.device.createBuffer({
size: this.mesh.textures.length * Float32Array.BYTES_PER_ELEMENT,
usage: GPUBufferUsage.VERTEX,
mappedAtCreation: true,
});
{
new Float32Array(this.vertexTexCoordsBuffer.getMappedRange()).set(this.mesh.textures);
this.vertexTexCoordsBuffer.unmap();
}
// Create the model index buffer.
this.indexCount = this.mesh.indices.length;
const indexCount = this.mesh.indices.length;
const size = Math.ceil(indexCount * Uint16Array.BYTES_PER_ELEMENT / 4) * 4;
this.indexBuffer = this.device.createBuffer({
size,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true
});
new Uint16Array(this.indexBuffer.getMappedRange()).set(this.mesh.indices);
this.indexBuffer.unmap();
this.indexCount = indexCount;
let glbInfo = {
arrayStride: 4 * 4, // vec4<f32> = 4 * 4 bytes
attributes: [{format: 'float32x4', offset: 0, shaderLocation: 4}]
}
this.vertexBuffers = [
{
arrayStride: Float32Array.BYTES_PER_ELEMENT * 3,
attributes: [
{
// position
shaderLocation: 0,
offset: 0,
format: "float32x3",
}
],
},
{
arrayStride: Float32Array.BYTES_PER_ELEMENT * 3,
attributes: [
{
// normal
shaderLocation: 1,
offset: 0,
format: "float32x3",
},
],
},
{
arrayStride: Float32Array.BYTES_PER_ELEMENT * 2,
attributes: [
{
// uvs
shaderLocation: 2,
offset: 0,
format: "float32x2",
},
],
},
// joint indices
{
arrayStride: 4 * 4, // vec4<u32> = 4 * 4 bytes
attributes: [{format: 'uint32x4', offset: 0, shaderLocation: 3}]
},
// weights
glbInfo,
];
if(this.mesh.tangentsBuffer) {
this.vertexBuffers.push({
arrayStride: 4 * 4,
attributes: [
{shaderLocation: 5, format: "float32x4", offset: 0}
]
});
} else {
// for non glb - non skinned use basic shaders
}
// Note: The frontFace and cullMode values have no effect on the
// "point-list", "line-list", or "line-strip" topologies.
this.primitive = {
topology: 'triangle-list',
cullMode: 'back', // typical for shadow passes
frontFace: 'ccw'
}
// Selected effect
this.selectedBuffer = device.createBuffer({
size: 4, // just one float
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
this.selectedBindGroupLayout = device.createBindGroupLayout({
entries: [
{binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: {}},
],
});
this.selectedBindGroup = device.createBindGroup({
layout: this.selectedBindGroupLayout,
entries: [{binding: 0, resource: {buffer: this.selectedBuffer}}],
});
this.setSelectedEffect = (selected = false) => {
this.device.queue.writeBuffer(this.selectedBuffer, 0, new Float32Array([selected ? 1.0 : 0.0]));
};
// 0 default
this.setSelectedEffect();
// Create a bind group layout which holds the scene uniforms and
// the texture+sampler for depth. We create it manually because the WebPU
// implementation doesn't infer this from the shader (yet).
this.createLayoutForRender();
// EDIT INSTANCED PART
this.instanceTargets = [];
this.lerpSpeed = 0.05;
this.lerpSpeedAlpha = 0.05;
this.maxInstances = 5;
this.instanceCount = 2;
this.floatsPerInstance = 16 + 4;
for(let x = 0;x < this.maxInstances;x++) {
this.instanceTargets.push({
index: x,
position: [0, 0, 0],
currentPosition: [0, 0, 0],
scale: [1, 1, 1],
currentScale: [1, 1, 1],
color: [0.6, 0.8, 1.0, 0.4],
currentColor: [0.6, 0.8, 1.0, 0.4],
});
}
this.instanceData = new Float32Array(this.instanceCount * this.floatsPerInstance);
this.instanceBuffer = device.createBuffer({
size: this.instanceData.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
this.updateInstanceData = (modelMatrix) => {
// original (base instance)
this.instanceData.set(modelMatrix, 0);
this.instanceData.set([1, 1, 1, 1], 16);
// instanced clones
for(let i = 1;i < this.instanceCount;i++) {
const t = this.instanceTargets[i];
const ghost = new Float32Array(modelMatrix);
// --- Smooth interpolate position
for(let j = 0;j < 3;j++) {
t.currentPosition[j] += (t.position[j] - t.currentPosition[j]) * this.lerpSpeed;
t.currentScale[j] += (t.scale[j] - t.currentScale[j]) * this.lerpSpeed;
t.currentColor[j] += (t.color[j] - t.currentColor[j]) * this.lerpSpeed;
if(j == 2) {
t.currentColor[j + 1] += (t.color[j + 1] - t.currentColor[j + 1]) * this.lerpSpeedAlpha;
}
}
// Apply smoothed transforms
ghost[0] *= t.currentScale[0];
ghost[5] *= t.currentScale[1];
ghost[10] *= t.currentScale[2];
// pos
ghost[12] += t.currentPosition[0]; // X
ghost[13] += t.currentPosition[1]; // Y
ghost[14] += t.currentPosition[2]; // Z
// t.color[0] += t.currentColor[0]//r;
// t.color[1] += t.currentColor[1]//r;
// t.color[2] += t.currentColor[2]//r;
// t.color[3] += t.currentColor[3]//r;
// Write instance matrix + color
const offset = 20 * i;
this.instanceData.set(ghost, offset);
this.instanceData.set(t.currentColor, offset + 16);
}
device.queue.writeBuffer(this.instanceBuffer, 0, this.instanceData);
};
this.updateInstances = (newCount) => {
if(newCount > this.maxInstances) {
console.error(`Instance count ${newCount} exceeds buffer max ${this.maxInstances}`);
return;
}
this.instanceCount = newCount;
this.instanceData = new Float32Array(this.instanceCount * this.floatsPerInstance);
this.instanceBuffer = device.createBuffer({
size: this.instanceData.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
let m = this.getModelMatrix(this.position);
this.updateInstanceData(m);
this.modelBindGroupInstanced = this.device.createBindGroup({
label: 'modelBindGroup in mesh [instanced]',
layout: this.uniformBufferBindGroupLayoutInstanced,
entries: [ //
{binding: 0, resource: {buffer: this.instanceBuffer, }},
{binding: 1, resource: {buffer: this.bonesBuffer}}
],
});
};
this.updateMaxInstances = (newMax) => {
this.maxInstances = newMax;
for(let x = 0;x < this.maxInstances;x++) {
this.instanceTargets.push({
index: x,
position: [0, 0, 0],
currentPosition: [0, 0, 0],
scale: [1, 1, 1],
currentScale: [1, 1, 1],
color: [0.6, 0.8, 1.0, 0.4],
currentColor: [0.6, 0.8, 1.0, 0.4],
});
}
}
// end of instanced
this.modelUniformBuffer = this.device.createBuffer({
size: 4 * 16, // 4x4 matrix
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
this.sceneUniformBuffer = this.device.createBuffer({
label: 'sceneUniformBuffer per mesh',
size: 176,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
// test MUST BE IF
this.uniformBufferBindGroupLayoutInstanced = this.device.createBindGroupLayout({
label: 'uniformBufferBindGroupLayout in mesh [instanced]',
entries: [
{binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: {type: "read-only-storage"}},
{binding: 1, visibility: GPUShaderStage.VERTEX, buffer: {type: 'uniform'}, },
],
});
this.uniformBufferBindGroupLayout = this.device.createBindGroupLayout({
label: 'uniformBufferBindGroupLayout in mesh [regular]',
entries: [
{
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: {
type: 'uniform',
},
},
{
binding: 1,
visibility: GPUShaderStage.VERTEX,
buffer: {
type: 'uniform',
},
},
],
});
// dummy for non skin mesh like this class
function alignTo256(n) {
return Math.ceil(n / 256) * 256;
}
let MAX_BONES = 100;
this.MAX_BONES = MAX_BONES;
this.bonesBuffer = device.createBuffer({
label: "bonesBuffer",
size: alignTo256(64 * MAX_BONES),
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
const bones = new Float32Array(this.MAX_BONES * 16);
for(let i = 0;i < this.MAX_BONES;i++) {
// identity matrices
bones.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], i * 16);
}
this.device.queue.writeBuffer(this.bonesBuffer, 0, bones);
this.modelBindGroup = this.device.createBindGroup({
label: 'modelBindGroup in mesh',
layout: this.uniformBufferBindGroupLayout,
entries: [ //
{binding: 0, resource: {buffer: this.modelUniformBuffer, }},
{binding: 1, resource: {buffer: this.bonesBuffer}}
],
});
this.modelBindGroupInstanced = this.device.createBindGroup({
label: 'modelBindGroup in mesh [instanced]',
layout: this.uniformBufferBindGroupLayoutInstanced,
entries: [ //
{binding: 0, resource: {buffer: this.instanceBuffer, }},
{binding: 1, resource: {buffer: this.bonesBuffer}}
],
});
this.mainPassBindGroupLayout = this.device.createBindGroupLayout({
label: 'mainPassBindGroupLayout mesh [instaced]',
entries: [
{binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: {sampleType: 'depth'}},
{binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: {type: 'comparison'}},
],
});
this.effects = {};
// console.log('>>>>>>>>>>>>>EFFECTS>>>>>>>>>>>>>>>>>>>>>>>')
if(this.pointerEffect && this.pointerEffect.enabled === true) {
let pf = navigator.gpu.getPreferredCanvasFormat();
if(typeof this.pointerEffect.pointer !== 'undefined' && this.pointerEffect.pointer == true) {
this.effects.pointer = new PointerEffect(device, pf, this, true);
}
if(typeof this.pointerEffect.ballEffect !== 'undefined' && this.pointerEffect.ballEffect == true) {
this.effects.ballEffect = new GenGeo(device, pf, 'sphere');
}
if(typeof this.pointerEffect.energyBar !== 'undefined' && this.pointerEffect.energyBar == true) {
this.effects.energyBar = new HPBarEffect(device, pf);
this.effects.manaBar = new MANABarEffect(device, pf);
}
if(typeof this.pointerEffect.flameEffect !== 'undefined' && this.pointerEffect.flameEffect == true) {
this.effects.flameEffect = new FlameEffect(device, pf);
}
if(typeof this.pointerEffect.flameEmitter !== 'undefined' && this.pointerEffect.flameEmitter == true) {
this.effects.flameEmitter = new FlameEmitter(device, pf);
}
if(typeof this.pointerEffect.circlePlane !== 'undefined' && this.pointerEffect.circlePlane == true) {
this.effects.circlePlane = new GenGeo(device, pf, 'circlePlane');
}
if(typeof this.pointerEffect.circlePlaneTex !== 'undefined' && this.pointerEffect.circlePlaneTex == true) {
this.effects.circlePlaneTex = new GenGeoTexture(device, pf, 'ring', this.pointerEffect.circlePlaneTexPath);
}
if(typeof this.pointerEffect.circle !== 'undefined' && this.pointerEffect.circlePlaneTexPath !== 'undefined') {
this.effects.circle = new GenGeoTexture2(device, pf, 'circle2', this.pointerEffect.circlePlaneTexPath);
}
}
// Rotates the camera around the origin based on time.
this.getTransformationMatrix = (mainRenderBundle, spotLight, index) => {
const now = Date.now();
const dt = (now - this.lastFrameMS) / this.mainCameraParams.responseCoef;
this.lastFrameMS = now;
const camera = this.cameras[this.mainCameraParams.type];
if(index == 0) camera.update(dt, inputHandler());
const camVP = mat4.multiply(camera.projectionMatrix, camera.view);
const sceneData = new Float32Array(44);
// Light VP
sceneData.set(spotLight.viewProjMatrix, 0);
// Camera VP
sceneData.set(camVP, 16);
// Camera position + padding
sceneData.set([camera.position.x, camera.position.y, camera.position.z, 0.0], 32);
// Light position + padding
sceneData.set([spotLight.position[0], spotLight.position[1], spotLight.position[2], 0.0], 36);
sceneData.set([this.globalAmbient[0], this.globalAmbient[1], this.globalAmbient[2], 0.0], 40);
device.queue.writeBuffer(
this.sceneUniformBuffer,
0,
sceneData.buffer,
sceneData.byteOffset,
sceneData.byteLength
);
};
this.getModelMatrix = (pos) => {
let modelMatrix = mat4.identity();
mat4.translate(modelMatrix, [pos.x, pos.y, pos.z], modelMatrix);
if(this.itIsPhysicsBody) {
mat4.rotate(modelMatrix,
[this.rotation.axis.x, this.rotation.axis.y, this.rotation.axis.z],
degToRad(this.rotation.angle),
modelMatrix
);
} else {
mat4.rotateX(modelMatrix, this.rotation.getRotX(), modelMatrix);
mat4.rotateY(modelMatrix, this.rotation.getRotY(), modelMatrix);
mat4.rotateZ(modelMatrix, this.rotation.getRotZ(), modelMatrix);
}
// Apply scale if you have it, e.g.:
// console.warn('what is csle comes from user level not glb ', this.scale)
if(this.glb || this.objAnim) {
mat4.scale(modelMatrix, [this.scale[0], this.scale[1], this.scale[2]], modelMatrix);
}
return modelMatrix;
};
// looks like affect on transformations for now const 0
// const modelMatrix = mat4.translation([0, 0, 0]);
// const modelData = modelMatrix;
// this.device.queue.writeBuffer(
// this.modelUniformBuffer,
// 0,
// modelData.buffer,
// modelData.byteOffset,
// modelData.byteLength
// );
this.done = true;
try {
this.setupPipeline();
} catch(err) {console.log('err in create pipeline in init ', err)}
}).then(() => {
if(typeof this.objAnim !== 'undefined' && this.objAnim !== null) {
console.log('after all updateMeshListBuffers...')
this.updateMeshListBuffers();
}
})
}
setupPipeline = () => {
this.createBindGroupForRender();
const baseDesc = {
label: 'Mesh Pipeline Base',
layout: this.device.createPipelineLayout({
label: 'createPipelineLayout Mesh',
bindGroupLayouts: [
this.bglForRender,
this.uniformBufferBindGroupLayoutInstanced,
this.selectedBindGroupLayout
],
}),
vertex: {
entryPoint: 'main',
module: this.device.createShaderModule({
code: vertexWGSLInstanced,
}),
buffers: this.vertexBuffers,
},
fragment: {
entryPoint: 'main',
module: this.device.createShaderModule({
code: (this.isVideo == true ? fragmentVideoWGSL : this.getMaterial()),
}),
constants: {
shadowDepthTextureSize: this.shadowDepthTextureSize,
},
},
depthStencil: {
depthWriteEnabled: true,
depthCompare: 'less',
format: 'depth24plus',
},
primitive: this.primitive,
};
// --- Normal (no blending)
this.pipeline = this.device.createRenderPipeline({
...baseDesc,
label: 'Mesh Pipeline Opaque ✅',
fragment: {
...baseDesc.fragment,
targets: [{
format: this.presentationFormat,
blend: undefined,
}],
},
});
// --- Blended (alpha)
this.pipelineBlended = this.device.createRenderPipeline({
...baseDesc,
label: 'Mesh Pipeline Blended ✅',
fragment: {
...baseDesc.fragment,
targets: [{
format: this.presentationFormat,
blend: {
color: {
srcFactor: 'src-alpha',
dstFactor: 'one-minus-src-alpha',
operation: 'add',
},
alpha: {
srcFactor: 'one',
dstFactor: 'one-minus-src-alpha',
operation: 'add',
},
},
}],
},
depthStencil: {
depthWriteEnabled: false, // <<< disable depth write for transparency
depthCompare: 'less',
format: 'depth24plus',
},
});
// console.log('✅Pipelines done');
};
updateModelUniformBuffer = () => {
// if(this.done == false) return;
// Per-object model matrix only
// const modelMatrix = this.getModelMatrix(this.position);
// this.device.queue.writeBuffer(
// this.modelUniformBuffer,
// 0,
// modelMatrix.buffer,
// modelMatrix.byteOffset,
// modelMatrix.byteLength
// );
}
createGPUBuffer(dataArray, usage) {
if(!dataArray || typeof dataArray.length !== 'number') {
throw new Error('Invalid data array passed to createGPUBuffer');
}
const size = dataArray.length * dataArray.BYTES_PER_ELEMENT;
if(!Number.isFinite(size) || size <= 0) {
throw new Error(`Invalid buffer size: ${size}`);
}
const buffer = this.device.createBuffer({
size,
usage,
mappedAtCreation: true,
});
const writeArray = dataArray.constructor === Float32Array
? new Float32Array(buffer.getMappedRange())
: new Uint16Array(buffer.getMappedRange());
writeArray.set(dataArray);
buffer.unmap();
return buffer;
}
updateMeshListBuffers() {
for(const key in this.objAnim.meshList) {
const mesh = this.objAnim.meshList[key];
mesh.vertexBuffer = this.device.createBuffer({
size: mesh.vertices.length * Float32Array.BYTES_PER_ELEMENT,
usage: GPUBufferUsage.VERTEX,
mappedAtCreation: true,
});
new Float32Array(mesh.vertexBuffer.getMappedRange()).set(mesh.vertices);
mesh.vertexBuffer.unmap();
// Normals
mesh.vertexNormalsBuffer = this.device.createBuffer({
size: mesh.vertexNormals.length * Float32Array.BYTES_PER_ELEMENT,
usage: GPUBufferUsage.VERTEX,
mappedAtCreation: true,
});
new Float32Array(mesh.vertexNormalsBuffer.getMappedRange()).set(mesh.vertexNormals);
mesh.vertexNormalsBuffer.unmap();
// UVs
mesh.vertexTexCoordsBuffer = this.device.createBuffer({
size: mesh.textures.length * Float32Array.BYTES_PER_ELEMENT,
usage: GPUBufferUsage.VERTEX,
mappedAtCreation: true,
});
new Float32Array(mesh.vertexTexCoordsBuffer.getMappedRange()).set(mesh.textures);
mesh.vertexTexCoordsBuffer.unmap();
// Indices
const indexCount = mesh.indices.length;
const indexSize = Math.ceil(indexCount * Uint16Array.BYTES_PER_ELEMENT / 4) * 4;
mesh.indexBuffer = this.device.createBuffer({
size: indexSize,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true,
});
new Uint16Array(mesh.indexBuffer.getMappedRange()).set(mesh.indices);
mesh.indexBuffer.unmap();
mesh.indexCount = indexCount;
}
}
drawElements = (pass, lightContainer) => {
if(this.isVideo) {this.updateVideoTexture()}
pass.setBindGroup(0, this.sceneBindGroupForRender);
if(this instanceof BVHPlayerInstances) {
pass.setBindGroup(1, this.modelBindGroupInstanced);
} else {
pass.setBindGroup(1, this.modelBindGroup);
}
// Bind each light’s shadow texture & sampler
if(this.isVideo == false) {
let bindIndex = 2;
for(const light of lightContainer) {
pass.setBindGroup(bindIndex++, light.getMainPassBindGroup(this));
}
}
if(this.selectedBindGroup) {
pass.setBindGroup(2, this.selectedBindGroup);
}
pass.setVertexBuffer(0, this.vertexBuffer);
pass.setVertexBuffer(1, this.vertexNormalsBuffer);
pass.setVertexBuffer(2, this.vertexTexCoordsBuffer);
if(this.joints) {
if(this.constructor.name === "BVHPlayer" || this.constructor.name === "BVHPlayerInstances") {
pass.setVertexBuffer(3, this.mesh.jointsBuffer); // real
pass.setVertexBuffer(4, this.mesh.weightsBuffer); //real
} else {
pass.setVertexBuffer(3, this.joints.buffer); // new dummy
pass.setVertexBuffer(4, this.weights.buffer); // new dummy
}
}
if(this.mesh.tangentsBuffer) {
pass.setVertexBuffer(5, this.mesh.tangentsBuffer);
}
pass.setIndexBuffer(this.indexBuffer, 'uint16');
// pass.drawIndexed(this.indexCount, this.instanceCount, 0, 0, 0);
pass.drawIndexed(this.indexCount, 1, 0, 0, 0);
// pipelineBlended
if(this.blendInstanced == true) pass.setPipeline(this.pipelineBlended)
else pass.setPipeline(this.pipeline);
for(var ins = 1;ins < this.instanceCount;ins++) {
pass.drawIndexed(this.indexCount, 1, 0, 0, ins);
}
}
drawElementsAnim = (renderPass, lightContainer) => {
if(!this.sceneBindGroupForRender || !this.modelBindGroup) {console.log(' NULL 1'); return;}
if(!this.objAnim.meshList[this.objAnim.id + this.objAnim.currentAni]) {console.log(' NULL 2'); return;}
renderPass.setBindGroup(0, this.sceneBindGroupForRender);
renderPass.setBindGroup(1, this.modelBindGroup);
const mesh = this.objAnim.meshList[this.objAnim.id + this.objAnim.currentAni];
if(this.isVideo == false) {
let bindIndex = 2; // start after UBO & model
for(const light of lightContainer) {
renderPass.setBindGroup(bindIndex++, light.getMainPassBindGroup(this));
}
}
renderPass.setVertexBuffer(0, mesh.vertexBuffer);
renderPass.setVertexBuffer(1, mesh.vertexNormalsBuffer);
renderPass.setVertexBuffer(2, mesh.vertexTexCoordsBuffer);
if(this.constructor.name === "BVHPlayer") {
renderPass.setVertexBuffer(3, this.mesh.jointsBuffer); // real
renderPass.setVertexBuffer(4, this.mesh.weightsBuffer);// real
} else {
// dummy
renderPass.setVertexBuffer(3, this.joints.buffer); // dummy
renderPass.setVertexBuffer(4, this.weights.buffer); // dummy
}
renderPass.setIndexBuffer(mesh.indexBuffer, 'uint16');
renderPass.drawIndexed(mesh.indexCount);
if(this.objAnim.playing == true) {
if(this.objAnim.animations[this.objAnim.animations.active].speedCounter >= this.objAnim.animations[this.objAnim.animations.active].speed) {
this.objAnim.currentAni++;
this.objAnim.animations[this.objAnim.animations.active].speedCounter = 0;
} else {
this.objAnim.animations[this.objAnim.animations.active].speedCounter++;
}
if(this.objAnim.currentAni >= this.objAnim.animations[this.objAnim.animations.active].to) {
this.objAnim.currentAni = this.objAnim.animations[this.objAnim.animations.active].from;
}
}
}
drawShadows = (shadowPass) => {
shadowPass.setVertexBuffer(0, this.vertexBuffer);
shadowPass.setVertexBuffer(1, this.vertexNormalsBuffer);
shadowPass.setVertexBuffer(2, this.vertexTexCoordsBuffer);
shadowPass.setIndexBuffer(this.indexBuffer, 'uint16');
if(this instanceof BVHPlayerInstances) {
shadowPass.drawIndexed(this.indexCount, this.instanceCount, 0, 0, 0);
} else {
shadowPass.drawIndexed(this.indexCount);
}
}
}