how to add ondrag mouse event in this example - javascript
I want a mouse drager event when i drag and move the mouse accordingly with the velocity of mouse movement it shuld rotate and slowly stop.
this html and javascript will give a cube which rotates at time interval 33
window.onload = startDemo;
function Point3D(x,y,z) {
this.x = x;
this.y = y;
this.z = z;
this.rotateX = function(angle) {
var rad, cosa, sina, y, z
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
y = this.y * cosa - this.z * sina
z = this.y * sina + this.z * cosa
return new Point3D(this.x, y, z)
}
this.rotateY = function(angle) {
var rad, cosa, sina, x, z
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
z = this.z * cosa - this.x * sina
x = this.z * sina + this.x * cosa
return new Point3D(x,this.y, z)
}
this.rotateZ = function(angle) {
var rad, cosa, sina, x, y
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
x = this.x * cosa - this.y * sina
y = this.x * sina + this.y * cosa
return new Point3D(x, y, this.z)
}
this.project = function(viewWidth, viewHeight, fov, viewDistance) {
var factor, x, y
factor = fov / (viewDistance + this.z)
x = this.x * factor + viewWidth / 2
y = this.y * factor + viewHeight / 2
return new Point3D(x, y, this.z)
}
}
var vertices = [
new Point3D(-1,1,-1),
new Point3D(1,1,-1),
new Point3D(1,-1,-1),
new Point3D(-1,-1,-1),
new Point3D(-1,1,1),
new Point3D(1,1,1),
new Point3D(1,-1,1),
new Point3D(-1,-1,1)
];
// Define the vertices that compose each of the 6 faces. These numbers are
// indices to the vertex list defined above.
var faces = [[0,1,2,3],[1,5,6,2],[5,4,7,6],[4,0,3,7],[0,4,5,1],[3,2,6,7]];
// Define the colors for each face.
var colors = [[255,0,0],[0,255,0],[0,0,255],[255,255,0],[0,255,255],[255,0,255]];
var angle = 0;
/* Constructs a CSS RGB value from an array of 3 elements. */
function arrayToRGB(arr) {
if( arr.length == 3 ) {
return "rgb(" + arr[0] + "," + arr[1] + "," + arr[2] + ")";
}
return "rgb(0,0,0)";
}
function startDemo() {
canvas = document.getElementById("thecanvas");
if( canvas && canvas.getContext ) {
ctx = canvas.getContext("2d");
}setInterval(loop,33);
}
function loop() {
var t = new Array();
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0,0,400,250);
for( var i = 0; i < vertices.length; i++ ) {
var v = vertices[i];
var r = v.rotateZ(angle).rotateX(angle);
var p = r.project(400,250,200,4);
t.push(p)
}
var avg_z = new Array();
for( var i = 0; i < faces.length; i++ ) {
var f = faces[i];
avg_z[i] = {"index":i, "z":(t[f[0]].z + t[f[1]].z + t[f[2]].z + t[f[3]].z) / 4.0};
}
avg_z.sort(function(a,b) {
return b.z - a.z;
});
for( var i = 0; i < faces.length; i++ ) {
var f = faces[avg_z[i].index]
ctx.fillStyle = arrayToRGB(colors[avg_z[i].index]);
ctx.beginPath()
ctx.moveTo(t[f[0]].x,t[f[0]].y)
ctx.lineTo(t[f[1]].x,t[f[1]].y)
ctx.lineTo(t[f[2]].x,t[f[2]].y)
ctx.lineTo(t[f[3]].x,t[f[3]].y)
ctx.closePath()
ctx.fill()
}
angle += 2
}
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Experiment: A Rotating Solid Cube</title>
</head>
<body>
<canvas id="thecanvas" width="500" height="250">
Your brows<a href=#>Click here</a> to watch the video.
</canvas>
</body>
</html>
please help me with this code
You can use
onmouseover
Follow the link and you will be understand.
http://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_onmouseover
Try this Code
<!doctype html>
<html>
<body>
<canvas width = "570" height = "570" id = "my_Canvas"></canvas>
<script>
/*============= Creating a canvas ======================*/
var canvas = document.getElementById('my_Canvas');
gl = canvas.getContext('experimental-webgl');
/*========== Defining and storing the geometry ==========*/
var vertices = [
-1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1,
-1,-1, 1, 1,-1, 1, 1, 1, 1, -1, 1, 1,
-1,-1,-1, -1, 1,-1, -1, 1, 1, -1,-1, 1,
1,-1,-1, 1, 1,-1, 1, 1, 1, 1,-1, 1,
-1,-1,-1, -1,-1, 1, 1,-1, 1, 1,-1,-1,
-1, 1,-1, -1, 1, 1, 1, 1, 1, 1, 1,-1,
];
var colors = [
5,3,7, 5,3,7, 5,3,7, 5,3,7,
1,1,3, 1,1,3, 1,1,3, 1,1,3,
0,0,1, 0,0,1, 0,0,1, 0,0,1,
1,0,0, 1,0,0, 1,0,0, 1,0,0,
1,1,0, 1,1,0, 1,1,0, 1,1,0,
0,1,0, 0,1,0, 0,1,0, 0,1,0
];
var indices = [
0,1,2, 0,2,3, 4,5,6, 4,6,7,
8,9,10, 8,10,11, 12,13,14, 12,14,15,
16,17,18, 16,18,19, 20,21,22, 20,22,23
];
// Create and store data into vertex buffer
var vertex_buffer = gl.createBuffer ();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// Create and store data into color buffer
var color_buffer = gl.createBuffer ();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
// Create and store data into index buffer
var index_buffer = gl.createBuffer ();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
/*=================== SHADERS =================== */
var vertCode = 'attribute vec3 position;'+
'uniform mat4 Pmatrix;'+
'uniform mat4 Vmatrix;'+
'uniform mat4 Mmatrix;'+
'attribute vec3 color;'+//the color of the point
'varying vec3 vColor;'+
'void main(void) { '+//pre-built function
'gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);'+
'vColor = color;'+
'}';
var fragCode = 'precision mediump float;'+
'varying vec3 vColor;'+
'void main(void) {'+
'gl_FragColor = vec4(vColor, 1.);'+
'}';
var vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);
var shaderprogram = gl.createProgram();
gl.attachShader(shaderprogram, vertShader);
gl.attachShader(shaderprogram, fragShader);
gl.linkProgram(shaderprogram);
/*======== Associating attributes to vertex shader =====*/
var _Pmatrix = gl.getUniformLocation(shaderprogram, "Pmatrix");
var _Vmatrix = gl.getUniformLocation(shaderprogram, "Vmatrix");
var _Mmatrix = gl.getUniformLocation(shaderprogram, "Mmatrix");
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
var _position = gl.getAttribLocation(shaderprogram, "position");
gl.vertexAttribPointer(_position, 3, gl.FLOAT, false,0,0);
gl.enableVertexAttribArray(_position);
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
var _color = gl.getAttribLocation(shaderprogram, "color");
gl.vertexAttribPointer(_color, 3, gl.FLOAT, false,0,0) ;
gl.enableVertexAttribArray(_color);
gl.useProgram(shaderprogram);
/*==================== MATRIX ====================== */
function get_projection(angle, a, zMin, zMax) {
var ang = Math.tan((angle*.5)*Math.PI/180);//angle*.5
return [
0.5/ang, 0 , 0, 0,
0, 0.5*a/ang, 0, 0,
0, 0, -(zMax+zMin)/(zMax-zMin), -1,
0, 0, (-2*zMax*zMin)/(zMax-zMin), 0
];
}
var proj_matrix = get_projection(40, canvas.width/canvas.height, 1, 100);
var mo_matrix = [ 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 ];
var view_matrix = [ 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 ];
view_matrix[14] = view_matrix[14]-6;
/*================= Mouse events ======================*/
var AMORTIZATION = 0.95;
var drag = false;
var old_x, old_y;
var dX = 0, dY = 0;
var mouseDown = function(e) {
drag = true;
old_x = e.pageX, old_y = e.pageY;
e.preventDefault();
return false;
};
var mouseUp = function(e){
drag = false;
};
var mouseMove = function(e) {
if (!drag) return false;
dX = (e.pageX-old_x)*2*Math.PI/canvas.width,
dY = (e.pageY-old_y)*2*Math.PI/canvas.height;
THETA+= dX;
PHI+=dY;
old_x = e.pageX, old_y = e.pageY;
e.preventDefault();
};
canvas.addEventListener("mousedown", mouseDown, false);
canvas.addEventListener("mouseup", mouseUp, false);
canvas.addEventListener("mouseout", mouseUp, false);
canvas.addEventListener("mousemove", mouseMove, false);
/*=========================rotation================*/
function rotateX(m, angle) {
var c = Math.cos(angle);
var s = Math.sin(angle);
var mv1 = m[1], mv5 = m[5], mv9 = m[9];
m[1] = m[1]*c-m[2]*s;
m[5] = m[5]*c-m[6]*s;
m[9] = m[9]*c-m[10]*s;
m[2] = m[2]*c+mv1*s;
m[6] = m[6]*c+mv5*s;
m[10] = m[10]*c+mv9*s;
}
function rotateY(m, angle) {
var c = Math.cos(angle);
var s = Math.sin(angle);
var mv0 = m[0], mv4 = m[4], mv8 = m[8];
m[0] = c*m[0]+s*m[2];
m[4] = c*m[4]+s*m[6];
m[8] = c*m[8]+s*m[10];
m[2] = c*m[2]-s*mv0;
m[6] = c*m[6]-s*mv4;
m[10] = c*m[10]-s*mv8;
}
/*=================== Drawing =================== */
var THETA = 0,
PHI = 0;
var time_old = 0;
var animate = function(time) {
var dt = time-time_old;
if (!drag) {
dX *= AMORTIZATION, dY*=AMORTIZATION;
THETA+=dX, PHI+=dY;
}
//set model matrix to I4
mo_matrix[0] = 1, mo_matrix[1] = 0, mo_matrix[2] = 0,
mo_matrix[3] = 0,
mo_matrix[4] = 0, mo_matrix[5] = 1, mo_matrix[6] = 0,
mo_matrix[7] = 0,
mo_matrix[8] = 0, mo_matrix[9] = 0, mo_matrix[10] = 1,
mo_matrix[11] = 0,
mo_matrix[12] = 0, mo_matrix[13] = 0, mo_matrix[14] = 0,
mo_matrix[15] = 1;
rotateY(mo_matrix, THETA);
rotateX(mo_matrix, PHI);
time_old = time;
gl.enable(gl.DEPTH_TEST);
// gl.depthFunc(gl.LEQUAL);
gl.clearColor(0.5, 0.5, 0.5, 0.9);
gl.clearDepth(1.0);
gl.viewport(0.0, 0.0, canvas.width, canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv(_Pmatrix, false, proj_matrix);
gl.uniformMatrix4fv(_Vmatrix, false, view_matrix);
gl.uniformMatrix4fv(_Mmatrix, false, mo_matrix);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
window.requestAnimationFrame(animate);
}
animate(0);
</script>
</body>
</html>
window.onload = startDemo;
function Point3D(x,y,z) {
this.x = x;
this.y = y;
this.z = z;
this.rotateX = function(angle) {
var rad, cosa, sina, y, z
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
y = this.y * cosa - this.z * sina
z = this.y * sina + this.z * cosa
return new Point3D(this.x, y, z)
}
this.rotateY = function(angle) {
var rad, cosa, sina, x, z
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
z = this.z * cosa - this.x * sina
x = this.z * sina + this.x * cosa
return new Point3D(x,this.y, z)
}
this.rotateZ = function(angle) {
var rad, cosa, sina, x, y
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
x = this.x * cosa - this.y * sina
y = this.x * sina + this.y * cosa
return new Point3D(x, y, this.z)
}
this.project = function(viewWidth, viewHeight, fov, viewDistance) {
var factor, x, y
factor = fov / (viewDistance + this.z)
x = this.x * factor + viewWidth / 2
y = this.y * factor + viewHeight / 2
return new Point3D(x, y, this.z)
}
}
var vertices = [
new Point3D(-1,1,-1),
new Point3D(1,1,-1),
new Point3D(1,-1,-1),
new Point3D(-1,-1,-1),
new Point3D(-1,1,1),
new Point3D(1,1,1),
new Point3D(1,-1,1),
new Point3D(-1,-1,1)
];
// Define the vertices that compose each of the 6 faces. These numbers are
// indices to the vertex list defined above.
var faces = [[0,1,2,3],[1,5,6,2],[5,4,7,6],[4,0,3,7],[0,4,5,1],[3,2,6,7]];
// Define the colors for each face.
var colors = [[255,0,0],[0,255,0],[0,0,255],[255,255,0],[0,255,255],[255,0,255]];
var angle = 0;
/* Constructs a CSS RGB value from an array of 3 elements. */
function arrayToRGB(arr) {
if( arr.length == 3 ) {
return "rgb(" + arr[0] + "," + arr[1] + "," + arr[2] + ")";
}
return "rgb(0,0,0)";
}
function startDemo() {
canvas = document.getElementById("thecanvas");
if( canvas && canvas.getContext ) {
ctx = canvas.getContext("2d");
}setInterval(loop,33);
}
function loop() {
var t = new Array();
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(0,0,400,250);
for( var i = 0; i < vertices.length; i++ ) {
var v = vertices[i];
var r = v.rotateZ(angle).rotateX(angle);
var p = r.project(400,250,200,4);
t.push(p)
}
var avg_z = new Array();
for( var i = 0; i < faces.length; i++ ) {
var f = faces[i];
avg_z[i] = {"index":i, "z":(t[f[0]].z + t[f[1]].z + t[f[2]].z + t[f[3]].z) / 4.0};
}
avg_z.sort(function(a,b) {
return b.z - a.z;
});
for( var i = 0; i < faces.length; i++ ) {
var f = faces[avg_z[i].index]
ctx.fillStyle = arrayToRGB(colors[avg_z[i].index]);
ctx.beginPath()
ctx.moveTo(t[f[0]].x,t[f[0]].y)
ctx.lineTo(t[f[1]].x,t[f[1]].y)
ctx.lineTo(t[f[2]].x,t[f[2]].y)
ctx.lineTo(t[f[3]].x,t[f[3]].y)
ctx.closePath()
ctx.fill()
}
angle += 2
}
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Experiment: A Rotating Solid Cube</title>
</head>
<body>
<canvas id="thecanvas" width="500" height="250">
Your brows<a href=#>Click here</a> to watch the video.
</canvas>
</body>
</html>
Related
webgl how to draw a ring
As shown below, all I can think of is to describe all the points on a circle and then use triangulation to draw a ring with width I can also think of a way to use overlay. First draw a circle and then draw a circle with a smaller radius const TAU_SEGMENTS = 360; const TAU = Math.PI * 2; export function arc(x0: number, y0: number, radius: number, startAng = 0, endAng = Math.PI * 2) { const ang = Math.min(TAU, endAng - startAng); const ret = ang === TAU ? [] : [x0, y0]; const segments = Math.round(TAU_SEGMENTS * ang / TAU); for(let i = 0; i <= segments; i++) { const x = x0 + radius * Math.cos(startAng + ang * i / segments); const y = y0 + radius * Math.sin(startAng + ang * i / segments); ret.push(x, y); } return ret; } const gl = container.current.getContext("webgl2"); const program = initProgram(gl); const position = new Float32Array(arc(0, 0, 1).concat(...arc(0, 0, 0.9))); let buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW); let gl_position = gl.getAttribLocation(program, "position"); gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(gl_position); gl.drawArrays(gl.LINE_STRIP, 0, position.length / 2); The final effect of the code I wrote is as follows. May I ask how I should modify it to become the same as the above picture
You have to add a color attributes for the vertices and you have to draw a gl.TRIANGLE_STRIP primitive instead of a gl.LINE_STRIP primitive. The color can be calculated from the angle. Map the angle from the range [0, PI] to the range [0, 1] and use the formula for the hue value from the HSL and HSV color space: function HUEtoRGB(hue) { return [ Math.min(1, Math.max(0, Math.abs(hue * 6.0 - 3.0) - 1.0)), Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 2.0))), Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 4.0))) ]; } Create vertices in pairs for the inner and outer arcs with the corresponding color attribute: const TAU_SEGMENTS = 360; const TAU = Math.PI * 2; function arc(x0, y0, innerRadius, outerRadius, startAng = 0, endAng = Math.PI * 2) { const ang = Math.min(TAU, endAng - startAng); const position = ang === TAU ? [] : [x0, y0]; const color = [] const segments = Math.round(TAU_SEGMENTS * ang / TAU); for(let i = 0; i <= segments; i++) { const angle = startAng + ang * i / segments; const x1 = x0 + innerRadius * Math.cos(angle); const y1 = y0 + innerRadius * Math.sin(angle); const x2 = x0 + outerRadius * Math.cos(angle); const y2 = y0 + outerRadius * Math.sin(angle); position.push(x1, y1, x2, y2); let hue = (Math.PI/2 - angle) / (2 * Math.PI); if (hue < 0) hue += 1; const rgb = HUEtoRGB(hue); color.push(...rgb); color.push(...rgb); } return { 'position': position, 'color': color }; } Create a shader with a color attribute and pass the color attribute from the vertex to the fragment shader: #version 300 es precision highp float; in vec2 position; in vec4 color; out vec4 vColor; void main() { vColor = color; gl_Position = vec4(position.xy, 0.0, 1.0); } #version 300 es precision highp float; in vec4 vColor; out vec4 fragColor; void main() { fragColor = vColor; } Vertex specification: const attributes = arc(0, 0, 0.6, 0.9); position = new Float32Array(attributes.position); color = new Float32Array(attributes.color); let position_buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer); gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW); let gl_position = gl.getAttribLocation(program, "position"); gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(gl_position); let color_buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer); gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW); let gl_color = gl.getAttribLocation(program, "color"); gl.vertexAttribPointer(gl_color, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(gl_color); Complet and runnable example: const canvas = document.getElementById( "ogl-canvas"); const gl = canvas.getContext("webgl2"); const program = gl.createProgram(); for (let i = 0; i < 2; ++i) { let source = document.getElementById(i==0 ? "draw-shader-vs" : "draw-shader-fs").text; let shaderObj = gl.createShader(i==0 ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER); gl.shaderSource(shaderObj, source); gl.compileShader(shaderObj); let status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS); if (!status) alert(gl.getShaderInfoLog(shaderObj)); gl.attachShader(program, shaderObj); gl.linkProgram(program); } status = gl.getProgramParameter(program, gl.LINK_STATUS); if ( !status ) alert(gl.getProgramInfoLog(program)); gl.useProgram(program); function HUEtoRGB(hue) { return [ Math.min(1, Math.max(0, Math.abs(hue * 6.0 - 3.0) - 1.0)), Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 2.0))), Math.min(1, Math.max(0, 2.0 - Math.abs(hue * 6.0 - 4.0))) ]; } const TAU_SEGMENTS = 360; const TAU = Math.PI * 2; function arc(x0, y0, innerRadius, outerRadius, startAng = 0, endAng = Math.PI * 2) { const ang = Math.min(TAU, endAng - startAng); const position = ang === TAU ? [] : [x0, y0]; const color = [] const segments = Math.round(TAU_SEGMENTS * ang / TAU); for(let i = 0; i <= segments; i++) { const angle = startAng + ang * i / segments; const x1 = x0 + innerRadius * Math.cos(angle); const y1 = y0 + innerRadius * Math.sin(angle); const x2 = x0 + outerRadius * Math.cos(angle); const y2 = y0 + outerRadius * Math.sin(angle); position.push(x1, y1, x2, y2); let hue = (Math.PI/2 - angle) / (2 * Math.PI); if (hue < 0) hue += 1; const rgb = HUEtoRGB(hue); color.push(...rgb); color.push(...rgb); } return { 'position': position, 'color': color }; } const attributes = arc(0, 0, 0.6, 0.9); position = new Float32Array(attributes.position); color = new Float32Array(attributes.color); let position_buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer); gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW); let gl_position = gl.getAttribLocation(program, "position"); gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(gl_position); let color_buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer); gl.bufferData(gl.ARRAY_BUFFER, color, gl.STATIC_DRAW); let gl_color = gl.getAttribLocation(program, "color"); gl.vertexAttribPointer(gl_color, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(gl_color); gl.enable( gl.DEPTH_TEST ); gl.clearColor( 0.0, 0.0, 0.0, 1.0 ); //vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight]; vp_size = [window.innerWidth, window.innerHeight]; vp_size = [256, 256] canvas.width = vp_size[0]; canvas.height = vp_size[1]; gl.viewport( 0, 0, canvas.width, canvas.height ); gl.clearColor(1, 1, 1, 1); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.drawArrays(gl.TRIANGLE_STRIP, 0, attributes.position.length / 2); <script id="draw-shader-vs" type="x-shader/x-vertex">#version 300 es precision highp float; in vec2 position; in vec4 color; out vec4 vColor; void main() { vColor = color; gl_Position = vec4(position.xy, 0.0, 1.0); } </script> <script id="draw-shader-fs" type="x-shader/x-fragment">#version 300 es precision highp float; in vec4 vColor; out vec4 fragColor; void main() { fragColor = vColor; } </script> <canvas id="ogl-canvas" style="border: none"></canvas>
WebGL fade-out does not work for white color
I am trying to adapt a webgl code that generates firework explositions, where projectiles fade out from canvas by means of painting over with 0.1 alpha (as I understand it). However, this technique somehow doesn't work for the explosition of white color. You can see that white projectiles leave traces and never fully disappear, while other colors work fine (or maybe they also don't work, but I don't see the traces on black background). Can somebody help me understand what is happening here? // from https://codepen.io/towc/pen/oBvYEL var gl = c.getContext('webgl2', {preserveDrawingBuffer: true}) , w = c.width = window.innerWidth , h = c.height = window.innerHeight , webgl = {} , opts = { projectileAlpha: .8, projectileLineWidth: 2, fireworkAngleSpan: .5, baseFireworkVel: 3, gravity: .02, xFriction: .995, baseShardVel: 1, addedShardVel: .2, fireworks: 1, // 1 firework for each 10x10 pixels, baseShardsParFirework: 10, addedShardsParFirework: 10, shardFireworkVelMultiplier: .3 } // updated to use WebGL2 and GL ES 3.0 // pass full color instead of just hue // remove moving projectile, keep only the explosion webgl.vertexShaderSource = `#version 300 es uniform int u_mode; uniform vec2 u_res; in vec4 a_data; out vec4 v_color; vec3 c2rgb(int color_int){ int r = color_int >> 16; int g = color_int >> 8 & 0xff; int b = color_int & 0xff; return vec3(r/255, g/255, b/255); } void clear(){ gl_Position = vec4( a_data.xy, 0, 1 ); v_color = vec4( 0, 0, 0, a_data.w ); } void draw(){ gl_Position = vec4( vec2( 1, -1 ) * ( ( a_data.xy / u_res ) * 2. - 1. ), 0, 1 ); v_color = vec4( 1, 1, 1, a_data.w ); } void main(){ if( u_mode == 0 ) draw(); else clear(); } `; webgl.fragmentShaderSource = `#version 300 es precision mediump float; in vec4 v_color; out vec4 fragColor; void main(){ fragColor = v_color; } `; webgl.vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(webgl.vertexShader, webgl.vertexShaderSource); gl.compileShader(webgl.vertexShader); if (!gl.getShaderParameter(webgl.vertexShader, gl.COMPILE_STATUS)) console.log(gl.getShaderInfoLog(webgl.vertexShader)); webgl.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(webgl.fragmentShader, webgl.fragmentShaderSource); gl.compileShader(webgl.fragmentShader); if (!gl.getShaderParameter(webgl.fragmentShader, gl.COMPILE_STATUS)) console.log(gl.getShaderInfoLog(webgl.fragmentShader)); webgl.shaderProgram = gl.createProgram(); gl.attachShader(webgl.shaderProgram, webgl.vertexShader); gl.attachShader(webgl.shaderProgram, webgl.fragmentShader); gl.linkProgram(webgl.shaderProgram); gl.useProgram(webgl.shaderProgram); webgl.dataAttribLoc = gl.getAttribLocation(webgl.shaderProgram, 'a_data'); webgl.dataBuffer = gl.createBuffer(); gl.enableVertexAttribArray(webgl.dataAttribLoc); gl.bindBuffer(gl.ARRAY_BUFFER, webgl.dataBuffer); gl.vertexAttribPointer(webgl.dataAttribLoc, 4, gl.FLOAT, false, 0, 0); webgl.resUniformLoc = gl.getUniformLocation(webgl.shaderProgram, 'u_res'); webgl.modeUniformLoc = gl.getUniformLocation(webgl.shaderProgram, 'u_mode'); gl.viewport(0, 0, w, h); gl.uniform2f(webgl.resUniformLoc, w, h); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); gl.enable(gl.BLEND); gl.lineWidth(opts.projectileLineWidth); webgl.data = []; webgl.clear = function () { gl.uniform1i(webgl.modeUniformLoc, 1); let a = .1; webgl.data = [ -1, -1, 0, 0.1, 1, -1, 0, 0.1, -1, 1, 0, 0.1, -1, 1, 0, 0.1, 1, -1, 0, 0.1, 1, 1, 0, 0.1 ]; webgl.draw(gl.TRIANGLES); gl.uniform1i(webgl.modeUniformLoc, 0); webgl.data.length = 0; } webgl.draw = function (glType) { gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(webgl.data), gl.STATIC_DRAW); gl.drawArrays(glType, 0, webgl.data.length / 4); } let fireworks = [] , tick = 0 , sins = [] , coss = [] , maxShardsParFirework = opts.baseShardsParFirework + opts.addedShardsParFirework , tau = 6.283185307179586476925286766559; for (let i = 0; i < maxShardsParFirework; ++i) { sins[i] = Math.sin(tau * i / maxShardsParFirework); coss[i] = Math.cos(tau * i / maxShardsParFirework); } function Firework() { this.reset(); this.shards = []; for (let i = 0; i < maxShardsParFirework; ++i) this.shards.push(new Shard(this)); } Firework.prototype.reset = function () { let angle = -Math.PI / 2 + (Math.random() - .5) * opts.fireworkAngleSpan , vel = opts.baseFireworkVel * Math.random(); this.mode = 0; this.vx = vel * Math.cos(angle); this.vy = vel * Math.sin(angle); this.x = Math.random() * w; this.y = Math.random() * h; this.hue = 0; let ph = this.hue , px = this.x , py = this.y; webgl.data.push( px, py, ph, opts.projectileAlpha * .2, this.x, this.y, this.hue, opts.projectileAlpha * .2); } Firework.prototype.step = function () { if (this.mode === 0) { this.mode = 1; this.shardAmount = opts.baseShardsParFirework + opts.addedShardsParFirework * Math.random() | 0; let baseAngle = Math.random() * tau , x = Math.cos(baseAngle) , y = Math.sin(baseAngle) , sin = sins[this.shardAmount] , cos = coss[this.shardAmount]; for (let i = 0; i < this.shardAmount; ++i) { let vel = opts.baseShardVel + opts.addedShardVel * Math.random(); this.shards[i].reset(x * vel, y * vel) let X = x; x = x * cos - y * sin; y = y * cos + X * sin; } } if (this.mode === 1) { this.ph = this.hue this.hue = 0; let allDead = true; for (let i = 0; i < this.shardAmount; ++i) { let shard = this.shards[i]; if (!shard.dead) { shard.step(); allDead = false; } } if (allDead) this.reset(); } } function Shard(parent) { this.parent = parent; } Shard.prototype.reset = function (vx, vy) { this.x = this.parent.x; this.y = this.parent.y; this.vx = this.parent.vx * opts.shardFireworkVelMultiplier + vx; this.vy = this.parent.vy * opts.shardFireworkVelMultiplier + vy; this.starty = this.y; this.dead = false; this.tick = 1; } Shard.prototype.step = function () { this.tick += .05; let px = this.x , py = this.y; this.x += this.vx *= opts.xFriction; this.y += this.vy += opts.gravity; var proportion = 1 - (this.y - this.starty) / (h - this.starty); webgl.data.push( px, py, this.parent.ph, opts.projectileAlpha / this.tick, this.x, this.y, this.parent.hue, opts.projectileAlpha / this.tick); if (this.y > h) this.dead = true; } function anim() { window.requestAnimationFrame(anim); webgl.clear(); ++tick; if (fireworks.length < opts.fireworks) fireworks.push(new Firework); fireworks.map(function (firework) { firework.step(); }); webgl.draw(gl.LINES); } anim(); window.addEventListener('resize', function () { w = c.width = window.innerWidth; h = c.height = window.innerHeight; gl.viewport(0, 0, w, h); gl.uniform2f(webgl.resUniformLoc, w, h); }); body { overflow: hidden; margin:0; } #c { position: absolute; top: 0; left: 0; background-color: #111; } <canvas id="c"> </canvas>
Change the color in WebGL + JavaScript
I am using the following code to generate an animation of particles. I would like to change the colors of individual particles to random colors in each frame. Is it possible to do this and, if so, how can I achieve it? Additionally, is it possible to change the rotation of the particles in each render? Any help with either of these questions would be greatly appreciated. <!doctype html> <head> <title>Triangles</title> <style> html, body { background: #000; height: 100%; margin: 0; } canvas { width: 1280px; height: 720px; position: absolute; margin: auto; top: 0; right: 0; left: 0; bottom: 0; } </style> </head> <body> <script> 'use strict'; const triangleCount = 2e5; const antialias = true; const generateTriangles = (count, width, height) => { const coords = new Float32Array(9 * count); for (var i = 0; i < coords.length;) { const x = Math.random() * 2 - 1; const y = Math.random() * 2 - 1; const z = Math.random() * 2 - 1; const theta = Math.random() * Math.PI; const ax = 10 * Math.cos(theta) / width; const ay = 10 * Math.sin(theta) / height; const bx = 10 * Math.cos(theta + 0.1) / width; const by = 10 * Math.sin(theta + 0.1) / height; coords[i++] = x + ax; coords[i++] = y + ay; coords[i++] = z; coords[i++] = x + bx; coords[i++] = y + by; coords[i++] = z; coords[i++] = x - ax; coords[i++] = y - ay; coords[i++] = z; coords[i++] = x - ax; coords[i++] = y - ay; coords[i++] = z; coords[i++] = x - bx; coords[i++] = y - by; coords[i++] = z; coords[i++] = x + ax; coords[i++] = y + ay; coords[i++] = z; } return coords; }; const vertexShaderSource = ` precision lowp float; attribute vec3 aPosition; uniform float uWobble; void main() { float p = 0.1 / (0.3 * aPosition.z - 0.14 + 0.1 * uWobble); gl_Position = vec4(p * aPosition.x, p * aPosition.y, aPosition.z, 1); } `; const fragmentShaderSource = ` precision lowp float; void main() { float z = gl_FragCoord.z; gl_FragColor = vec4(1.5 * z, z * z, z, 1.7); } `; const canvas = document.createElement('canvas'); document.body.appendChild(canvas); canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; const gl = canvas.getContext('webgl', { alpha: false, antialias }); const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexShaderSource); gl.compileShader(vertexShader); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(fragmentShader); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); const aVertexPosition = gl.getAttribLocation(program, 'aPosition'); gl.enableVertexAttribArray(aVertexPosition); const uWobble = gl.getUniformLocation(program, 'uWobble'); gl.uniform1f(uWobble, 1); const vertices = generateTriangles(triangleCount, canvas.width, canvas.height); const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0); const render = (timestamp) => { requestAnimationFrame(render); gl.uniform1f(uWobble, Math.sin(0.00002 * timestamp)); gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 3); }; window.requestAnimationFrame(render); </script> </body>
Here is solution to get semi-random color for each particle for each frame - change your fragmentShaderSource to below: const fragmentShaderSource = ` precision lowp float; uniform float uWobble; void main() { float r = fract(sin(uWobble*10000.0*gl_FragCoord.z)); float g = fract(cos(uWobble*10000.0*gl_FragCoord.z)*43758.5453); float b = fract(cos(uWobble*10000.0*gl_FragCoord.z)*12.9898); gl_FragColor = vec4(r, g, b, 1.7); } `; Working example here on fiddle (for easy edit) or below: 'use strict'; const triangleCount = 2e5; const antialias = true; const generateTriangles = (count, width, height) => { const coords = new Float32Array(9 * count); for (var i = 0; i < coords.length;) { const x = Math.random() * 2 - 1; const y = Math.random() * 2 - 1; const z = Math.random() * 2 - 1; const theta = Math.random() * Math.PI; const ax = 10 * Math.cos(theta) / width; const ay = 10 * Math.sin(theta) / height; const bx = 10 * Math.cos(theta + 0.1) / width; const by = 10 * Math.sin(theta + 0.1) / height; coords[i++] = x + ax; coords[i++] = y + ay; coords[i++] = z; coords[i++] = x + bx; coords[i++] = y + by; coords[i++] = z; coords[i++] = x - ax; coords[i++] = y - ay; coords[i++] = z; coords[i++] = x - ax; coords[i++] = y - ay; coords[i++] = z; coords[i++] = x - bx; coords[i++] = y - by; coords[i++] = z; coords[i++] = x + ax; coords[i++] = y + ay; coords[i++] = z; } return coords; }; const vertexShaderSource = ` precision lowp float; attribute vec3 aPosition; uniform float uWobble; void main() { float p = 0.1 / (0.3 * aPosition.z - 0.14 + 0.1 * uWobble); gl_Position = vec4(p * aPosition.x, p * aPosition.y, aPosition.z, 1); } `; const fragmentShaderSource = ` precision lowp float; uniform float uWobble; void main() { float r = fract(sin(uWobble*10000.0*gl_FragCoord.z)); float g = fract(cos(uWobble*10000.0*gl_FragCoord.z)*43758.5453); float b = fract(cos(uWobble*10000.0*gl_FragCoord.z)*12.9898); gl_FragColor = vec4(r, g, b, 1.7); } `; const canvas = document.createElement('canvas'); document.body.appendChild(canvas); canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; const gl = canvas.getContext('webgl', { alpha: false, antialias }); const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexShaderSource); gl.compileShader(vertexShader); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentShaderSource); gl.compileShader(fragmentShader); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); const aVertexPosition = gl.getAttribLocation(program, 'aPosition'); gl.enableVertexAttribArray(aVertexPosition); const uWobble = gl.getUniformLocation(program, 'uWobble'); gl.uniform1f(uWobble, 1); const vertices = generateTriangles(triangleCount, canvas.width, canvas.height); const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0); const render = (timestamp) => { requestAnimationFrame(render); gl.uniform1f(uWobble, Math.sin(0.00002 * timestamp)); gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 3); }; window.requestAnimationFrame(render); html, body { background: #000; height: 100%; margin: 0; } canvas { width: 1280px; height: 720px; position: absolute; margin: auto; top: 0; right: 0; left: 0; bottom: 0; }
How To Get Value Of Canvas From A Wheel
How To Get The Value Of A Canvas . I have wheel which is rotating on mouse over the wheel stops now i want to echo out the value on which it was stopped. It is printing the whole array . Not the one on which the wheel stop. $("#canvas").mouseover(function(){ backup= ctx; alert(myData); ctx = null; }); this is the fiddle: https://jsfiddle.net/z61n9ccx/3/ Here is the full code: var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); var cw = canvas.width; var ch = canvas.height; var PI2 = Math.PI * 2; var myData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; var cx = 150; var cy = 150; var radius = 150; var wheel = document.createElement('canvas'); var wheelCtx = wheel.getContext('2d'); var indicator = document.createElement('canvas'); var indicatorCtx = indicator.getContext('2d'); var angle = PI2 - PI2 / 4; var myColor = []; for (var i = 0; i < myData.length; i++) { myColor.push(randomColor()); } makeWheel(); makeIndicator(); requestAnimationFrame(animate); function makeWheel() { wheel.width = wheel.height = radius * 2 + 2; wheelCtx.lineWidth = 1; wheelCtx.font = '40px Pacifico, cursive'; wheelCtx.textAlign = 'center'; wheelCtx.textBaseline = 'middle'; var cx = wheel.width / 2; var cy = wheel.height / 2; var sweepAngle = PI2 / myData.length; var startAngle = 0; for (var i = 0; i < myData.length; i++) { // calc ending angle based on starting angle var endAngle = startAngle + sweepAngle; // draw the wedge wheelCtx.beginPath(); wheelCtx.moveTo(cx, cy); wheelCtx.arc(cx, cy, radius, startAngle, endAngle, false); wheelCtx.closePath(); wheelCtx.fillStyle = myColor[i]; wheelCtx.strokeStyle = 'black'; wheelCtx.fill(); wheelCtx.stroke(); // draw the label var midAngle = startAngle + (endAngle - startAngle) / 2; var labelRadius = radius * .85; var x = cx + (labelRadius) * Math.cos(midAngle); var y = cy + (labelRadius) * Math.sin(midAngle); wheelCtx.fillStyle = 'gold'; wheelCtx.fillText(myData[i], x, y); wheelCtx.strokeText(myData[i], x, y); // increment angle startAngle += sweepAngle; } } function makeIndicator() { indicator.width = indicator.height = radius + radius / 10; indicatorCtx.font = '40px Georgia'; indicatorCtx.textAlign = 'center'; indicatorCtx.textBaseline = 'middle'; indicatorCtx.fillStyle = 'skyblue'; indicatorCtx.strokeStyle = 'blue'; indicatorCtx.lineWidth = 1; var cx = indicator.width / 2; var cy = indicator.height / 2; indicatorCtx.beginPath(); indicatorCtx.moveTo(cx - radius / 8, cy); indicatorCtx.lineTo(cx, cy - indicator.height / 2); indicatorCtx.lineTo(cx + radius / 8, cy); indicatorCtx.closePath(); indicatorCtx.fillStyle = 'skyblue' indicatorCtx.fill(); indicatorCtx.stroke(); indicatorCtx.beginPath(); indicatorCtx.arc(cx, cy, radius / 3, 0, PI2); indicatorCtx.closePath(); indicatorCtx.fill(); indicatorCtx.stroke(); indicatorCtx.fillStyle = 'blue'; indicatorCtx.fillText('Prizes', cx, cy); } function animate(time) { ctx.clearRect(0, 0, cw, ch); ctx.translate(cw / 2, ch / 2); ctx.rotate(angle); ctx.drawImage(wheel, -wheel.width / 2, -wheel.height / 2); ctx.rotate(-angle); ctx.translate(-cw / 2, -ch / 2); ctx.drawImage(indicator, cw / 2 - indicator.width / 2, ch / 2 - indicator.height / 2) angle += PI2 / 360; requestAnimationFrame(animate); } function randomColor() { return ('#' + Math.floor(Math.random() * 16777215).toString(16)); } var backup = null; $("#canvas").mouseover(function() { backup = ctx; alert(myData); ctx = null; }); $("#canvas").mouseout(function() { // backup= ctx; ctx = backup; animate(); }); <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> <canvas id="canvas" width="600" height="600" style="background-color:#ffff"> </canvas>
I added a counter, and then use that as a index: https://jsfiddle.net/Twisty/L6nws9yz/2/ HTML <canvas id="canvas" width="310" height="310" style="background-color:#ffff"> </canvas> <div id="counterBox"> <label>Counter:</label> <span></span> </div> <div id="countBox"> <label>Index:</label> <span></span> </div> JS var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); var cw = canvas.width; var ch = canvas.height; var PI2 = Math.PI * 2; var myData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; var cx = 150; var cy = 150; var radius = 150; var wheel = document.createElement('canvas'); var wheelCtx = wheel.getContext('2d'); var indicator = document.createElement('canvas'); var indicatorCtx = indicator.getContext('2d'); var currentSelection = 12; var counter = 360; var angle = PI2 - PI2 / 4; var myColor = []; for (var i = 0; i < myData.length; i++) { myColor.push(randomColor()); } makeWheel(); makeIndicator(); requestAnimationFrame(animate); function makeWheel() { wheel.width = wheel.height = radius * 2 + 2; wheelCtx.lineWidth = 1; wheelCtx.font = '40px Pacifico, cursive'; wheelCtx.textAlign = 'center'; wheelCtx.textBaseline = 'middle'; var cx = wheel.width / 2; var cy = wheel.height / 2; var sweepAngle = PI2 / myData.length; var startAngle = 0; for (var i = 0; i < myData.length; i++) { // calc ending angle based on starting angle var endAngle = startAngle + sweepAngle; // draw the wedge wheelCtx.beginPath(); wheelCtx.moveTo(cx, cy); wheelCtx.arc(cx, cy, radius, startAngle, endAngle, false); wheelCtx.closePath(); wheelCtx.fillStyle = myColor[i]; wheelCtx.strokeStyle = 'black'; wheelCtx.fill(); wheelCtx.stroke(); // draw the label var midAngle = startAngle + (endAngle - startAngle) / 2; var labelRadius = radius * .85; var x = cx + (labelRadius) * Math.cos(midAngle); var y = cy + (labelRadius) * Math.sin(midAngle); wheelCtx.fillStyle = 'gold'; wheelCtx.fillText(myData[i], x, y); wheelCtx.strokeText(myData[i], x, y); // increment angle startAngle += sweepAngle; } } function makeIndicator() { indicator.width = indicator.height = radius + radius / 10; indicatorCtx.font = '40px Georgia'; indicatorCtx.textAlign = 'center'; indicatorCtx.textBaseline = 'middle'; indicatorCtx.fillStyle = 'skyblue'; indicatorCtx.strokeStyle = 'blue'; indicatorCtx.lineWidth = 1; var cx = indicator.width / 2; var cy = indicator.height / 2; indicatorCtx.beginPath(); indicatorCtx.moveTo(cx - radius / 8, cy); indicatorCtx.lineTo(cx, cy - indicator.height / 2); indicatorCtx.lineTo(cx + radius / 8, cy); indicatorCtx.closePath(); indicatorCtx.fillStyle = 'skyblue' indicatorCtx.fill(); indicatorCtx.stroke(); indicatorCtx.beginPath(); indicatorCtx.arc(cx, cy, radius / 3, 0, PI2); indicatorCtx.closePath(); indicatorCtx.fill(); indicatorCtx.stroke(); indicatorCtx.fillStyle = 'blue'; indicatorCtx.fillText('Prizes', cx, cy); } var lastloop = new Date; var thisloop = new Date; var fps = 0; function animate(time) { ctx.clearRect(0, 0, cw, ch); ctx.translate(cw / 2, ch / 2); ctx.rotate(angle); ctx.drawImage(wheel, -wheel.width / 2, -wheel.height / 2); ctx.rotate(-angle); ctx.translate(-cw / 2, -ch / 2); ctx.drawImage(indicator, cw / 2 - indicator.width / 2, ch / 2 - indicator.height / 2) angle += PI2 / 360; thisloop = new Date; fps = 1000 / (thisloop - lastloop); lastloop = thisloop; counter--; if (counter < 1) { counter = 360; } $("#counterBox span").html(counter); var index = counter / 30; $("#countBox span").html(Math.round(index)); //$("#fpsBox span").html(fps); requestAnimationFrame(animate); } function randomColor() { return ('#' + Math.floor(Math.random() * 16777215).toString(16)); } var backup = null; $("#canvas").mouseover(function() { backup = ctx; alert(myData[Math.round(counter / 30)-1]); ctx = null; }); $("#canvas").mouseout(function() { // backup= ctx; ctx = backup; animate(); }); Counter is set to 360 and then each frame decreases it. Take that and divide by 30 (360 / 12), and you can count each wedge. I round up and now I have 0 - 11 count. Update I moved the Index into a global space. To make it more precise, I used the % operator like so: counter--; if (counter == 0) { counter = 360; } $("#counterBox span").html(counter); if (counter % 30 === 0) { index--; } $("#countBox span").html(Math.round(index)); if (index === 0) { index = 12; } When you mouse over, you get the selection: $("#canvas").mouseover(function() { backup = ctx; alert(index); ctx = null; });
I wrapped everything in an IIFE so that there aren't any global variables. Updated Example It's important to note that the angle calculation is: angle = degree * Math.PI / 180; With that being said, you can calculate the current degree and normalize it using: (angle * (180 / Math.PI)) % 360 I added a function called getValue which takes an angle parameter: function getValue(angle) { var degree = (angle * (180 / Math.PI)) % 360, offsetIndex = (Math.floor(degree / sweepDegree) + offset) % myData.length, normalizedIndex = Math.abs(offsetIndex - (myData.length - 1)); return myData[normalizedIndex]; } It essentially calculates the current degree, normalizes it taking into account what the initial degree was when the animation was initialized (which is the offset). Then it divides the degree by the sweep degree, which is 30 in this case since there are 12 items (i.e., 360/12 === 30) and rounds down. var sweepDegree = 360 / myData.length; var offset = (360 - (angle * (180 / Math.PI)) % 360) / sweepDegree; This should work for a varying number of array items. In other words, nothing is hardcoded for a set length of 12 items (like in your case), so it should work for any given number of items. Then you can simply use the getValue function in the mouseover event listener: Updated Example $("#canvas").mouseover(function() { // ... alert(getValue(angle)); }); (function() { var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); var cw = canvas.width; var ch = canvas.height; var PI2 = Math.PI * 2; var myData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; var cx = 150; var cy = 150; var radius = 150; var wheel = document.createElement('canvas'); var wheelCtx = wheel.getContext('2d'); var indicator = document.createElement('canvas'); var indicatorCtx = indicator.getContext('2d'); var angle = PI2 - PI2 / 4; var sweepDegree = 360 / myData.length; var offset = (360 - (angle * (180 / Math.PI)) % 360) / sweepDegree; var myColor = []; for (var i = 0; i < myData.length; i++) { myColor.push(randomColor()); } makeWheel(); makeIndicator(); requestAnimationFrame(animate); function makeWheel() { wheel.width = wheel.height = radius * 2 + 2; wheelCtx.lineWidth = 1; wheelCtx.font = '40px Pacifico, cursive'; wheelCtx.textAlign = 'center'; wheelCtx.textBaseline = 'middle'; var cx = wheel.width / 2; var cy = wheel.height / 2; var sweepAngle = PI2 / myData.length; var startAngle = 0; for (var i = 0; i < myData.length; i++) { // calc ending angle based on starting angle var endAngle = startAngle + sweepAngle; // draw the wedge wheelCtx.beginPath(); wheelCtx.moveTo(cx, cy); wheelCtx.arc(cx, cy, radius, startAngle, endAngle, false); wheelCtx.closePath(); wheelCtx.fillStyle = myColor[i]; wheelCtx.strokeStyle = 'black'; wheelCtx.fill(); wheelCtx.stroke(); // draw the label var midAngle = startAngle + (endAngle - startAngle) / 2; var labelRadius = radius * .85; var x = cx + (labelRadius) * Math.cos(midAngle); var y = cy + (labelRadius) * Math.sin(midAngle); wheelCtx.fillStyle = 'gold'; wheelCtx.fillText(myData[i], x, y); wheelCtx.strokeText(myData[i], x, y); // increment angle startAngle += sweepAngle; } } function makeIndicator() { indicator.width = indicator.height = radius + radius / 10; indicatorCtx.font = '40px Georgia'; indicatorCtx.textAlign = 'center'; indicatorCtx.textBaseline = 'middle'; indicatorCtx.fillStyle = 'skyblue'; indicatorCtx.strokeStyle = 'blue'; indicatorCtx.lineWidth = 1; var cx = indicator.width / 2; var cy = indicator.height / 2; indicatorCtx.beginPath(); indicatorCtx.moveTo(cx - radius / 8, cy); indicatorCtx.lineTo(cx, cy - indicator.height / 2); indicatorCtx.lineTo(cx + radius / 8, cy); indicatorCtx.closePath(); indicatorCtx.fillStyle = 'skyblue' indicatorCtx.fill(); indicatorCtx.stroke(); indicatorCtx.beginPath(); indicatorCtx.arc(cx, cy, radius / 3, 0, PI2); indicatorCtx.closePath(); indicatorCtx.fill(); indicatorCtx.stroke(); indicatorCtx.fillStyle = 'blue'; indicatorCtx.fillText('Prizes', cx, cy); } function animate(time) { if (ctx === null) { return } ctx.clearRect(0, 0, cw, ch); ctx.translate(cw / 2, ch / 2); ctx.rotate(angle); ctx.drawImage(wheel, -wheel.width / 2, -wheel.height / 2); ctx.rotate(-angle); ctx.translate(-cw / 2, -ch / 2); ctx.drawImage(indicator, cw / 2 - indicator.width / 2, ch / 2 - indicator.height / 2) angle += PI2 / 360; requestAnimationFrame(animate); } function randomColor() { return ('#' + Math.floor(Math.random() * 16777215).toString(16)); } var backup = null; $("#canvas").mouseover(function() { backup = ctx; ctx = null; alert(getValue(angle)); }); $("#canvas").mouseout(function() { ctx = backup; animate(); }); function getValue(angle) { var degree = (angle * (180 / Math.PI)) % 360, offsetIndex = (Math.floor(degree / sweepDegree) + offset) % myData.length, normalizedIndex = Math.abs(offsetIndex - (myData.length - 1)); return myData[normalizedIndex]; } })(); <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> <canvas id="canvas" width="600" height="600" style="background-color:#ffff"> </canvas>
How to draw 3d objects on a 2d canvas
here's the full code, I had to remove spaces from some of the functions that weren't related to the problem to make sure im in the 30k character limit of stack overflow const EPSILON = 0.000001; const mat4 = { rotateZ: function(out, a, rad) { let s = Math.sin(rad); let c = Math.cos(rad); let a00 = a[0]; let a01 = a[1]; let a02 = a[2]; let a03 = a[3]; let a10 = a[4]; let a11 = a[5]; let a12 = a[6]; let a13 = a[7]; if (a !== out) { out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[0] = a00 * c + a10 * s; out[1] = a01 * c + a11 * s; out[2] = a02 * c + a12 * s; out[3] = a03 * c + a13 * s; out[4] = a10 * c - a00 * s; out[5] = a11 * c - a01 * s; out[6] = a12 * c - a02 * s; out[7] = a13 * c - a03 * s; return out; }, create: function() { let out = new Float32Array(16); out[0] = 1; out[5] = 1; out[10] = 1; out[15] = 1; return out; }, perspective: function(out, fovy, aspect, near, far) { let f = 1.0 / Math.tan(fovy / 2), nf; out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[11] = -1; out[12] = 0; out[13] = 0; out[15] = 0; if (far !== null && far !== Infinity) { nf = 1 / (near - far); out[10] = (far + near) * nf; out[14] = (2 * far * near) * nf; } else { out[10] = -1; out[14] = -2 * near; } return out; }, translate: function(out, a, v) { let x = v[0], y = v[1], z = v[2]; if (a === out) { out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; return out; } else { let a00, a01, a02, a03; let a10, a11, a12, a13; let a20, a21, a22, a23; a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03; out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13; out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23; out[12] = a00 * x + a10 * y + a20 * z + a[12]; out[13] = a01 * x + a11 * y + a21 * z + a[13]; out[14] = a02 * x + a12 * y + a22 * z + a[14]; out[15] = a03 * x + a13 * y + a23 * z + a[15]; return out; } }, scale: function(out, a, v) { let x = v[0], y = v[1], z = v[2]; out[0] = a[0] * x; out[1] = a[1] * x; out[2] = a[2] * x; out[3] = a[3] * x; out[4] = a[4] * y; out[5] = a[5] * y; out[6] = a[6] * y; out[7] = a[7] * y; out[8] = a[8] * z; out[9] = a[9] * z; out[10] = a[10] * z; out[11] = a[11] * z; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; }, multiply: function(out, a, b) { let a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; let a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; let a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; let a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33; b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30; out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31; out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32; out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33; return out; }, lookAt: function(out, eye, center, up) { let x0, x1, x2, y0, y1, y2, z0, z1, z2, len; let eyex = eye[0]; let eyey = eye[1]; let eyez = eye[2]; let upx = up[0]; let upy = up[1]; let upz = up[2]; let centerx = center[0]; let centery = center[1]; let centerz = center[2]; if (Math.abs(eyex - centerx) < EPSILON && Math.abs(eyey - centery) < EPSILON && Math.abs(eyez - centerz) < EPSILON) { return identity(out); } z0 = eyex - centerx; z1 = eyey - centery; z2 = eyez - centerz; len = 1 / Math.hypot(z0, z1, z2); z0 *= len; z1 *= len; z2 *= len; x0 = upy * z2 - upz * z1; x1 = upz * z0 - upx * z2; x2 = upx * z1 - upy * z0; len = Math.hypot(x0, x1, x2); if (!len) { x0 = 0; x1 = 0; x2 = 0; } else { len = 1 / len; x0 *= len; x1 *= len; x2 *= len; } y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0; len = Math.hypot(y0, y1, y2); if (!len) { y0 = 0; y1 = 0; y2 = 0; } else { len = 1 / len; y0 *= len; y1 *= len; y2 *= len; } out[0] = x0; out[1] = y0; out[2] = z0; out[3] = 0; out[4] = x1; out[5] = y1; out[6] = z1; out[7] = 0; out[8] = x2; out[9] = y2; out[10] = z2; out[11] = 0; out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez); out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez); out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez); out[15] = 1; return out; }, moveToVec3: function(out, v) { out[12] = v[0]; out[13] = v[1]; out[14] = v[2]; } }; const mat3 = { clone: function(a) { let out = new Float32Array(9); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; return out; }, create: function() { let out = new Float32Array(9); out[0] = 1; out[4] = 1; out[8] = 1; return out; } }; const vec3 = { multiply: function(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; return out; }, create: function() { return new Float32Array(3);; }, copy: function(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; return out; } }; const vec2 = { create: function() { return new Float32Array(2);; }, copy: function(out, a) { out[0] = a[0]; out[1] = a[1]; return out; }, fromValues: function(x, y) { let out = new Float32Array(2); out[0] = x; out[1] = y; return out; }, multiply: function(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; return out; }, add: function(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; return out; } }; const FRAGMENT_SHADER = ` precision highp float; varying highp vec2 vTextureCoord; varying lowp vec4 vColor; uniform sampler2D uSampler; uniform bool aUseText; void main(void) { if( aUseText ){ gl_FragColor = texture2D(uSampler, vTextureCoord); } else { gl_FragColor = vColor; } } `; const VERTEX_SHADER = ` attribute vec4 aVertexPosition; attribute vec4 aVertexColor; attribute vec2 aTextureCoord; uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix; uniform mat3 uTextMatrix; uniform float uPointSize; varying lowp vec4 vColor; varying highp vec2 vTextureCoord; void main(void) { gl_PointSize = uPointSize; gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; vColor = aVertexColor; vTextureCoord = (vec3(aTextureCoord, 1)*uTextMatrix).xy; } `; class WebglEntity { constructor() { this.matrix = mat4.create(); this.coords = vec3.create(); } translate(newCoords) { const { matrix, coords } = this; mat4.translate(matrix, matrix, newCoords); vec3.copy(coords, [matrix[12], matrix[13], matrix[14]]); return this; } move(newCoords) { const { matrix, coords } = this; vec3.copy(coords, newCoords); mat4.moveToVec3(matrix, coords); return this; } } class Camera extends WebglEntity { constructor(fieldOfView, aspect, zNear, zFar) { super(); this.projection = mat4.perspective(mat4.create(), fieldOfView, aspect, zNear, zFar); } lookAt(lookAt) { const { matrix, projection, coords } = this; mat4.lookAt(matrix, coords, lookAt, [0, 1, 0]); mat4.multiply(matrix, projection, matrix); return this; } } class Rect extends WebglEntity{ constructor(){ super(); this.positionsBuffer = undefined; this.fragColorPos = undefined; this.strokeColorPos = undefined; this.strokePositionBuffer = undefined; this.vertexAttribInfo = undefined; this.vertextColorAttribInfo = undefined; this.vertexCount = undefined; this.textureInfo = undefined; this.multiTextures = false; this.strokeSize = 1; this.fillers = { fill: false, texture: false, stroke: false }; } setup(matrix, positionsBuffer, strokePositionBuffer, vertexAttribInfo, vertextColorAttribInfo, vertexCount){ this.matrix = matrix; this.positionsBuffer = positionsBuffer; this.strokePositionBuffer = strokePositionBuffer; this.vertexAttribInfo = vertexAttribInfo; this.vertextColorAttribInfo = vertextColorAttribInfo; this.vertexCount = vertexCount; return this; } } class Display{ constructor(gl, programInfo, zAxis, texture){ this.gl = gl; this.programInfo = programInfo; this.canvas = gl.canvas; this.currentCamera = new Camera(45 * Math.PI / 180, gl.canvas.width/gl.canvas.height, 0.1, 100.0); this.currentCamera.translate([0, 0, zAxis]).lookAt([0, 0, 0]); this.zAxis = zAxis; this.drawZAxis = 0; this.last = {}; texture.textAttribInfo = { numComponents: 2, type: gl.FLOAT, normalize: false, stride: 0, offset: 0 }; this.texture = texture; this.spriteSheets = []; const context = texture.context; const canvas = texture.canvas; this.images = {}; } clear(color){ const gl = this.gl; gl.clearColor(0.1, 0.1, 0.1, 1); gl.clearDepth(1.0); gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } rect(x, y, w, h){ const {rect, stroke} = this.createRectPos(w, h); const square = new Rect(); square.setup(...this.getRectInfo(x, y, rect, stroke)); return square; } fillRect(rect, color){ const {createStaticDrawBuffer, gl, parseColor} = this; rect.fillers.fill = true; if(color){ rect.fragColorPos = createStaticDrawBuffer(gl, [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]); } } createRectPos(w, h){ const rect = [ w/2, h/2, -w/2, h/2, w/2, -h/2, -w/2, -h/2 ]; const stroke = [ -w/2, h/2, w/2, h/2, w/2, -h/2, -w/2, -h/2, ]; return {rect, stroke}; } getRectInfo(x, y, rect, stroke){ return this.createSquareBuffer(rect, stroke, [x, y, this.drawZAxis]); } createStaticDrawBuffer(gl, data){ const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW); return buffer; } createSquareBuffer(positions, strokePosition, coords) { const {gl, createStaticDrawBuffer} = this; const positionsBuffer = createStaticDrawBuffer(gl, positions); const strokePositionBuffer = createStaticDrawBuffer(gl, strokePosition); const modelViewMatrix = mat4.create(); mat4.translate(modelViewMatrix, modelViewMatrix, coords); return [modelViewMatrix, positionsBuffer, strokePositionBuffer, this.createAttribInfo(2, gl.FLOAT, false, 0, 0), this.createAttribInfo(4, gl.FLOAT, false, 0, 0), positions.length/2]; } createAttribInfo(numComponents, type, normalize, stride, offset){ return { numComponents, type, normalize, stride, offset}; } enableAttrib(buffer, attrib, gl, {numComponents, type, normalize, stride, offset}){ gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.vertexAttribPointer(attrib, numComponents,type,normalize,stride,offset); gl.enableVertexAttribArray(attrib); } drawBuffer(buffer){ const {gl, drawTexture, enableAttrib, createStaticDrawBuffer, currentCamera, texture: {context, canvas, textAttribInfo}, programInfo: {uniformLocations, program, attribLocations: {vertexPosition, vertexColor, textureCoord}}} = this; const cameraMatrix = currentCamera.matrix; const {positionsBuffer, fragColorPos, strokeColorPos, strokePositionBuffer, matrix, vertexAttribInfo, vertextColorAttribInfo, vertexCount, fragTextPos, fillers: {fill, stroke, texture}, strokeSize, textureInfo, multiTextures} = buffer; gl.uniformMatrix4fv(uniformLocations.projectionMatrix, false, cameraMatrix); gl.uniformMatrix4fv(uniformLocations.modelViewMatrix, false, matrix); if(fill){ enableAttrib(positionsBuffer, vertexPosition, gl, vertexAttribInfo); enableAttrib(fragColorPos, vertexColor, gl, vertextColorAttribInfo); gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount); gl.disableVertexAttribArray(vertexColor); } } static loadShader(gl, program, type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); gl.attachShader(program, shader); } static async create(canvas, width, height, zAxis = 6){ canvas.width = width; canvas.height = height; const gl = canvas.getContext("webgl"); const shaderProgram = gl.createProgram(); Display.loadShader(gl, shaderProgram, gl.VERTEX_SHADER, VERTEX_SHADER); Display.loadShader(gl, shaderProgram, gl.FRAGMENT_SHADER, FRAGMENT_SHADER); gl.linkProgram(shaderProgram); const programInfo = { program: shaderProgram, attribLocations: { vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'), vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'), textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'), }, uniformLocations: { projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'), modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'), textMatrix: gl.getUniformLocation(shaderProgram, 'uTextMatrix'), sampler: gl.getUniformLocation(shaderProgram, 'uSampler'), useText: gl.getUniformLocation(shaderProgram, 'aUseText'), pointSize: gl.getUniformLocation(shaderProgram, 'uPointSize'), }, }; gl.useProgram(programInfo.program); gl.uniform1f(programInfo.uniformLocations.pointSize, 1.0); gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); const textureBuffer = gl.createTexture(); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, textureBuffer); gl.uniform1i(programInfo.uniformLocations.uSampler, 0); const textureCanvas = document.createElement("canvas"); textureCanvas.width = 0; textureCanvas.height = 0; let texture = { canvas: textureCanvas, buffer: textureBuffer, context: textureCanvas.getContext("2d"), }; return new Display(gl, programInfo, zAxis, texture); } } class Engine { constructor(time_step, update, render, allowedSkippedFrames) { this.accumulated_time = 0; this.animation_frame_request = undefined, this.time = undefined, this.time_step = time_step, this.updated = false; this.update = update; this.render = render; this.allowedSkippedFrames = allowedSkippedFrames; this.run = this.run.bind(this); this.end = false; } run(time_stamp) { const { accumulated_time, time, time_step, updated, update, render, allowedSkippedFrames, end } = this; this.accumulated_time += time_stamp - time; this.time = time_stamp; if (accumulated_time > time_stamp * allowedSkippedFrames) { this.accumulated_time = time_stamp; } while (this.accumulated_time >= time_step) { this.accumulated_time -= time_step; update(time_stamp); this.updated = true; } if (updated) { this.updated = false; render(time_stamp); } if (end) { return; } this.animation_frame_request = requestAnimationFrame(this.run); } start() { this.accumulated_time = this.time_step; this.time = performance.now(); this.animation_frame_request = requestAnimationFrame(this.run); } stop() { this.end = true; cancelAnimationFrame(this.animation_frame_request); } } class Entity extends Rect { constructor(){ super(); this.velocity = vec2.create(); this.area = undefined; this.mass = 2; this.updateFillers = {}; this.delete = false; this.draw = true; } setup(w, h, ...args){ this.area = vec2.fromValues(w, h); super.setup(...args); return this; } fill(...args){ this.updateFillers.fill = args; } update(deltaTime, speed){ return this; } move(x, y){ super.move([x, y, this.coords[2]]); return this; } } class Quixotic{ constructor(display){ this.display = display; this.engine = undefined; this.render = undefined; this.update = undefined; this.frameRate = undefined; this.time = 0; this.speed = 1; this.world = { objects: {}, objectsCollisionInfo: {}, objectsArray: [], classesInfo: {} }; this.timePassed = 0; } createEntity(Class, ...args){ const display = this.display; const {rect, stroke} = display.createRectPos(5, 5); Class = Class ? Class : Entity; const className = Class.name; if(className !== "Entity" && !Entity.prototype.isPrototypeOf(Class.prototype)){ throw new TypeError("Expected extended class of Entity. Instead got: " + className); } let instance; const {objectsArray, classesInfo, objects} = this.world; const classInfo = classesInfo[className]; if(classInfo){ if(classInfo.args){ instance = new Class(...[...classInfo.args, ...args]); } else { instance = new Class(...args); } const name = classInfo.name; if(Array.isArray(objects[name])){ objects[name].push(instance); instance.name = name; } else { console.warn("Didn't save object in world.objects object, object wouldn't detect collision"); } } else { instance = new Class(...args); } instance.setup(5, 5, ...display.getRectInfo(0, 0, rect, stroke, "#000")); objectsArray.push(instance); return instance; } createBackground(objects){ const buffer = document.createElement("canvas").getContext("2d"); const bufferRect = this.createEntity(); let {zAxis, canvas: {width, height}} = this.display; zAxis--; const halfZ = zAxis/2; let {coords: [x, y], area: [w, h]} = objects[objects.length - 1]; let [mX, mY, mW, mH] = [x, y, w, h]; for(let i = objects.length-1; i--;){ const {coords: [_x, _y], area: [_w, _h]} = objects[i]; x < _x ? _x : x; y < _y ? _y : y; if(mX < _x){ mX = _x; mW = _w; } if(mY < _y){ mY = _y; mH = _h; } } buffer.canvas.width = width; buffer.canvas.height = height; for(let i = objects.length; i--;){ const {coords: [_x, _y], area: [_w, _h]} = objects[i]; buffer.fillRect(((_x-halfZ-_w*2)/zAxis+1)*width, ((-_y-halfZ-_h*2)/zAxis+1)*height, _w*2/zAxis*width, _h*2/zAxis*height); } document.body.appendChild(buffer.canvas) } buildWorld({objects, classes, tileMap}){ const world = this.world; if(Array.isArray(objects)){ for(let i = objects.length - 1; i > -1; i --){ const object = objects[i]; const {name, array, amount, position, collision, args, area} = object; let createClass; if(!object.class){ createClass = Entity; } const _args = args ? args : []; let pos; if(position){ let p = amount; if(array){ const positions = position.positions; pos = function(){ p--; return positions[p]; }; } else { pos = function(){ return position.position; }; } } if(array){ let _array = []; for(let j = amount; j--;){ const instance = this.createEntity(createClass, ..._args); instance.name = name; if(position){ instance.move(...pos()); } if(area){ instance.setSize(area); } _array.push(instance); } world.objects[name] = _array; world.objectsArray.push(..._array); } } } return; } setup(game){ const {style: {backgroundColor, backgroundImage, stroke}, world, engine: {frameRate, update, render}, setup} = game; this.buildWorld(world); const {display, entitySystem, world: {objectsArray, objects}} = this; if(backgroundImage){ display.gl.canvas.style.background = `url(${backgroundImage})`; if(repeatX || repeatY){ console.log("not read yet"); } } this.frameRate = frameRate; let lastUpdated = 0; this.update = (time) =>{ let deltaTime = time - lastUpdated; lastUpdated = time; const speed = this.speed; this.timePassed += deltaTime*speed; for(let i = objectsArray.length; i--;){ const object = objectsArray[i]; if(object.delete){ objectsArray.splice(i, 1); } object.update(deltaTime/1000, speed); } update(deltaTime/1000, this); }; let lastRendered = 0; this.render = (timeStamp) => { const deltaTime = timeStamp - lastRendered; lastRendered = timeStamp; if(backgroundColor) display.clear(backgroundColor); const length = objectsArray.length; for(let i = objectsArray.length; i--; ){ const object = objectsArray[length - i - 1]; if(object.draw){ const updateFillers = Object.entries(object.updateFillers); const fillersLength = updateFillers.length; if(fillersLength){ for(let i = fillersLength; i--;){ const [func, args] = updateFillers[fillersLength - i - 1]; display[func + "Rect"](object, ...args); } object.updateFillers = {}; } display.drawBuffer(object); } } const speed = this.speed; const spriteSheets = display.spriteSheets; for(let i = spriteSheets.length; i--;){ spriteSheets[i].update(deltaTime/1000*speed); } render(display, this); }; setup(this, display, this.world); this.engine = new Engine(this.frameRate, this.update, this.render, 3); this.engine.start(); return game; } static async create({display: {canvas, width, height, zAxis}, homeURL}){ const display = await Display.create(canvas, width, height, zAxis); return new Quixotic(display); } } const fps = document.querySelector("#fps"); const minLength = innerWidth > innerHeight ? innerHeight : innerWidth; const game = { create: { display: { canvas: document.querySelector("#canvas"), zAxis: 96, width: minLength, height: minLength, }, homeURL: "/src" }, style: { backgroundColor: "#111122" }, world: { objects: [ { name: "trees", array: true, amount: 5, position: { type: "set", positions: [ [-37.5, 37.5], [0,0], [-37.5,-37.5], [37.5,-37.5], [37.5,37.5], [10,10], [15,10], [20,10], [25,10], [30,10]] } } ] }, engine: { frameRate: 1000/30, update: function(deltaTime, engine){ fps.innerText = 1/deltaTime; }, render: function(display){} }, setup: function(engine, display, {objects: {trees}}){ trees.forEach(tree => { tree.fill("#00ff00") }) engine.createBackground(trees); } }; Quixotic.create(game.create) .then(engine => { engine.setup(game); }); * { box-sizing:border-box; margin:0; padding:0; } body { background-color: #111c31; overflow: hidden; align-items:space-around; display:grid; height:100%; width:100%; } #canvas { background-color: #152646; /* justify-self: center; */ } #fps { position: fixed; color: white; right: 0; } canvas { position: fixed } <!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title>webgl x 2dCanvas</title> </head> <body> <canvas id="canvas" width="300" height="300"></canvas> <p id = "fps"></p> </body> </html> Here's code from line 374 where the problem is happening createBackground(objects){ //method const buffer = document.createElement("canvas").getContext("2d"); const bufferRect = this.createEntity(); let {zAxis, canvas: {width, height}} = this.display; zAxis--; //zAxis is where the camera is at, currently 96, but with webgl the objects have to be 1 point lower, so 95. const halfZ = zAxis/2; let {coords: [x, y], area: [w, h]} = objects[objects.length - 1]; let [mX, mY, mW, mH] = [x, y, w, h]; for(let i = objects.length-1; i--;){ const {coords: [_x, _y], area: [_w, _h]} = objects[i]; x < _x ? _x : x; y < _y ? _y : y; if(mX < _x){ mX = _x; mW = _w; } if(mY < _y){ mY = _y; mH = _h; } } buffer.canvas.width = ((mX-halfZ+mW*2)/zAxis+1)*width; buffer.canvas.height = ((mY-halfZ+mH*2)/zAxis+1)*height; for(let i = objects.length; i--;){ const {coords: [_x, _y], area: [_w, _h]} = objects[i]; buffer.fillRect(((_x-halfZ-_w*2)/zAxis+1)*width, ((_y-halfZ-_h*2)/zAxis+1)*height, _w*2/zAxis*width, _h*2/zAxis*height); } document.body.appendChild(buffer.canvas) } I have this function that takes objects that are being drawn with webgl on a 3d world with a couple vectors and matrices, basically I get all their positions and volumes to draw them on a 2d canvas, heres the result I got so far the green squares are the ones being drawn with webgl and the black squares are the ones being draw on a canvas rendering 2d, the end result should be the black squares covering the green squares but my math is off somewhere. The full code can be found here https://github.com/bahaaaldin214/Quixotic-Engine/tree/test The shaders are in src/modules/webgl/shaders other information camera position: 96, green squares positions: [ [-37.5, 37.5], //bottom left [0,0], //center [-37.5,-37.5], //top left [37.5,-37.5], //bottom right [37.5,37.5], //top right ]
Well now that I've seen the code. First off, my bad but I didn't make it clear you should post minimal code. There is lots of unneeded code. Also I'm not sure if that's your own math library or if it's paired down glmatrix. If it's the latter you can just <script src="cdn/to/glmatrix"></script> to use it. In any case you're positioning the squares using a perspective matrix and view matrix (the camera) so you need to use the same math for the 2D canvas. const worldViewProjection = mat4.create(); buffer.canvas.width = width; buffer.canvas.height = height; for (let i = objects.length; i--;) { const { coords: [_x, _y], area: [_w, _h] } = objects[i]; mat4.multiply(worldViewProjection, this.display.currentCamera.matrix, objects[i].matrix); const points = [ [-_w / 2, -_h / 2, 0], [ _w / 2, _h / 2, 0], ].map(p => { const ndc = vec3.transformMat4([], p, worldViewProjection); return [ (ndc[0] * 0.5 + 0.5) * width, (ndc[1] * -0.5 + 0.5) * height, ]; }); const ww = points[1][0] - points[0][0]; const hh = points[1][1] - points[0][1]; buffer.strokeStyle = 'red'; buffer.strokeRect(...points[0], ww, hh); } const EPSILON = 0.000001; const FRAGMENT_SHADER = ` precision highp float; varying highp vec2 vTextureCoord; varying lowp vec4 vColor; uniform sampler2D uSampler; uniform bool aUseText; void main(void) { if( aUseText ){ gl_FragColor = texture2D(uSampler, vTextureCoord); } else { gl_FragColor = vColor; } } `; const VERTEX_SHADER = ` attribute vec4 aVertexPosition; attribute vec4 aVertexColor; attribute vec2 aTextureCoord; uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix; uniform mat3 uTextMatrix; uniform float uPointSize; varying lowp vec4 vColor; varying highp vec2 vTextureCoord; void main(void) { gl_PointSize = uPointSize; gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; vColor = aVertexColor; vTextureCoord = (vec3(aTextureCoord, 1)*uTextMatrix).xy; } `; mat4.moveToVec3 = function(out, v) { out[12] = v[0]; out[13] = v[1]; out[14] = v[2]; }; class WebglEntity { constructor() { this.matrix = mat4.create(); this.coords = vec3.create(); } translate(newCoords) { const { matrix, coords } = this; mat4.translate(matrix, matrix, newCoords); vec3.copy(coords, [matrix[12], matrix[13], matrix[14]]); return this; } move(newCoords) { const { matrix, coords } = this; vec3.copy(coords, newCoords); mat4.moveToVec3(matrix, coords); return this; } } class Camera extends WebglEntity { constructor(fieldOfView, aspect, zNear, zFar) { super(); this.projection = mat4.perspective(mat4.create(), fieldOfView, aspect, zNear, zFar); } lookAt(lookAt) { const { matrix, projection, coords } = this; mat4.lookAt(matrix, coords, lookAt, [0, 1, 0]); mat4.multiply(matrix, projection, matrix); return this; } } class Rect extends WebglEntity { constructor() { super(); this.positionsBuffer = undefined; this.fragColorPos = undefined; this.strokeColorPos = undefined; this.strokePositionBuffer = undefined; this.vertexAttribInfo = undefined; this.vertextColorAttribInfo = undefined; this.vertexCount = undefined; this.textureInfo = undefined; this.multiTextures = false; this.strokeSize = 1; this.fillers = { fill: false, texture: false, stroke: false }; } setup(matrix, positionsBuffer, strokePositionBuffer, vertexAttribInfo, vertextColorAttribInfo, vertexCount) { this.matrix = matrix; this.positionsBuffer = positionsBuffer; this.strokePositionBuffer = strokePositionBuffer; this.vertexAttribInfo = vertexAttribInfo; this.vertextColorAttribInfo = vertextColorAttribInfo; this.vertexCount = vertexCount; return this; } } class Display { constructor(gl, programInfo, zAxis, texture) { this.gl = gl; this.programInfo = programInfo; this.canvas = gl.canvas; this.currentCamera = new Camera(45 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.1, 100.0); this.currentCamera.translate([0, 0, zAxis]).lookAt([0, 0, 0]); this.zAxis = zAxis; this.drawZAxis = 0; this.last = {}; texture.textAttribInfo = { numComponents: 2, type: gl.FLOAT, normalize: false, stride: 0, offset: 0 }; this.texture = texture; this.spriteSheets = []; const context = texture.context; const canvas = texture.canvas; this.images = {}; } clear(color) { const gl = this.gl; gl.clearColor(0.1, 0.1, 0.1, 1); gl.clearDepth(1.0); gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } rect(x, y, w, h) { const { rect, stroke } = this.createRectPos(w, h); const square = new Rect(); square.setup(...this.getRectInfo(x, y, rect, stroke)); return square; } fillRect(rect, color) { const { createStaticDrawBuffer, gl, parseColor } = this; rect.fillers.fill = true; if (color) { rect.fragColorPos = createStaticDrawBuffer(gl, [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]); } } createRectPos(w, h) { const rect = [w / 2, h / 2, -w / 2, h / 2, w / 2, -h / 2, -w / 2, -h / 2]; const stroke = [-w / 2, h / 2, w / 2, h / 2, w / 2, -h / 2, -w / 2, -h / 2, ]; return { rect, stroke }; } getRectInfo(x, y, rect, stroke) { return this.createSquareBuffer(rect, stroke, [x, y, this.drawZAxis]); } createStaticDrawBuffer(gl, data) { const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW); return buffer; } createSquareBuffer(positions, strokePosition, coords) { const { gl, createStaticDrawBuffer } = this; const positionsBuffer = createStaticDrawBuffer(gl, positions); const strokePositionBuffer = createStaticDrawBuffer(gl, strokePosition); const modelViewMatrix = mat4.create(); mat4.translate(modelViewMatrix, modelViewMatrix, coords); return [modelViewMatrix, positionsBuffer, strokePositionBuffer, this.createAttribInfo(2, gl.FLOAT, false, 0, 0), this.createAttribInfo(4, gl.FLOAT, false, 0, 0), positions.length / 2]; } createAttribInfo(numComponents, type, normalize, stride, offset) { return { numComponents, type, normalize, stride, offset }; } enableAttrib(buffer, attrib, gl, { numComponents, type, normalize, stride, offset }) { gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.vertexAttribPointer(attrib, numComponents, type, normalize, stride, offset); gl.enableVertexAttribArray(attrib); } drawBuffer(buffer) { const { gl, drawTexture, enableAttrib, createStaticDrawBuffer, currentCamera, texture: { context, canvas, textAttribInfo }, programInfo: { uniformLocations, program, attribLocations: { vertexPosition, vertexColor, textureCoord } } } = this; const cameraMatrix = currentCamera.matrix; const { positionsBuffer, fragColorPos, strokeColorPos, strokePositionBuffer, matrix, vertexAttribInfo, vertextColorAttribInfo, vertexCount, fragTextPos, fillers: { fill, stroke, texture }, strokeSize, textureInfo, multiTextures } = buffer; gl.uniformMatrix4fv(uniformLocations.projectionMatrix, false, cameraMatrix); gl.uniformMatrix4fv(uniformLocations.modelViewMatrix, false, matrix); if (fill) { enableAttrib(positionsBuffer, vertexPosition, gl, vertexAttribInfo); enableAttrib(fragColorPos, vertexColor, gl, vertextColorAttribInfo); gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount); gl.disableVertexAttribArray(vertexColor); } } static loadShader(gl, program, type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); gl.attachShader(program, shader); } static async create(canvas, width, height, zAxis = 6) { canvas.width = width; canvas.height = height; const gl = canvas.getContext("webgl"); const shaderProgram = gl.createProgram(); Display.loadShader(gl, shaderProgram, gl.VERTEX_SHADER, VERTEX_SHADER); Display.loadShader(gl, shaderProgram, gl.FRAGMENT_SHADER, FRAGMENT_SHADER); gl.linkProgram(shaderProgram); const programInfo = { program: shaderProgram, attribLocations: { vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'), vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'), textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'), }, uniformLocations: { projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'), modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'), textMatrix: gl.getUniformLocation(shaderProgram, 'uTextMatrix'), sampler: gl.getUniformLocation(shaderProgram, 'uSampler'), useText: gl.getUniformLocation(shaderProgram, 'aUseText'), pointSize: gl.getUniformLocation(shaderProgram, 'uPointSize'), }, }; gl.useProgram(programInfo.program); gl.uniform1f(programInfo.uniformLocations.pointSize, 1.0); gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); const textureBuffer = gl.createTexture(); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, textureBuffer); gl.uniform1i(programInfo.uniformLocations.uSampler, 0); const textureCanvas = document.createElement("canvas"); textureCanvas.width = 0; textureCanvas.height = 0; let texture = { canvas: textureCanvas, buffer: textureBuffer, context: textureCanvas.getContext("2d"), }; return new Display(gl, programInfo, zAxis, texture); } } class Engine { constructor(time_step, update, render, allowedSkippedFrames) { this.accumulated_time = 0; this.animation_frame_request = undefined, this.time = undefined, this.time_step = time_step, this.updated = false; this.update = update; this.render = render; this.allowedSkippedFrames = allowedSkippedFrames; this.run = this.run.bind(this); this.end = false; } run(time_stamp) { const { accumulated_time, time, time_step, updated, update, render, allowedSkippedFrames, end } = this; this.accumulated_time += time_stamp - time; this.time = time_stamp; if (accumulated_time > time_stamp * allowedSkippedFrames) { this.accumulated_time = time_stamp; } while (this.accumulated_time >= time_step) { this.accumulated_time -= time_step; update(time_stamp); this.updated = true; } if (updated) { this.updated = false; render(time_stamp); } if (end) { return; } this.animation_frame_request = requestAnimationFrame(this.run); } start() { this.accumulated_time = this.time_step; this.time = performance.now(); this.animation_frame_request = requestAnimationFrame(this.run); } stop() { this.end = true; cancelAnimationFrame(this.animation_frame_request); } } class Entity extends Rect { constructor() { super(); this.velocity = vec2.create(); this.area = undefined; this.mass = 2; this.updateFillers = {}; this.delete = false; this.draw = true; } setup(w, h, ...args) { this.area = vec2.fromValues(w, h); super.setup(...args); return this; } fill(...args) { this.updateFillers.fill = args; } update(deltaTime, speed) { return this; } move(x, y) { super.move([x, y, this.coords[2]]); return this; } } class Quixotic { constructor(display) { this.display = display; this.engine = undefined; this.render = undefined; this.update = undefined; this.frameRate = undefined; this.time = 0; this.speed = 1; this.world = { objects: {}, objectsCollisionInfo: {}, objectsArray: [], classesInfo: {} }; this.timePassed = 0; } createEntity(Class, ...args) { const display = this.display; const { rect, stroke } = display.createRectPos(5, 5); Class = Class ? Class : Entity; const className = Class.name; if (className !== "Entity" && !Entity.prototype.isPrototypeOf(Class.prototype)) { throw new TypeError("Expected extended class of Entity. Instead got: " + className); } let instance; const { objectsArray, classesInfo, objects } = this.world; const classInfo = classesInfo[className]; if (classInfo) { if (classInfo.args) { instance = new Class(...[...classInfo.args, ...args]); } else { instance = new Class(...args); } const name = classInfo.name; if (Array.isArray(objects[name])) { objects[name].push(instance); instance.name = name; } else { console.warn("Didn't save object in world.objects object, object wouldn't detect collision"); } } else { instance = new Class(...args); } instance.setup(5, 5, ...display.getRectInfo(0, 0, rect, stroke, "#000")); objectsArray.push(instance); return instance; } createBackground(objects) { const buffer = document.createElement("canvas").getContext("2d"); const bufferRect = this.createEntity(); let { zAxis, canvas: { width, height } } = this.display; zAxis--; const halfZ = zAxis / 2; let { coords: [x, y], area: [w, h] } = objects[objects.length - 1]; const worldViewProjection = mat4.create(); buffer.canvas.width = width; buffer.canvas.height = height; for (let i = objects.length; i--;) { const { coords: [_x, _y], area: [_w, _h] } = objects[i]; mat4.multiply(worldViewProjection, this.display.currentCamera.matrix, objects[i].matrix); const points = [ [-_w / 2, -_h / 2, 0], [_w / 2, _h / 2, 0], ].map(p => { const ndc = vec3.transformMat4([], p, worldViewProjection); return [ (ndc[0] * 0.5 + 0.5) * width, (ndc[1] * -0.5 + 0.5) * height, ]; }); const ww = points[1][0] - points[0][0]; const hh = points[1][1] - points[0][1]; buffer.strokeStyle = 'red'; buffer.strokeRect(...points[0], ww, hh); } document.body.appendChild(buffer.canvas) } buildWorld({ objects, classes, tileMap }) { const world = this.world; if (Array.isArray(objects)) { for (let i = objects.length - 1; i > -1; i--) { const object = objects[i]; const { name, array, amount, position, collision, args, area } = object; let createClass; if (!object.class) { createClass = Entity; } const _args = args ? args : []; let pos; if (position) { let p = amount; if (array) { const positions = position.positions; pos = function() { p--; return positions[p]; }; } else { pos = function() { return position.position; }; } } if (array) { let _array = []; for (let j = amount; j--;) { const instance = this.createEntity(createClass, ..._args); instance.name = name; if (position) { instance.move(...pos()); } if (area) { instance.setSize(area); } _array.push(instance); } world.objects[name] = _array; world.objectsArray.push(..._array); } } } return; } setup(game) { const { style: { backgroundColor, backgroundImage, stroke }, world, engine: { frameRate, update, render }, setup } = game; this.buildWorld(world); const { display, entitySystem, world: { objectsArray, objects } } = this; if (backgroundImage) { display.gl.canvas.style.background = `url(${backgroundImage})`; if (repeatX || repeatY) { console.log("not read yet"); } } this.frameRate = frameRate; let lastUpdated = 0; this.update = (time) => { let deltaTime = time - lastUpdated; lastUpdated = time; const speed = this.speed; this.timePassed += deltaTime * speed; for (let i = objectsArray.length; i--;) { const object = objectsArray[i]; if (object.delete) { objectsArray.splice(i, 1); } object.update(deltaTime / 1000, speed); } update(deltaTime / 1000, this); }; let lastRendered = 0; this.render = (timeStamp) => { const deltaTime = timeStamp - lastRendered; lastRendered = timeStamp; if (backgroundColor) display.clear(backgroundColor); const length = objectsArray.length; for (let i = objectsArray.length; i--;) { const object = objectsArray[length - i - 1]; if (object.draw) { const updateFillers = Object.entries(object.updateFillers); const fillersLength = updateFillers.length; if (fillersLength) { for (let i = fillersLength; i--;) { const [func, args] = updateFillers[fillersLength - i - 1]; display[func + "Rect"](object, ...args); } object.updateFillers = {}; } display.drawBuffer(object); } } const speed = this.speed; const spriteSheets = display.spriteSheets; for (let i = spriteSheets.length; i--;) { spriteSheets[i].update(deltaTime / 1000 * speed); } render(display, this); }; setup(this, display, this.world); this.engine = new Engine(this.frameRate, this.update, this.render, 3); this.engine.start(); return game; } static async create({ display: { canvas, width, height, zAxis }, homeURL }) { const display = await Display.create(canvas, width, height, zAxis); return new Quixotic(display); } } const fps = document.querySelector("#fps"); const minLength = innerWidth > innerHeight ? innerHeight : innerWidth; const game = { create: { display: { canvas: document.querySelector("#canvas"), zAxis: 96, width: minLength, height: minLength, }, homeURL: "/src" }, style: { backgroundColor: "#111122" }, world: { objects: [{ name: "trees", array: true, amount: 5, position: { type: "set", positions: [ [-37.5, 37.5], [0, 0], [-37.5, -37.5], [37.5, -37.5], [37.5, 37.5], [10, 10], [15, 10], [20, 10], [25, 10], [30, 10] ] } }] }, engine: { frameRate: 1000 / 30, update: function(deltaTime, engine) { fps.innerText = 1 / deltaTime; }, render: function(display) {} }, setup: function(engine, display, { objects: { trees } }) { trees.forEach(tree => { tree.fill("#00ff00") }) engine.createBackground(trees); } }; Quixotic.create(game.create) .then(engine => { engine.setup(game); }); * { box-sizing: border-box; margin: 0; padding: 0; } body { background-color: #111c31; overflow: hidden; align-items: space-around; display: grid; height: 100%; width: 100%; } #canvas { background-color: #152646; /* justify-self: center; */ } #fps { position: fixed; color: white; right: 0; } canvas { position: fixed } <script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script> <canvas id="canvas" width="300" height="300"></canvas> <p id="fps"></p> note: the code only works because the camera is not rotated, nor are the squares. If you did rotate the camera or the squares you'd need to draw triangles with canvas 2d after transforming each set of 3 vertices, just like WebGL does.