how to import a height map in WebGL - javascript

I know that in theory you have to first find the coordinates on the height map like (x = width HM / width Terrain * x Terrain) and y coordinate (y = height HM / height Terrain * y Terrain) and after getting the location on the height map, we get the actual height by min_height + (colorVal / (max_color - min_color) * *max_height - min_height) thus returning a Z value for a particular segment.
But how can i actually import the height map and take its parameters? I'm writing in javascript with no additional libraries (three,babylon).
edit
Currently i'm hardcoding the z values based on x and y ranges:
Plane.prototype.modifyGeometry=function(x,y){
if((x>=0&&x<100)&&(y>=0&&y<100)){
return 25;
}
else if((x>=100&&x<150)&&(y>=100&&y<150)){
return 20;
}
else if((x>=150&&x<200)&&(y>=150&&y<200)){
return 15;
}
else if((x>=200&&x<250)&&(y>=200&&y<250)){
return 10;
}
else if((x>=250&&x<300)&&(y>=250&&y<300)){
return 5;
}
else{
return 0;
}
** edit **
i can get a flat grid (or with randomly generated heights), but as soon as i add the image data, i get a blank screen(no errors though). Here is the code (i changed it up a bit):
var gl;
var canvas;
var img = new Image();
// img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';
var gridWidth;
var gridDepth;
var gridPoints = [];
var gridIndices = [];
var rowOff = 0;
var rowStride = gridWidth + 1;
var numVertices = (gridWidth * 2 * (gridDepth + 1)) + (gridDepth * 2 * (gridWidth + 1));
//creating plane
function generateHeightPoints() {
var ctx = document.createElement("canvas").getContext("2d"); //using 2d canvas to read image
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
gridWidth = imgData.width - 1;
gridDepth = imgData.height - 1;
for (var z = 0; z <= gridDepth; ++z) {
for (var x = 0; x <= gridWidth; ++x) {
var offset = (z * imgData.width + x) * 4;
var height = imgData.data[offset] * 10 / 255;
gridPoints.push(x, height, z);
}
}
}
function generateIndices() {
for (var z = 0; z<=gridDepth; ++z) {
rowOff = z*rowStride;
for(var x = 0; x<gridWidth; ++x) {
gridIndices.push(rowOff+x,rowOff+x+1);
}
}
for (var x = 0; x<=gridWidth; ++x) {
for(var z = 0; z<gridDepth; ++z) {
rowOff = z * rowStride;
gridIndices.push(rowOff+x,rowOff+x+rowStride);
}
}
}
//init
//program init
window.onload = function init()
{
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
var program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
generateHeightPoints();
generateIndices();
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints),
gl.STATIC_DRAW);
var indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices),
gl.STATIC_DRAW);
var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );
var matrixLoc = gl.getUniformLocation(program, 'matrix');
var m4 = twgl.m4;
var projection = m4.perspective(60 * Math.PI / 180, gl.canvas.clientWidth /
gl.canvas.clientHeight, 0.1, 100);
var cameraPosition = [-18, 15, -10];
var target = [gridWidth / 2, -10, gridDepth / 2];
var up = [0, 1, 0];
var camera = m4.lookAt(cameraPosition, target, up);
var view = m4.inverse(camera);
var mat = m4.multiply(projection, view);
gl.uniformMatrix4fv(matrixLoc, false, mat);
render();
}
function render() {
gl.drawElements(gl.LINES, numVertices, gl.UNSIGNED_SHORT, 0);
gl.drawElements(gl.LINES,numVertices,gl.UNSIGNED_SHORT,0);
requestAnimFrame(render);
}

You basically just make a grid of points and change the Z values.
First a flat grid
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision highp float;
void main() {
gl_FragColor = vec4(0, 1, 0, 1);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const matrixLoc = gl.getUniformLocation(program, 'matrix');
const gridWidth = 40;
const gridDepth = 40;
const gridPoints = [];
for (let z = 0; z <= gridDepth; ++z) {
for (let x = 0; x <= gridWidth; ++x) {
gridPoints.push(x, 0, z);
}
}
const gridIndices = [];
const rowStride = gridWidth + 1;
// x lines
for (let z = 0; z <= gridDepth; ++z) {
const rowOff = z * rowStride;
for (let x = 0; x < gridWidth; ++x) {
gridIndices.push(rowOff + x, rowOff + x + 1);
}
}
// z lines
for (let x = 0; x <= gridWidth; ++x) {
for (let z = 0; z < gridDepth; ++z) {
const rowOff = z * rowStride;
gridIndices.push(rowOff + x, rowOff + x + rowStride);
}
}
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints), gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices), gl.STATIC_DRAW);
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const m4 = twgl.m4;
const projection = m4.perspective(
60 * Math.PI / 180, // field of view
gl.canvas.clientWidth / gl.canvas.clientHeight, // aspect
0.1, // near
100, // far
);
const cameraPosition = [-gridWidth / 8, 10, -gridDepth / 8];
const target = [gridWidth / 2, -10, gridDepth / 2];
const up = [0, 1, 0];
const camera = m4.lookAt(cameraPosition, target, up);
const view = m4.inverse(camera);
const mat = m4.multiply(projection, view);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(
positionLoc, // location
3, // size
gl.FLOAT, // type
false, // normalize
0, // stride
0, // offset
);
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLoc, false, mat);
const numVertices = (gridWidth * 2 * (gridDepth + 1)) +
(gridDepth * 2 * (gridWidth + 1));
gl.drawElements(
gl.LINES, // primitive type
numVertices, //
gl.UNSIGNED_SHORT, // type of indices
0, // offset
);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Grid with height map.
Here's a gray scale image we can use as a height map
Read it by loading a img, drawing to a 2D canvas, calling getImageData. Then just read the red values for height.
const img = new Image();
img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';
function run() {
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision highp float;
void main() {
gl_FragColor = vec4(0, 1, 0, 1);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const matrixLoc = gl.getUniformLocation(program, 'matrix');
// use a canvas 2D to read the image
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
const gridWidth = imgData.width - 1;
const gridDepth = imgData.height - 1;
const gridPoints = [];
for (let z = 0; z <= gridDepth; ++z) {
for (let x = 0; x <= gridWidth; ++x) {
const offset = (z * imgData.width + x) * 4;
// height 0 to 10
const height = imgData.data[offset] * 10 / 255;
gridPoints.push(x, height, z);
}
}
const gridIndices = [];
const rowStride = gridWidth + 1;
// x lines
for (let z = 0; z <= gridDepth; ++z) {
const rowOff = z * rowStride;
for (let x = 0; x < gridWidth; ++x) {
gridIndices.push(rowOff + x, rowOff + x + 1);
}
}
// z lines
for (let x = 0; x <= gridWidth; ++x) {
for (let z = 0; z < gridDepth; ++z) {
const rowOff = z * rowStride;
gridIndices.push(rowOff + x, rowOff + x + rowStride);
}
}
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints), gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices), gl.STATIC_DRAW);
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const m4 = twgl.m4;
const projection = m4.perspective(
60 * Math.PI / 180, // field of view
gl.canvas.clientWidth / gl.canvas.clientHeight, // aspect
0.1, // near
100, // far
);
const cameraPosition = [-10, 10, -10];
const target = [gridWidth / 2, -10, gridDepth / 2];
const up = [0, 1, 0];
const camera = m4.lookAt(cameraPosition, target, up);
const view = m4.inverse(camera);
const mat = m4.multiply(projection, view);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(
positionLoc, // location
3, // size
gl.FLOAT, // type
false, // normalize
0, // stride
0, // offset
);
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLoc, false, mat);
const numVertices = (gridWidth * 2 * (gridDepth + 1)) +
(gridDepth * 2 * (gridWidth + 1));
gl.drawElements(
gl.LINES, // primitive type
numVertices, //
gl.UNSIGNED_SHORT, // type of indices
0, // offset
);
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Then instead of making a grid of lines make a grid of triangles. There's lots of ways to do that. You could put 2 triangle per grid square. This code put's 4. You also need to generate normals. I copied the code to generate normals from this article which is fairly generic normal generating code. Being a grid you could make a grid specific normal generator which would be faster since being a grid you know which vertices are shared.
This code is also using twgl because WebGL is too verbose but it should be clear how to do it in plain WebGL from reading the names of the twgl functions.
'use strict';
/* global twgl, m4, requestAnimationFrame, document */
const img = new Image();
img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';
function run() {
// use a canvas 2D to read the image
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
function getHeight(offset) {
const v = imgData.data[offset * 4]; // x4 because RGBA
return v * 10 / 255; // 0 to 10
}
// first generate a grid of triangles, at least 2 or 4 for each cell
//
// 2 4
// +----+ +----+
// | /| |\ /|
// | / | | \/ |
// | / | | /\ |
// |/ | |/ \|
// +----+ +----+
//
const positions = [];
const texcoords = [];
const indices = [];
const cellsAcross = imgData.width - 1;
const cellsDeep = imgData.height - 1;
for (let z = 0; z < cellsDeep; ++z) {
for (let x = 0; x < cellsAcross; ++x) {
const base0 = z * imgData.width + x;
const base1 = base0 + imgData.width;
const h00 = getHeight(base0); const h01 = getHeight(base0 + 1);
const h10 = getHeight(base1);
const h11 = getHeight(base1 + 1);
const hm = (h00 + h01 + h10 + h11) / 4;
const x0 = x;
const x1 = x + 1;
const z0 = z;
const z1 = z + 1;
const ndx = positions.length / 3;
positions.push(
x0, h00, z0,
x1, h01, z0,
x0, h10, z1,
x1, h11, z1,
(x0 + x1) / 2, hm, (z0 + z1) / 2,
);
const u0 = x / cellsAcross;
const v0 = z / cellsDeep;
const u1 = (x + 1) / cellsAcross;
const v1 = (z + 1) / cellsDeep;
texcoords.push(
u0, v0,
u1, v0,
u0, v1,
u1, v1,
(u0 + u1) / 2, (v0 + v1) / 2,
);
//
// 0----1
// |\ /|
// | \/4|
// | /\ |
// |/ \|
// 2----3
indices.push(
ndx, ndx + 4, ndx + 1,
ndx, ndx + 2, ndx + 4,
ndx + 2, ndx + 3, ndx + 4,
ndx + 1, ndx + 4, ndx + 3,
);
}
}
const maxAngle = 2 * Math.PI / 180; // make them facetted
const arrays = generateNormals({
position: positions,
texcoord: texcoords,
indices,
}, maxAngle);
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texcoord;
uniform mat4 projection;
uniform mat4 modelView;
varying vec3 v_normal;
varying vec2 v_texcoord;
void main() {
gl_Position = projection * modelView * position;
v_normal = mat3(modelView) * normal;
v_texcoord = texcoord;
}
`;
const fs = `
precision highp float;
varying vec3 v_normal;
varying vec2 v_texcoord;
varying float v_modelId;
void main() {
vec3 lightDirection = normalize(vec3(1, 2, -3)); // arbitrary light direction
float l = dot(lightDirection, normalize(v_normal)) * .5 + .5;
gl_FragColor = vec4(vec3(0,1,0) * l, 1);
}
`;
// compile shader, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// make some vertex data
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) {
time *= 0.001; // seconds
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
const fov = Math.PI * 0.25;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const near = 0.1;
const far = 100;
const projection = m4.perspective(fov, aspect, near, far);
const eye = [0, 10, 25];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
let modelView = m4.yRotate(view, time);
modelView = m4.translate(modelView, imgData.width / -2, 0, imgData.height / -2)
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniforms(programInfo, {
projection,
modelView,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function generateNormals(arrays, maxAngle) {
const positions = arrays.position;
const texcoords = arrays.texcoord;
// first compute the normal of each face
let getNextIndex = makeIndiceIterator(arrays);
const numFaceVerts = getNextIndex.numElements;
const numVerts = arrays.position.length;
const numFaces = numFaceVerts / 3;
const faceNormals = [];
// Compute the normal for every face.
// While doing that, create a new vertex for every face vertex
for (let i = 0; i < numFaces; ++i) {
const n1 = getNextIndex() * 3;
const n2 = getNextIndex() * 3;
const n3 = getNextIndex() * 3;
const v1 = positions.slice(n1, n1 + 3);
const v2 = positions.slice(n2, n2 + 3);
const v3 = positions.slice(n3, n3 + 3);
faceNormals.push(m4.normalize(m4.cross(m4.subtractVectors(v1, v2), m4.subtractVectors(v3, v2))));
}
let tempVerts = {};
let tempVertNdx = 0;
// this assumes vertex positions are an exact match
function getVertIndex(x, y, z) {
const vertId = x + "," + y + "," + z;
const ndx = tempVerts[vertId];
if (ndx !== undefined) {
return ndx;
}
const newNdx = tempVertNdx++;
tempVerts[vertId] = newNdx;
return newNdx;
}
// We need to figure out the shared vertices.
// It's not as simple as looking at the faces (triangles)
// because for example if we have a standard cylinder
//
//
// 3-4
// / \
// 2 5 Looking down a cylinder starting at S
// | | and going around to E, E and S are not
// 1 6 the same vertex in the data we have
// \ / as they don't share UV coords.
// S/E
//
// the vertices at the start and end do not share vertices
// since they have different UVs but if you don't consider
// them to share vertices they will get the wrong normals
const vertIndices = [];
for (let i = 0; i < numVerts; ++i) {
const offset = i * 3;
const vert = positions.slice(offset, offset + 3);
vertIndices.push(getVertIndex(vert));
}
// go through every vertex and record which faces it's on
const vertFaces = [];
getNextIndex.reset();
for (let i = 0; i < numFaces; ++i) {
for (let j = 0; j < 3; ++j) {
const ndx = getNextIndex();
const sharedNdx = vertIndices[ndx];
let faces = vertFaces[sharedNdx];
if (!faces) {
faces = [];
vertFaces[sharedNdx] = faces;
}
faces.push(i);
}
}
// now go through every face and compute the normals for each
// vertex of the face. Only include faces that aren't more than
// maxAngle different. Add the result to arrays of newPositions,
// newTexcoords and newNormals, discarding any vertices that
// are the same.
tempVerts = {};
tempVertNdx = 0;
const newPositions = [];
const newTexcoords = [];
const newNormals = [];
function getNewVertIndex(x, y, z, nx, ny, nz, u, v) {
const vertId =
x + "," + y + "," + z + "," +
nx + "," + ny + "," + nz + "," +
u + "," + v;
const ndx = tempVerts[vertId];
if (ndx !== undefined) {
return ndx;
}
const newNdx = tempVertNdx++;
tempVerts[vertId] = newNdx;
newPositions.push(x, y, z);
newNormals.push(nx, ny, nz);
newTexcoords.push(u, v);
return newNdx;
}
const newVertIndices = [];
getNextIndex.reset();
const maxAngleCos = Math.cos(maxAngle);
// for each face
for (let i = 0; i < numFaces; ++i) {
// get the normal for this face
const thisFaceNormal = faceNormals[i];
// for each vertex on the face
for (let j = 0; j < 3; ++j) {
const ndx = getNextIndex();
const sharedNdx = vertIndices[ndx];
const faces = vertFaces[sharedNdx];
const norm = [0, 0, 0];
faces.forEach(faceNdx => {
// is this face facing the same way
const otherFaceNormal = faceNormals[faceNdx];
const dot = m4.dot(thisFaceNormal, otherFaceNormal);
if (dot > maxAngleCos) {
m4.addVectors(norm, otherFaceNormal, norm);
}
});
m4.normalize(norm, norm);
const poffset = ndx * 3;
const toffset = ndx * 2;
newVertIndices.push(getNewVertIndex(
positions[poffset + 0], positions[poffset + 1], positions[poffset + 2],
norm[0], norm[1], norm[2],
texcoords[toffset + 0], texcoords[toffset + 1]));
}
}
return {
position: newPositions,
texcoord: newTexcoords,
normal: newNormals,
indices: newVertIndices,
};
}
function makeIndexedIndicesFn(arrays) {
const indices = arrays.indices;
let ndx = 0;
const fn = function() {
return indices[ndx++];
};
fn.reset = function() {
ndx = 0;
};
fn.numElements = indices.length;
return fn;
}
function makeUnindexedIndicesFn(arrays) {
let ndx = 0;
const fn = function() {
return ndx++;
};
fn.reset = function() {
ndx = 0;
}
fn.numElements = arrays.positions.length / 3;
return fn;
}
function makeIndiceIterator(arrays) {
return arrays.indices
? makeIndexedIndicesFn(arrays)
: makeUnindexedIndicesFn(arrays);
}
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/3d-math.js"></script>
<canvas></canvas>

Related

How to dynamically change/set arrow size of an edge in sigma v2?

I am trying to let the user control the arrow size without affecting the size of the edge, in v1 (canvas renderer), it was done like with changing the minArrowSize in the settings, this is not the case in v2 (WebGL).
I have seen this solution: https://stackoverflow.com/a/73988100/9294601 that allows me to statically modify the size of the input, but how can I access this thickness variable dyanmically
I tried this but as expected, it did not work (note: I am using sim51/react-sigma
<SigmaContainer
settings={{
nodeProgramClasses: {image: getNodeProgramImage(), circle: CircleNodeProgram},
edgeProgramClasses: {
arrow: ArrowEdgeProgram,
},
minArrowSize: 100,
...graphSettings,
}}
style={{height: '100%', background: props.noBackground ? 'white' : mainBackgroundColor}}
>
For anyone wondering, I utilized the solution I posted in my question with a slight twist, in the custom arrowhead, you'll find data attribute passed to process, this represent the edge, what you can do is pass another attribute, called for example minArrowSize (or whatever other name you want), and modify the thickness like this:
const thickness = data.minArrowSize || 1;
so the complete arrowProgram would be
import {AbstractEdgeProgram} from 'sigma/rendering/webgl/programs/common/edge';
import fragmentShaderSource from 'sigma/rendering/webgl/shaders/edge.arrowHead.frag.glsl';
import vertexShaderSource from 'sigma/rendering/webgl/shaders/edge.arrowHead.vert.glsl';
import {floatColor} from 'sigma/utils';
const POINTS = 3;
const ATTRIBUTES = 9;
const STRIDE = POINTS * ATTRIBUTES;
export default class EdgeArrowHeadProgram extends AbstractEdgeProgram {
constructor(gl) {
super(gl, vertexShaderSource, fragmentShaderSource, POINTS, ATTRIBUTES);
// Locations
this.positionLocation = gl.getAttribLocation(this.program, 'a_position');
this.colorLocation = gl.getAttribLocation(this.program, 'a_color');
this.normalLocation = gl.getAttribLocation(this.program, 'a_normal');
this.radiusLocation = gl.getAttribLocation(this.program, 'a_radius');
this.barycentricLocation = gl.getAttribLocation(this.program, 'a_barycentric');
// Uniform locations
const matrixLocation = gl.getUniformLocation(this.program, 'u_matrix');
if (matrixLocation === null) throw new Error('EdgeArrowHeadProgram: error while getting matrixLocation');
this.matrixLocation = matrixLocation;
const sqrtZoomRatioLocation = gl.getUniformLocation(this.program, 'u_sqrtZoomRatio');
if (sqrtZoomRatioLocation === null) {
throw new Error('EdgeArrowHeadProgram: error while getting sqrtZoomRatioLocation');
}
this.sqrtZoomRatioLocation = sqrtZoomRatioLocation;
const correctionRatioLocation = gl.getUniformLocation(this.program, 'u_correctionRatio');
if (correctionRatioLocation === null) {
throw new Error('EdgeArrowHeadProgram: error while getting correctionRatioLocation');
}
this.correctionRatioLocation = correctionRatioLocation;
this.bind();
}
// Locations
positionLocation;
colorLocation;
normalLocation;
radiusLocation;
barycentricLocation;
matrixLocation;
sqrtZoomRatioLocation;
correctionRatioLocation;
bind() {
const gl = this.gl;
// Bindings
gl.enableVertexAttribArray(this.positionLocation);
gl.enableVertexAttribArray(this.normalLocation);
gl.enableVertexAttribArray(this.radiusLocation);
gl.enableVertexAttribArray(this.colorLocation);
gl.enableVertexAttribArray(this.barycentricLocation);
gl.vertexAttribPointer(this.positionLocation, 2, gl.FLOAT, false, ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 0);
gl.vertexAttribPointer(this.normalLocation, 2, gl.FLOAT, false, ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 8);
gl.vertexAttribPointer(this.radiusLocation, 1, gl.FLOAT, false, ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, 16);
gl.vertexAttribPointer(
this.colorLocation,
4,
gl.UNSIGNED_BYTE,
true,
ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
20,
);
// TODO: maybe we can optimize here by packing this in a bit mask
gl.vertexAttribPointer(
this.barycentricLocation,
3,
gl.FLOAT,
false,
ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT,
24,
);
}
computeIndices() {
// nothing to do
}
process(
sourceData,
targetData,
data,
hidden,
offset,
) {
if (hidden) {
for (let i = offset * STRIDE, l = i + STRIDE; i < l; i++) this.array[i] = 0;
return;
}
const thickness = data.minArrowSize || 1;
const radius = targetData.size || 1;
const x1 = sourceData.x;
const y1 = sourceData.y;
const x2 = targetData.x;
const y2 = targetData.y;
const color = floatColor(data.color);
// Computing normals
const dx = x2 - x1;
const dy = y2 - y1;
let len = dx * dx + dy * dy;
let n1 = 0;
let n2 = 0;
if (len) {
len = 1 / Math.sqrt(len);
n1 = -dy * len * thickness;
n2 = dx * len * thickness;
}
let i = POINTS * ATTRIBUTES * offset;
const array = this.array;
// First point
array[i++] = x2;
array[i++] = y2;
array[i++] = -n1;
array[i++] = -n2;
array[i++] = radius;
array[i++] = color;
array[i++] = 1;
array[i++] = 0;
array[i++] = 0;
// Second point
array[i++] = x2;
array[i++] = y2;
array[i++] = -n1;
array[i++] = -n2;
array[i++] = radius;
array[i++] = color;
array[i++] = 0;
array[i++] = 1;
array[i++] = 0;
// Third point
array[i++] = x2;
array[i++] = y2;
array[i++] = -n1;
array[i++] = -n2;
array[i++] = radius;
array[i++] = color;
array[i++] = 0;
array[i++] = 0;
array[i] = 1;
}
render(params) {
if (this.hasNothingToRender()) return;
const gl = this.gl;
const program = this.program;
gl.useProgram(program);
// Binding uniforms
gl.uniformMatrix3fv(this.matrixLocation, false, params.matrix);
gl.uniform1f(this.sqrtZoomRatioLocation, Math.sqrt(params.ratio));
gl.uniform1f(this.correctionRatioLocation, params.correctionRatio);
// Drawing:
gl.drawArrays(gl.TRIANGLES, 0, this.array.length / ATTRIBUTES);
}
}
and you would just modify this arrow size in your regular code:
useEffect(()=>{
const {minArrowSize} = graphSettings;
graph.updateEachEdgeAttributes((edgeId, e)=>{
const updatedEdge = {
...e,
minArrowSize,
};
return updatedEdge;
});
}, [graphSettings.minArrowSize]);
also don't forget to do a hard refresh after that so that changes in the arrowProgram file reflect

Declaring the indices of an UV-Sphere [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I'm trying to create a uv-sphere in WebGL for a university project, but I'm having problems declaring the vertices indices correctly (I assume). I'm following this http://www.songho.ca/opengl/gl_sphere.html and I think my code is pretty similar to the one shown there. This is how I'm declaring my vertices/indices/normals/texture coordinates:
this.vertices = [];
this.indices = [];
this.normals = [];
this.texCoords = [];
let horizontalAng = 0;
let horizontalDiff = (2 * Math.PI) / this.horizontalDiv;
let verticalAng = Math.PI / 2;
let verticalDiff = - (Math.PI / this.verticalDiv);
for (var i = 0; i <= this.verticalDiv; i++) {
let cosVert = Math.cos(verticalAng);
let sinVert = Math.sin(verticalAng);
for (var j = 0; j <= this.horizontlDiv; j++) {
let cosHor = Math.cos(horizontalAng);
let sinHor = Math.sin(horizontalAng);
// z = (r * cos(verticalAng)) * cos(horizontalAng)
// x = (r * cos(verticalAng)) * sin(horizontalAng)
// y = r * sin(veritcalAng)
let x = cosVert * sinHor;
let y = sinVert;
let z = cosVert * cosHor;
this.vertices.push(x, y, z);
this.normals.push(x, y, z);
this.texCoords.push(j / this.horizontalDiv);
this.texCoords.push(i / this.verticalDiv);
horizontalAng += horizontalDiff;
}
verticalAng += verticalDiff;
}
for (var i = 0; i < this.verticalDiv; i++) {
k1 = i * (this.horizontalDiv + 1);
k2 = k1 + this.horizontalDiv + 1;
for (var j = 0; j < this.horizontalDiv; j++) {
if (i != 0) {
this.indices.push(k1);
this.indices.push(k2);
this.indices.push(k1 + 1);
}
if (i != (this.verticalDiv - 1)) {
this.indices.push(k1 + 1);
this.indices.push(k2);
this.indices.push(k2 + 1);
}
k1++;
k2++;
}
}
```
The code has several typos
It doesn't declare k1 or k2.
horizontalDiv is mis-spelled in places as horizontlDiv
class Foo {
constructor(horizontalDiv, verticalDiv) {
this.horizontalDiv = horizontalDiv;
this.verticalDiv = verticalDiv;
this.vertices = [];
this.indices = [];
this.normals = [];
this.texCoords = [];
let horizontalAng = 0;
let horizontalDiff = (2 * Math.PI) / this.horizontalDiv;
let verticalAng = Math.PI / 2;
let verticalDiff = -(Math.PI / this.verticalDiv);
for (var i = 0; i <= this.verticalDiv; i++) {
let cosVert = Math.cos(verticalAng);
let sinVert = Math.sin(verticalAng);
for (var j = 0; j <= this.horizontalDiv; j++) {
let cosHor = Math.cos(horizontalAng);
let sinHor = Math.sin(horizontalAng);
// z = (r * cos(verticalAng)) * cos(horizontalAng)
// x = (r * cos(verticalAng)) * sin(horizontalAng)
// y = r * sin(veritcalAng)
let x = cosVert * sinHor;
let y = sinVert;
let z = cosVert * cosHor;
this.vertices.push(x, y, z);
this.normals.push(x, y, z);
this.texCoords.push(j / this.horizontalDiv);
this.texCoords.push(i / this.verticalDiv);
horizontalAng += horizontalDiff;
}
verticalAng += verticalDiff;
}
for (var i = 0; i < this.verticalDiv; i++) {
let k1 = i * (this.horizontalDiv + 1);
let k2 = k1 + this.horizontalDiv + 1;
for (var j = 0; j < this.horizontalDiv; j++) {
if (i != 0) {
this.indices.push(k1);
this.indices.push(k2);
this.indices.push(k1 + 1);
}
if (i != (this.verticalDiv - 1)) {
this.indices.push(k1 + 1);
this.indices.push(k2);
this.indices.push(k2 + 1);
}
k1++;
k2++;
}
}
}
}
const f = new Foo(10, 10);
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texcoord;
uniform mat4 matrix;
varying vec4 v_color;
void main() {
gl_Position = matrix * position;
v_color = vec4(0, 0, 1, 1);
// comment in next line to show normals
//v_color = vec4(normal * .5 + .5, 1);
// comment in next line to show texcoords
//v_color = vec4(texcoord, 0, 1);
}
`;
const fs = `
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: f.vertices,
normal: f.normals,
texcoord: f.texCoords,
indices: f.indices,
});
let matrix = m4.perspective(Math.PI * 0.25, 2, 0.1, 100);
matrix = m4.translate(matrix, [0, 0, -5]);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.uniformXXX
twgl.setUniforms(programInfo, {
matrix,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

convolve kernal matrics in javascript for image filter in html5canvas

I have been trying to learn filters in javascript, i have been following
https://www.html5rocks.com/en/tutorials/canvas/imagefilters/
this tutorial.
I came across some of code i don't get, can some body help me understanding these codes.
Filters.convolute = function(pixels, weights, opaque) {
var side = Math.round(Math.sqrt(weights.length));
var halfSide = Math.floor(side/2);
var src = pixels.data;
var sw = pixels.width;
var sh = pixels.height;
// pad output by the convolution matrix
var w = sw;
var h = sh;
var output = Filters.createImageData(w, h);
var dst = output.data;
// go through the destination image pixels
var alphaFac = opaque ? 1 : 0;
for (var y=0; y<h; y++) {
for (var x=0; x<w; x++) {
var sy = y;
var sx = x;
var dstOff = (y*w+x)*4;
// calculate the weighed sum of the source image pixels that
// fall under the convolution matrix
var r=0, g=0, b=0, a=0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = sy + cy - halfSide;
var scx = sx + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
var srcOff = (scy*sw+scx)*4;
var wt = weights[cy*side+cx];
r += src[srcOff] * wt;
g += src[srcOff+1] * wt;
b += src[srcOff+2] * wt;
a += src[srcOff+3] * wt;
}
}
}
dst[dstOff] = r;
dst[dstOff+1] = g;
dst[dstOff+2] = b;
dst[dstOff+3] = a + alphaFac*(255-a);
}
}
return output;
};
what is side and halfSide and why 4 for nested loop is used for. i am stuck here like many days.
I do, like you the same thing, I am trying to implement convolution filters using Javascript - TypeScript.
The reason why is 4 is because we have r, g, b, a
where r = red
where g = green
where b = blue
where a = alpha
this image data is inside an array of type Uint8ClampedArray
you get this information with this way:
const width = canvas.width;
const height = canvas.height;
const imageData = ctx.getImageData(0, 0, width, height);
and then to get the real image data:
const pixels = imageData.data;
The pixel data is a type of Uint8ClampedArray and can be represented like this:
[r, g, b, a, r, g, b, a, r, g, b, a ]
and every 4 elements in the array you get the pixel index and every 1,5 times you get the kernel Center but this depends on the kernel size 3x3 or 9x9
const image = imageData.data
The only code is working for me is this.
init() {
const img = new Image();
const img2 = new Image();
img.src = '../../../assets/graffiti.jpg';
img2.src = '../../../assets/graffiti.jpg';
const canvas: HTMLCanvasElement = this.canvas1.nativeElement;
const canvas2: HTMLCanvasElement = this.canvas2.nativeElement;
const ctx: CanvasRenderingContext2D = canvas.getContext('2d');
const ctx2: CanvasRenderingContext2D = canvas2.getContext('2d');
this.onImgLoad(img, ctx, canvas.width, canvas.height);
this.input(img2, ctx2, canvas2.width, canvas2.height);
}
input(img, ctx: CanvasRenderingContext2D, width, height) {
img.onload = () => {
ctx.drawImage(img, 0, 0);
};
}
onImgLoad(img, ctx: CanvasRenderingContext2D, width, height) {
img.onload = () => {
ctx.drawImage(img, 0, 0);
const kernelArr = new Kernel([
[0, 1, 0],
[0, 1, 0],
[0, 1, 0],
]);
const kernel = [
0, 1, 0,
0, 1, 0,
0, 1, 0
];
console.log(kernel);
const newImg = new Filter2D(ctx, width, height);
// const imgData = newImg.inverse(width, height); // applys inverse filter
const imgData = newImg.applyKernel(kernel);
ctx.putImageData(imgData, 0, 0);
};
}
class Filter2D {
width: number;
height: number;
ctx: CanvasRenderingContext2D;
imgData: ImageData;
constructor(ctx: CanvasRenderingContext2D, width: number, height: number) {
this.width = width;
this.height = height;
this.ctx = ctx;
this.imgData = ctx.getImageData(0, 0, width, height);
console.log(this.imgData);
}
grey(width: number, height: number): ImageData {
return this.imgData;
}
inverse(width: number, height: number): ImageData {
console.log('Width: ', width);
console.log('Height: ', height);
const pixels = this.imgData.data;
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = 255 - pixels[i]; // red
pixels[i + 1] = 255 - pixels[i + 1]; // green
pixels[i + 2] = 255 - pixels[i + 2]; // blue
}
return this.imgData;
}
applyKernel(kernel: any[]): ImageData {
const k1: number[] = [
1, 0, -1,
2, 0, -2,
1, 0, -1
];
const k2: number[] = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
];
kernel = k2;
const dim = Math.sqrt(kernel.length);
const pad = Math.floor(dim / 2);
const pixels: Uint8ClampedArray = this.imgData.data;
const width: number = this.imgData.width;
const height: number = this.imgData.height;
console.log(this.imgData);
console.log('applyKernelMethod start');
console.log('Width: ', width);
console.log('Height: ', height);
console.log('kernel: ', kernel);
console.log('dim: ', dim); // 3
console.log('pad: ', pad); // 1
console.log('dim % 2: ', dim % 2); // 1
console.log('pixels: ', pixels);
if (dim % 2 !== 1) {
console.log('Invalid kernel dimension');
}
let pix, i, r, g, b;
const w = width;
const h = height;
const cw = w + pad * 2; // add padding
const ch = h + pad * 2;
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
r = 0;
g = 0;
b = 0;
for (let kx = -pad; kx <= pad; kx++) {
for (let ky = -pad; ky <= pad; ky++) {
i = (ky + pad) * dim + (kx + pad); // kernel index
pix = 4 * ((row + ky) * cw + (col + kx)); // image index
r += pixels[pix++] * kernel[i];
g += pixels[pix++] * kernel[i];
b += pixels[pix ] * kernel[i];
}
}
pix = 4 * ((row - pad) * w + (col - pad)); // destination index
pixels[pix++] = r;
pixels[pix++] = g;
pixels[pix++] = b;
pixels[pix ] = 255; // we want opaque image
}
}
console.log(pixels);
return this.imgData;
}
}

WebGL: smoothly fade lines out of canvas

I'm a beginner at WebGL programming.
I've made a web application in Three.JS that draws a sin wave onto a canvas with occasional noise. After they've been drawn, I fade them away. The final effect looks something like this:
I'm trying to make the application in WebGL because of speed issues with Three.JS. I am able to draw one plain sin wave in WebGL but don't know how to achieve the same effect where I can draw a single wave, keep it in the buffer somehow, and fade it away.
This is what I currently have (in WebGL):
Also, here is the relevant code:
this.gl;
try {
this.gl = this.canvas.getContext('experimental-webgl',{antialias: false});
} catch (e) {
alert('WebGL not supported.');
}
//set position of vertices in clip coordinates
this.vtxShaderSrc = "\n\
attribute vec2 position;\n\
uniform vec2 viewport;\n\
\n\
void main(void){\n\
\n\
gl_Position = vec4((position/viewport)*2.0-1.0, 0.0, 1.0);\n\
}";
//fragment shader returns the color of pixel
this.fmtShaderSrc = "\n\
precision mediump float;\n\
\n\
\n\
\n\
void main(void){\n\
int r = 255;\n\
int g = 255;\n\
int b = 255;\n\
gl_FragColor = vec4(r/255,g/255,b/255,1.);\n\
}";
this.getShader = function(source, type){
var shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
return shader;
}
this.vtxShader = this.getShader(this.vtxShaderSrc, this.gl.VERTEX_SHADER);
this.fmtShader = this.getShader(this.fmtShaderSrc, this.gl.FRAGMENT_SHADER);
this.program = this.gl.createProgram();
//attach fragment and vertex shader to program
this.gl.attachShader(this.program, this.vtxShader);
this.gl.attachShader(this.program, this.fmtShader);
//link program to WebGL
this.gl.linkProgram(this.program);
//get position attribute and enable it in vertex shader
this._position = this.gl.getAttribLocation(this.program, 'position');
this.gl.enableVertexAttribArray(this._position);
//tell WebGL to use this program
this.gl.useProgram(this.program);
//create buffers
this.vertexBuffer = this.gl.createBuffer();
this.facesBuffer = this.gl.createBuffer();
this.lineVertices = [];
this.faceCount = [];
//bind them to WebGL
this.bindVertexBuffer = function(){
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.lineVertices), this.gl.STREAM_DRAW);
}
this.bindFacesBuffer = function(){
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.facesBuffer);
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.faceCount), this.gl.STREAM_DRAW);
}
this.bindVertexBuffer();
this.bindFacesBuffer();
//set background color to black
this.gl.clearColor(0.0,0.0,0.0,1.0);
//draw on canvas
this.draw = function(){
this.gl.enable(this.gl.BLEND);
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.vertexAttribPointer(this._position, 2, this.gl.FLOAT, false, 8*2, 0);
var loc = this.gl.getUniformLocation(this.program, 'viewport');
this.gl.uniform2f(loc, this.canvas.width, this.canvas.height);
//draw only if number of lines is greater than 0
if(this.faceCount.length > 0){
this.gl.drawElements(this.gl.LINE_STRIP, this.faceCount.length/4, this.gl.UNSIGNED_SHORT, 0);
}
this.gl.disable(this.gl.BLEND);
}
//update vertices and faces so next call to this.draw() updates the wave
this.update = function(newPts){
this.lineVertices = newPts;
this.bindVertexBuffer();
var faces = [];
for(var i = 0; i < this.lineVertices.length; i++) faces.push(i);
this.faceCount = faces;
this.bindFacesBuffer();
}
Any help/pointers are appreciated. Thanks
It's hard to give an answer as there's an infinite number of ways to do it but basically WebGL is just a rasteration API so if you want something to fade out overtime you have to render it every frame and over time draw things you want to fade out with a more transparency.
In pseudo code
for each thing to draw
compute its age
draw more transparent the older it is
(optionally, delete it if it's too old)
Here's a canvas 2d version to keep it simple
var ctx = document.querySelector("canvas").getContext("2d")
var currentTime = 0; // in seconds
var ageLimit = 1; // 1 second
var birthDuration = 0.2; // 0.2 seconds
var birthTimer = 0;
var thingsToDraw = [];
function addThingToDraw() {
thingsToDraw.push({
timeOfBirth: currentTime,
x: Math.random(),
y: Math.random(),
});
}
function computeAge(thing) {
return currentTime - thing.timeOfBirth;
}
function removeOldThings() {
while(thingsToDraw.length > 0) {
var age = computeAge(thingsToDraw[0]);
if (age < ageLimit) {
break;
}
// remove thing that's too old
thingsToDraw.shift();
}
}
function drawThings() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
thingsToDraw.forEach(function(thing) {
var age = computeAge(thing);
var lerp = age / ageLimit;
var x = ctx.canvas.width * thing.x;
var y = ctx.canvas.height * thing.y;
var radius = 10 + lerp * 10; // 10 to 20
var color = makeCSSRGBAColor(0, 0, 0, 1. - lerp);
drawCircle(ctx, x, y, radius, color);
});
}
function drawCircle(ctx, x, y, radius, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
function makeCSSRGBAColor(r, g, b, a) {
return "rgba(" + r + "," + g + "," + b + "," + a + ")";
}
var then = 0;
function process(time) {
currentTime = time * 0.001;
var deltaTime = currentTime - then;
then = currentTime;
birthTimer -= deltaTime;
if (birthTimer <= 0) {
addThingToDraw();
birthTimer = birthDuration;
}
removeOldThings();
drawThings();
requestAnimationFrame(process);
}
requestAnimationFrame(process);
canvas { border: 1px solid black; }
<canvas></canvas>
WebGL is no different, just replace ctx.clearRect with gl.clear and drawCircle with some function that draws a circle.
Here's the WebGL version of the same program
var gl = document.querySelector("canvas").getContext("webgl")
var currentTime = 0; // in seconds
var ageLimit = 1; // 1 second
var birthDuration = 0.2; // 0.2 seconds
var birthTimer = 0;
var thingsToDraw = [];
var vs = `
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
`;
var fs = `
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
var program = twgl.createProgramFromSources(gl, [vs, fs]);
var positionLocation = gl.getAttribLocation(program, "position");
var colorLocation = gl.getUniformLocation(program, "u_color");
var matrixLocation = gl.getUniformLocation(program, "u_matrix");
// make a circle of triangles
var numAround = 60;
var verts = [];
for (var i = 0; i < numAround; ++i) {
addPoint(verts, i / numAround, 1);
addPoint(verts, (i + 1) / numAround, 1);
addPoint(verts, i / numAround, 0);
}
var numVerts = verts.length / 2;
var buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
function addPoint(verts, angleZeroToOne, radius) {
var angle = angleZeroToOne * Math.PI * 2;
verts.push(Math.cos(angle) * radius, Math.sin(angle) * radius);
}
function addThingToDraw() {
thingsToDraw.push({
timeOfBirth: currentTime,
x: Math.random(),
y: Math.random(),
});
}
function computeAge(thing) {
return currentTime - thing.timeOfBirth;
}
function removeOldThings() {
while(thingsToDraw.length > 0) {
var age = computeAge(thingsToDraw[0]);
if (age < ageLimit) {
break;
}
// remove thing that's too old
thingsToDraw.shift();
}
}
function drawThings() {
gl.clear(gl.CLEAR_BUFFER_BIT);
thingsToDraw.forEach(function(thing) {
var age = computeAge(thing);
var lerp = age / ageLimit;
var x = gl.canvas.width * thing.x;
var y = gl.canvas.height * thing.y;
var radius = 10 + lerp * 10; // 10 to 20
var color = [0, 0, 0, 1 - lerp];
drawCircle(gl, x, y, radius, color);
});
}
function drawCircle(gl, x, y, radius, color) {
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var matrix = [
2 / gl.canvas.width * radius, 0, 0, 0,
0, 2 / gl.canvas.height * radius, 0, 0,
0, 0, 1, 0,
x / gl.canvas.width * 2 - 1, y / gl.canvas.height * 2 - 1, 0, 1,
];
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLocation, false, matrix);
gl.uniform4fv(colorLocation, color);
gl.drawArrays(gl.TRIANGLES, 0, numVerts);
}
function ortho(width, height) {
return [
2 / (width), 0, 0, 0,
0, 2 / (height), 0, 0,
0, 0, 1, 0, 0,
(width) / (-width), (height) / (-height), -1, 1,
];
}
var then = 0;
function process(time) {
currentTime = time * 0.001;
var deltaTime = currentTime - then;
then = currentTime;
birthTimer -= deltaTime;
if (birthTimer <= 0) {
addThingToDraw();
birthTimer = birthDuration;
}
removeOldThings();
drawThings();
requestAnimationFrame(process);
}
requestAnimationFrame(process);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<canvas></canvas>
I didn't want to include a matrix library but you can read about matrices here and because almost everyone into issues when graduating from one shape/shader to 2 you probably want to read this about drawing multiple things

Generate grid mesh

I need a mesh of quads so I can have 200x200 vertices and UV coordinates from 0 to 1 in both X and Y.
I can't write this by hand, any way to generate such a mesh?
You want 200x200 vertices or 200x200 quads?
with indices
var positions = [];
var uvs = [];
var indices = [];
var quadsAcross = 200;
var quadsDown = 200;
for (var y = 0; y <= quadsDown; ++y) {
var v = y / quadsDown;
for (var x = 0; x <= quadsAcross; ++x) {
var u = x / quadsAcross;
positions.push(u, v);
uvs.push(u, v);
}
}
var rowSize = (quadsAcross + 1);
for (var y = 0; y < quadsDown; ++y) {
var rowOffset0 = (y + 0) * rowSize;
var rowOffset1 = (y + 1) * rowSize;
for (var x = 0; x < quadsAcross; ++x) {
var offset0 = rowOffset0 + x;
var offset1 = rowOffset1 + x;
indices.push(offset0, offset0 + 1, offset1);
indices.push(offset1, offset0 + 1, offset1 + 1);
}
}
var positions = [];
var uvs = [];
var indices = [];
var quadsAcross = 200;
var quadsDown = 200;
for (var y = 0; y <= quadsDown; ++y) {
var v = y / quadsDown;
for (var x = 0; x <= quadsAcross; ++x) {
var u = x / quadsAcross;
positions.push(u, v);
uvs.push(u, v);
}
}
var rowSize = (quadsAcross + 1);
for (var y = 0; y < quadsDown; ++y) {
var rowOffset0 = (y + 0) * rowSize;
var rowOffset1 = (y + 1) * rowSize;
for (var x = 0; x < quadsAcross; ++x) {
var offset0 = rowOffset0 + x;
var offset1 = rowOffset1 + x;
indices.push(offset0, offset0 + 1, offset1);
indices.push(offset1, offset0 + 1, offset1 + 1);
}
}
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: { numComponents: 2, data: positions},
uv: { numComponents: 2, data: uvs },
indices: indices,
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
var scale = 2 + (Math.sin(time) * 0.5 + 0.5) * 16;
var uniforms = {
matrix: [
scale, 0, 0, 0,
0, scale, 0, 0,
0, 0, 1, 0,
-1, -1, 0, 1,
],
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, gl.LINE_STRIP, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
html, body {
width: 100%;
height: 100%;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
}
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<script id="vs" type="notjs">
attribute vec4 position;
attribute vec2 uv;
uniform mat4 matrix;
varying vec2 v_uv;
void main() {
gl_Position = matrix * position;
v_uv = uv;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec2 v_uv;
void main() {
gl_FragColor = vec4(v_uv, 0, 1);
}
</script>
<canvas id="c"></canvas>
without indices
var positions = [];
var uvs = [];
var quadsAcross = 200;
var quadsDown = 200;
for (var y = 0; y < quadsDown; ++y) {
var v0 = (y + 0) / quadsDown;
var v1 = (y + 1) / quadsDown;
for (var x = 0; x < quadsAcross; ++x) {
var u0 = (x + 0) / quadsAcross;
var u1 = (x + 1) / quadsAcross;
positions.push(u0, v0, u1, v0, u0, v1);
positions.push(u0, v1, u1, v0, u1, v1);
uvs.push(u0, v0, u1, v0, u0, v1);
uvs.push(u0, v1, u1, v0, u1, v1);
}
}
That's 200x200 quads which is 201x201 vertices. If you want 200x200 vertices change quadsAcross and quadsDown to 199
var positions = [];
var uvs = [];
var quadsAcross = 200;
var quadsDown = 200;
for (var y = 0; y < quadsDown; ++y) {
var v0 = (y + 0) / quadsDown;
var v1 = (y + 1) / quadsDown;
for (var x = 0; x < quadsAcross; ++x) {
var u0 = (x + 0) / quadsAcross;
var u1 = (x + 1) / quadsAcross;
positions.push(u0, v0, u1, v0, u0, v1);
positions.push(u0, v1, u1, v0, u1, v1);
uvs.push(u0, v0, u1, v0, u0, v1);
uvs.push(u0, v1, u1, v0, u1, v1);
}
}
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: { numComponents: 2, data: positions},
uv: { numComponents: 2, data: uvs },
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
var scale = 2 + (Math.sin(time) * 0.5 + 0.5) * 16;
var uniforms = {
matrix: [
scale, 0, 0, 0,
0, scale, 0, 0,
0, 0, 1, 0,
-1, -1, 0, 1,
],
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, gl.LINE_STRIP, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
html, body {
width: 100%;
height: 100%;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
}
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<script id="vs" type="notjs">
attribute vec4 position;
attribute vec2 uv;
uniform mat4 matrix;
varying vec2 v_uv;
void main() {
gl_Position = matrix * position;
v_uv = uv;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec2 v_uv;
void main() {
gl_FragColor = vec4(v_uv, 0, 1);
}
</script>
<canvas id="c"></canvas>
You can use loop to create this vertices array. Simple:
for (var i = 0; i < 200; i++)
for (var j = 0; j < 200; j++)
{
// fill our array here
}

Categories