///
"use strict";
var createScene = function (canvas, engine) {
var scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color3(0.2, 0.4, 0.8);
var camera = new BABYLON.ArcRotateCamera("cam", 0, 0, 0, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
camera.setPosition(new BABYLON.Vector3(0, 0, -600));
var light = new BABYLON.PointLight("pl", camera.position, scene);
light.intensity = 1.0;
var mat = new BABYLON.StandardMaterial("m", scene);
mat.diffuseColor = BABYLON.Color3.Yellow();
//mat.backFaceCulling = false;
//mat.wireframe = true;
var particleNb = 40000;
var areaSize = 250.0;
var particleSize = 12.0;
// particle initialization function
var initParticle = function () {
/*
sps.particle.position.x = areaSize * (Math.random() - 0.5);
sps.particle.position.y = areaSize * (Math.random() - 0.5);
sps.particle.position.z = areaSize * (Math.random() - 0.5);
*/
sps.particle.rotation.x = 6.28 * Math.random();
sps.particle.rotation.y = 6.28 * Math.random();
sps.particle.rotation.z = 6.28 * Math.random();
sps.particle.scaling.x = particleSize * Math.random() + 0.5;
sps.particle.scaling.y = particleSize * Math.random() + 0.5;
sps.particle.scaling.z = particleSize * Math.random() + 0.5;
};
// particle custom update function
var k = 0.0;
var updateParticle = function () {
sps.particle.position.x = areaSize * Math.sin(k + sps.particle.idx);
sps.particle.position.y = areaSize * Math.sin(k * 3.0 / particleNb * (sps.particle.idx + 1)) * Math.cos(sps.particle.position.x / areaSize);
sps.particle.position.z = areaSize * Math.cos(k * 1.5 / particleNb * (sps.particle.idx + 1));
sps.particle.rotation.x += 0.01;
sps.particle.rotation.y += 0.02;
sps.particle.rotation.z += 0.05;
};
// proto sps creation
var sps = new SPS("sps", scene);
//var model = BABYLON.MeshBuilder.CreatePlane("m", {}, scene);
var model = BABYLON.MeshBuilder.CreateBox("m", {}, scene);
var model2 = BABYLON.MeshBuilder.CreatePolyhedron("m2", {
size: 0.5
}, scene);
sps.addShape(model, particleNb * 0.5);
sps.addShape(model2, particleNb * 0.5);
model.dispose();
model2.dispose();
var particles = sps.buildMesh();
particles.material = mat;
particles.alwaysSelectAsActiveMesh = true;
// init particles
sps.updateParticle = initParticle;
// animation
var initialized = false;
scene.registerBeforeRender(function () {
k += 0.005;
sps.updateParticles();
if (!initialized && sps.compiled) {
sps.updateParticle = updateParticle;
initialized = true;
};
});
return scene;
};
// =========
// Turbo SPS
// =========
var SPS = function (name, scene) {
this.name = name;
this.scene = scene;
this.particleNb = 0;
// Tmp particle object to mask the array access
this.particle = {
idx: 0, // particle index
position: BABYLON.Vector3.Zero(),
rotation: BABYLON.Vector3.Zero(),
scaling: BABYLON.Vector3.Zero(),
velocity: BABYLON.Vector3.Zero(),
color: new BABYLON.Color4(1., 1., 1., 1.),
uvs: new BABYLON.Vector4(0., 0., 1., 1.),
alive: true,
isVisible: true
};
this.stride = 9; // stride in the transforms array
this.shiftInd = 0; // indice shift
this.transforms = []; // particle transformations (posx, posy, posz, rotx, roty, rotz, scalx, scaly, scalz)
this.localPos = []; // mesh vertex local positions (x, y, z) * particle shape nb of points
this.localNor = []; // mesh local normals
this.localUVs = []; // mesh local uvs
this.localCol = []; // mesh local colors
this.indices = []; // mesh indices
this.shapeLengths = []; // length of each shape
this.posBuffer = undefined; // the vertex buffer
this.norBuffer = undefined; // the normal buffer
this.uvsBuffer = undefined // the uvs buffer
this.mesh = undefined;
this.wasmBuffer = undefined; // buffer to be passed to the wasm module
this.compiled = false;
this.wasmModule = undefined;
this.vertexLength = 0; // array length of the positions (or normals)
this.transformLength = 0; // array length of transformations
this.offset1 = 0;
this.offset2 = 0;
this.offset3 = 0;
this.offset4 = 0;
this.offset5 = 0;
this.offset6 = 0;
this.byteOffset1 = 0;
this.byteOffset2 = 0;
this.byteOffset3 = 0;
this.byteOffset4 = 0;
this.byteOffset5 = 0;
};
SPS.prototype.updateParticle = function () {
return;
};
SPS.prototype.addShape = function (mesh, nb) {
var meshInd = mesh.getIndices();
var meshPos = mesh.getVerticesData(BABYLON.VertexBuffer.PositionKind);
var meshNor = mesh.getVerticesData(BABYLON.VertexBuffer.NormalKind);
var meshUVs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind);
var meshCol = mesh.getVerticesData(BABYLON.VertexBuffer.ColorKind);
var l = (meshPos.length / 3) | 0;
for (var m = 0; m < nb; m++) {
this.addElementsToRef(meshInd, this.shiftInd, this.indices);
this.addElementsToRef(meshPos, 0, this.localPos);
this.addElementsToRef(meshNor, 0, this.localNor, );
this.addElementsToRef(meshUVs, 0, this.localUVs);
if (meshCol) {
this.addElementsToRef(meshCol, this.localCol);
}
this.shapeLengths.push(l);
this.transforms.push(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
this.shiftInd += l;
}
this.particleNb += nb;
};
SPS.prototype.addElementsToRef = function (fromArray, shift, toArray) {
for (var i = 0; i < fromArray.length; i++) {
toArray.push(fromArray[i] + shift);
}
};
SPS.prototype.buildMesh = function () {
var wasmURL = "./build/optimized.wasm";
// required minimal memory (float32 = 4 bytes, u32 = 4 bytes):
// geometry (initial positions and normals) : this.vertexLength x 4
// shapes (int : how many vertices per particle ?) : particleNb x 4
// transformations (pos, rot, scal) : this.transformLength x 4
// transformed geometry : this.vertexLength x 4
var pages = Math.ceil((this.vertexLength * 2 + this.transformLength + this.particleNb) * 4 / (64 * 1024)) + 250; // let's add some fixed extra memory
var memory = new WebAssembly.Memory({
initial: 1000
});
this.wasmBuffer = memory.buffer;
var imports = {
env: {
memory,
abort(msg, file, line, column) {
console.error("abort called at main.ts:" + line + ":" + column);
},
},
// Debug functions
console: {
logInt(idx, val) {
console.log("logInt idx : " + idx + " val : " + val);
},
logFloat(idx, val) {
console.log("logFloat idx : " + idx + " val : " + val);
},
}
};
this.posBuffer = new Float32Array(this.localPos);
this.norBuffer = new Float32Array(this.localNor);
//this.uvsBuffer = new Float32Array(this.localUVs);
this.transforms = new Float32Array(this.transforms);
var vertexData = new BABYLON.VertexData();
vertexData.indices = this.indices;
vertexData.positions = this.posBuffer;
vertexData.normals = this.norBuffer;
vertexData.uvs = this.uvsBuffer;
// colors to be done ...
var mesh = new BABYLON.Mesh(this.name, this.scene);
vertexData.applyToMesh(mesh, true);
this.mesh = mesh;
this.shapeLengths = new Uint32Array(this.shapeLengths); // turn the shapeLengths array to a typed array
// WASM Buffer population
this.vertexLength = this.localPos.length * 2; // positions + normals
this.transformLength = this.particleNb * this.stride; // a transform set per particle
// Views on different parts of the wasm buffer
// all types are 4 bytes long here
this.offset1 = this.localPos.length;
this.offset2 = this.offset1 + this.localNor.length;
this.offset3 = this.offset2 + this.shapeLengths.length;
this.offset4 = this.offset3 + this.transformLength;
this.offset5 = this.offset4 + this.localPos.length;
this.byteOffset1 = this.offset1 * 4;
this.byteOffset2 = this.offset2 * 4;
this.byteOffset3 = this.offset3 * 4;
this.byteOffset4 = this.offset4 * 4;
this.byteOffset5 = this.offset5 * 4;
WebAssembly.instantiateStreaming(fetch(wasmURL), imports).then(obj => {
this.wasmModule = obj.instance.exports;
this.compiled = true;
// All this must be AFTER the module instanciation, else something altering the data is written in the memory buffer
this.wasmPos = new Float32Array(this.wasmBuffer, 0, this.localPos.length);
this.wasmNor = new Float32Array(this.wasmBuffer, this.byteOffset1, this.localNor.length);
this.wasmShp = new Uint32Array(this.wasmBuffer, this.byteOffset2, this.shapeLengths.length);
this.wasmTransforms = new Float32Array(this.wasmBuffer, this.byteOffset3, this.transforms.length);
this.wasmTransformedPos = new Float32Array(this.wasmBuffer, this.byteOffset4, this.localPos.length);
this.wasmTransformedNor = new Float32Array(this.wasmBuffer, this.byteOffset5, this.localNor.length);
// init the buffer
this.wasmPos.set(this.posBuffer);
this.wasmNor.set(this.norBuffer);
this.wasmShp.set(this.shapeLengths);
this.wasmTransforms.set(this.transforms);
this.wasmTransformedPos.set(this.localPos);
this.wasmTransformedNor.set(this.localNor);
//this.wasmModule.testArray(this.particleNb, this.offset3);
//this.wasmModule.transform(this.particleNb, this.offset1, this.offset2, this.offset3, this.offset4, this.offset5);
});
return mesh;
};
SPS.prototype.updateParticles = function () {
if (!this.compiled) {
return;
}
var trIdx = 0; // transformation index
for (var p = 0; p < this.particleNb; p++) {
// get the current particle status
this.particle.idx = p;
// stride = 20
trIdx = p * this.stride;
this.particle.position.x = this.wasmTransforms[trIdx];
this.particle.position.y = this.wasmTransforms[trIdx + 1];
this.particle.position.z = this.wasmTransforms[trIdx + 2];
this.particle.rotation.x = this.wasmTransforms[trIdx + 3];
this.particle.rotation.y = this.wasmTransforms[trIdx + 4];
this.particle.rotation.z = this.wasmTransforms[trIdx + 5];
this.particle.scaling.x = this.wasmTransforms[trIdx + 6];
this.particle.scaling.y = this.wasmTransforms[trIdx + 7];
this.particle.scaling.z = this.wasmTransforms[trIdx + 8];
// call to particle update function
this.updateParticle();
this.wasmTransforms[trIdx] = this.particle.position.x;
this.wasmTransforms[trIdx + 1] = this.particle.position.y;
this.wasmTransforms[trIdx + 2] = this.particle.position.z;
this.wasmTransforms[trIdx + 3] = this.particle.rotation.x;
this.wasmTransforms[trIdx + 4] = this.particle.rotation.y;
this.wasmTransforms[trIdx + 5] = this.particle.rotation.z;
this.wasmTransforms[trIdx + 6] = this.particle.scaling.x;
this.wasmTransforms[trIdx + 7] = this.particle.scaling.y;
this.wasmTransforms[trIdx + 8] = this.particle.scaling.z;
}
// call the wasm function "transform" that updates the wasm buffer
this.wasmModule.transform(this.particleNb, this.offset1, this.offset2, this.offset3, this.offset4, this.offset5);
// update the actual mesh positions and normals
this.mesh.updateVerticesData(BABYLON.VertexBuffer.PositionKind, this.wasmTransformedPos, false, false);
this.mesh.updateVerticesData(BABYLON.VertexBuffer.NormalKind, this.wasmTransformedNor, false, false);
};
var init = function () {
// Canvas
var canvas = document.querySelector('#renderCanvas');
var engine = new BABYLON.Engine(canvas, true);
var scene = createScene(canvas, engine);
window.addEventListener("resize", function () {
engine.resize();
});
var limit = 20;
var count = 0;
var fps = 0;
var fpsElem = document.querySelector("#fps");
engine.runRenderLoop(function () {
count++;
scene.render();
if (count == limit) {
fps = Math.floor(engine.getFps());
fpsElem.innerHTML = fps.toString() + " fps";
count = 0;
}
});
};