====== NormalProducer and TileSampler implementation ======
/* Started on 10/02/2022 */
* Our current target on this project is to create an instance of a **TileSampler** object to attach to our terrain in our ''Test8Scene''
* But to create that TileSampler, we first need an instance of a **NormalProducer**, and we'll try to implement that here.
* Again we need a special factory to create the framebuffer for the normal producer given a normal texture: **OK**
* But now to finalize the support to create the normal producer framebuffer, we need to add the FrameBuffer methods ''setColorMask'', ''setDepthMask'' and ''setStencilMask''
* **note** for the color mask it seems we don't have support for multiple color mask in WebGL2.
* **OK!** we now have the constructor for NormalProducer ready.
* Now let's confirm we can instanciate that correctly. **OK** Working just fine ðĪŠ! [//This was almost too easy...//]
* Now that we have the NormalProducer instantiated, we should create the corresponding TileSampler object: **PK** no problem with that either: // Create the normal tile sampler:
this.DEBUG("Creating normal tile sampler...")
let ts = new TileSampler("fragmentNormalSampler", normProd);
ts.setStoreParent(false);
ts.setStoreInvisible(false);/*//*/
* So this means we are done already with the Normalproducer implementation ðĪ okay, so now I need to figure out how to integrate those tile samplers in my terrain note object ð
* I think I need a refresh on what is a "TerrainNode" and how we got that implemented so far:
* In our previous test scene (test scene 7) we had already implemented the TerrainNode object, and we where also creating an entity containing that terrain: world
.createEntity()
.addComponent('transform')
.addComponent('terrain', terrain)
.addComponent('program', p3)
.addComponent('meshbuffers', quad.getBuffers())
* Then we were also creating a system to handle that terrain entity: world.registerSystem('terrain', (entity: Entity) => {
let terrain = ECS.getTerrain(entity);
let mesh = ECS.getMesh(entity);
terrain.update(entity);
terrain.draw(entity);
});
* In that initial example we were already assigning a quad mesh to the terrain entity => Here we should also do the same, except that the quad data is a bit more complex:
* The new quas mesh contains 25 x 25 points.
* The points should cover the range [0.0,1.0]
* The indices should draw the triangles counter-clockwise: so we start with: 0 1 25 25 1 26 1 2 26 26 2 27
* => instead of loading that quad from a file we could manually generate it with a utility function: Let's create that function as a static method in a **MeshUtils** class.
* One thing I'm wondering here is on the behavior of Float32Array vs DataView.setFloat32(): in the last one we can specify the endianess to be little endian. Is that the result we get when simply using a Float32Array ? Let's write a unit test to check that. **OK** so yes: writting to a Float32Array is the same as setFloat32 on DataView in little endian.
* So we can just as well construct a Float32Array view on the vertices array to fill the data ð!
* => I have now created the function **MeshUtils.createPlane(width, settings=null)** where ''width'' is the number of points on one side of the plane grid.
* We should now try using that function directly when building our scene, but to achieve that we need to be able to create a 'procedural-plane' using the resource manager: **OK**: just added that section in the ResourceManager.loadMesh() function: // Check if the mesh is procedural:
if (name.startsWith('%')) {
let parts = name.split('-');
let m: Mesh = null;
// Check if we are trying to create a plane:
if (parts[0] == '%plane') {
// Get the width of the plane:
let width = Number(parts[1]);
let m = MeshUtils.createPlane(width);
}
this.CHECK(m != null, `Unsupported procedural shape: ${parts[0]}`)
// Store that new mesh:
this.meshes.set(name, m);
return m;
}
* Next I should use that procedural generation path to build our "quad" object:
* ðĪ hmmm... tried to replace the "quad" with a "%plane-2" mesh for the default terrain rendering as follow: // load the quad mesh:
// let quad = await rman.loadMesh('quad');
let quad = await rman.loadMesh('%plane-2');/*//*/
* ... but that doesn't seem to work: not displaying the terrain quads at all anymore.
* Let's check the program in use here: // load the terrain waves shader:
let p3 = await rman.loadProgram('terrainWaves');/*//*/
* (Because one thing to note already is that our procedural planes is constructed with "triangles" and not "triangle_strip" and uses **indices**
* => The terrain program doesn't seem to be doing anything fancy with the coordinates, only using the x and y coords from each vertex, so using triangles and setting z=1 should not be a problem here => the issue is thus with the indices ? Checking...
* Oh oh, another thing I just noticed when using my procedural plane is that we end up with a message: Mesh2: Uploading 0 vertices on GPU
* => So there is something wrong with the vertices data already.
* Arrff, OK, this is due to how we retrieve the vertices and indices count in a Mesh object: public getVerticesCount(): number {
return this.verticesPos / this.vertexSize;
}
public getIndicesCount(): number {
return this.indicesPos / this.indexSize;
}
* => With our procedural generation we should set those members correctly too: **OK**
* And now I get a exception because I need to handle indices correctly: Error: Mesh2: Should handle indices here.
* This comes from the ''Mesh.createBuffers'' method with this section: if (ni != 0) {
this.THROW("Should handle indices here.")
}
* So, let's implement that part now: if (ni != 0) {
if (this.usage == MeshUsage.GPU_STATIC || this.usage == MeshUsage.GPU_DYNAMIC || this.usage == MeshUsage.GPU_STREAM) {
this.indexBuffer = new GPUBuffer();
if (this.usage == MeshUsage.GPU_STATIC) {
this.uploadIndexDataToGPU(BufferUsage.STATIC_DRAW);
}
} else if (this.usage == MeshUsage.CPU) {
this.indexBuffer = new CPUBuffer(this.indices);
}
// AttributeType type;
// switch (sizeof(index)) {
// case 1:
// type = A8UI;
// break;
// case 2:
// type = A16UI;
// break;
// default:
// type = A32UI;
// break;
// }
this.buffers.setIndicesBuffer(new AttributeBuffer(0, 1, this.indicesType, false, this.indexBuffer));
}
In the code above the **sizeof(index)** part is commented because this is a template parameter for the Mesh class, but we use instead the indicesType member is our javascript implementation.
* Now unfortunately, this change leads to an invalid GL operation exception ðĒ. To be investigated.
* Note: the warning before the error is: GPUBuffer.ts:49 WebGL: INVALID_OPERATION: bindBuffer: buffers bound to non ELEMENT_ARRAY_BUFFER targets can not be bound to ELEMENT_ARRAY_BUFFER target
* => Okay, so it seems **we cannot bind a buffer bound to ELEMENT_ARRAY_BUFFER to something else**: so when copying the data with setData, we have to use that target too: **OK**
* I have now update the ''GPUBuffer.setData'' method to also support overriding the target to use for the copy: public setData(buf: ArrayBuffer, u: BufferUsage, tgt: number = -1) {
this.size = buf.byteLength;
if (tgt == -1)
tgt = this.ctx.gl2 != null ? this.ctx.gl2.COPY_WRITE_BUFFER : this.ctx.gl.ARRAY_BUFFER;
this.ctx.gl.bindBuffer(tgt, this.bufferId);
this.ctx.gl.bufferData(tgt, buf, u);
this.ctx.gl.bindBuffer(tgt, null);
this.ctx.checkGLError();
}
* And this seems to do the trick to be able to draw meshes with indices ð!
I should probably update the method ''GPUBuffer.setSubData(...)'' eventually.
* Next problem is: it doesn't quite work anymore if I use "%plane-4" for instance instead of "%plane-2": why that ?
* => arrff, I was computing the indices incorrectly in the procedural generation function, now fixed that introducing the required 'offset' for each row: idx = 0;
let offset = 0;
for (let r = 0; r < (width - 1); r++) {
offset = r * width;
for (let c = 0; c < (width - 1); c++) {
indices[idx++] = offset + c;
indices[idx++] = offset + c + 1;
indices[idx++] = offset + c + width;
indices[idx++] = offset + c + width;
indices[idx++] = offset + c + 1;
indices[idx++] = offset + c + width + 1;
}
}
* Now using our "quad" mesh for the terrain with 25 vertices on one side: **OK**
* Also updating the terrain parameters: this.DEBUG('Creating TerrainNode...');
let deform = new Deformation();
let size = 50000.0;
let zmin = 0.0;
let zmax = 5000.0;
let splitFactor = 2;
let maxLevel = 16;
let terrain = new TerrainNode(deform, size, zmin, zmax, splitFactor, maxLevel);
* Okay, and now the next step is going to be to actually **use** the tile samplers & producers in our terrain node: I feel this might be a somewhat large topic, so let's move that on a new page.