Drawing parametric shapes in webGL (without three.js) - javascript

I've written a program that draws some parametric shapes (spheres, toruses, and cylinders) just using HTML5 and regular Javascript. I'm trying to convert this code so that it uses WebGL, implementing the shapes with triangle strips as is done in this tutorial. My point of confusion is how triangle strips are even being used to create spheres at all. The way I was doing it before just featured the calculation of where to draw each horizontal slice or circle based on the latitude lines in a for loop and nested inside of that loop was the calculation of each point on that circle. After all of those points were generated, I passed them to a function which adds all of the vertices to an array which is returned and passed to a curve plotting function that used moveTo() and lineTo() in order to draw lines between each point. The problem is that I don't know what the equivalent of moveTo() and lineTo() is in webGL when using triangle shapes. How can I translate my implementation to WebGL?
Here is some of the code from my original implementation:
//Calculates point on sphere
function spherePoint(uv) {
var u = uv[0];
var v = uv[1];
var phi = -Math.PI/2 + Math.PI * v;
var theta = 2 * Math.PI * u;
return [ Math.cos(phi) * Math.cos(theta),
Math.cos(phi) * Math.sin(theta),
Math.sin(phi)];
}
// Takes the parametric function as an argument and constructs 3D shape
function makeShape(num_u, num_v, eq, possRad) {
var shell = [];
for (var j = 0 ; j <= num_v ; j++) {
var v = j / num_v;
shell.push([]);
for (var i = 0 ; i <= num_u ; i++) {
var u = i / num_u;
var p = eq([u, v], possRad);
shell[j].push(p);
}
}
return shell;
}
// Used to create shapes to render parametric surface
function renderShape(shape) {
var num_j = shape.length;
var num_i = shape[0].length;
for (var j = 0 ; j < num_j - 1 ; j++)
for (var i = 0 ; i < num_i - 1 ; i++) {
plotCurve([shape[j][i],
shape[j + 1][i],
shape[j + 1][i + 1],
shape[j][i + 1]]);
}
}
//plot curve on canvas
function plotCurve(C) {
g.beginPath();
for (var i = 0 ; i < C.length ; i++)
if (i == 0)
moveTo(C[i]);
else
lineTo(C[i]);
g.stroke();
}
function moveTo(p) {
var q = m.transform(p); // APPLY 3D MATRIX TRANFORMATION
var xy = viewport(q); // APPLY VIEWPORT TRANSFORM
g.moveTo(xy[0], xy[1]);
}
function lineTo(p) {
var q = m.transform(p); // APPLY 3D MATRIX TRANFORMATION
var xy = viewport(q); // APPLY VIEWPORT TRANSFORM
g.lineTo(xy[0], xy[1]);
}
The webGL version should look something like this I would think:
The plain Javascript version looks like this:

That's a pretty basic WebGL question. Some more tutorials on webgl might be helpful.
WebGL only draws chunks of data. It doesn't really have a lineTo or a moveTo. Instead you give it buffers of data, tell it how to pull data out of those buffers, then you write a function (a vertex shader) to use that data to tell WebGL how convert it to clip space coordinates and whether to draw points, lines, or triangles with the result. You also supply a function (a fragment shader) to tell it what colors to use for the points, lines or triangles.
Basically to draw the thing you want to draw you need to generate 2 triangles for every rectangle on that sphere. In other words you need to generate 6 vertices for every rectangle. The reason is in order to draw each triangle in a different color you can't share any vertices since the colors are associated with the vertices.
So for one rectangle you need to generate these points
0--1 4
| / /|
|/ / |
2 3--5
Where 0, 1, and 2 are pink points and 3, 4, 5 are green points. 1 and 4 have the same position but because their colors are different they have to be different points. The same with points 2 and 3.
var pink = [1, 0.5, 0.5, 1];
var green = [0.5, 1, 0.5, 1];
var positions = [];
var colors = [];
var across = 20;
var down = 10;
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
colors.push(color[0], color[1], color[2], color[3]);
}
for (var y = 0; y < down; ++y) {
for (var x = 0; x < across; ++x) {
// for each rect we need 6 points
addPoint(x , y , pink);
addPoint(x + 1, y , pink);
addPoint(x , y + 1, pink);
addPoint(x , y + 1, green);
addPoint(x + 1, y , green);
addPoint(x + 1, y + 1, green);
}
}
Here's the sphere above rendered but without any lighting, perspective or anything.
var pink = [1, 0.5, 0.5, 1];
var green = [0.5, 1, 0.5, 1];
var positions = [];
var colors = [];
var across = 20;
var down = 10;
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
colors.push(color[0], color[1], color[2], color[3]);
}
for (var y = 0; y < down; ++y) {
for (var x = 0; x < across; ++x) {
// for each rect we need 6 points
addPoint(x , y , pink);
addPoint(x + 1, y , pink);
addPoint(x , y + 1, pink);
addPoint(x , y + 1, green);
addPoint(x + 1, y , green);
addPoint(x + 1, y + 1, green);
}
}
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: positions,
color: colors,
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
var uniforms = {
resolution: [gl.canvas.width, gl.canvas.height],
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, bufferInfo);
canvas { border: 1px solid black; }
<canvas id="c"></canvas>
<script id="vs" type="not-js">
attribute vec4 position;
attribute vec4 color;
uniform vec2 resolution;
varying vec4 v_color;
void main() {
gl_Position = position * vec4(resolution.y / resolution.x, 1, 1, 1);
v_color = color;
}
</script>
<script id="fs" type="not-js">
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
</script>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
If you later want to light it you'll also need normals (values that you can use to tell which direction something is facing). We can add those in by adding
var normals = [];
and inside addPoint
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
colors.push(color[0], color[1], color[2], color[3]);
normals.push(nx, ny, nz);
}
Here's a sample with hacked lighting
var pink = [1, 0.5, 0.5, 1];
var green = [0.5, 1, 0.5, 1];
var positions = [];
var colors = [];
var normals = [];
var across = 20;
var down = 10;
function addPoint(x, y, color) {
var u = x / across;
var v = y / down;
var radius = Math.sin(v * Math.PI);
var angle = u * Math.PI * 2;
var nx = Math.cos(angle);
var ny = Math.cos(v * Math.PI);
var nz = Math.sin(angle);
positions.push(
nx * radius, // x
ny, // y
nz * radius); // z
normals.push(nx, ny, nz);
colors.push(color[0], color[1], color[2], color[3]);
}
for (var y = 0; y < down; ++y) {
for (var x = 0; x < across; ++x) {
// for each rect we need 6 points
addPoint(x , y , pink);
addPoint(x + 1, y , pink);
addPoint(x , y + 1, pink);
addPoint(x , y + 1, green);
addPoint(x + 1, y , green);
addPoint(x + 1, y + 1, green);
}
}
var gl = document.getElementById("c").getContext("webgl");
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: positions,
normal: normals,
color: colors,
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
var uniforms = {
resolution: [gl.canvas.width, gl.canvas.height],
lightDirection: [0.5, 0.5, -1],
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, bufferInfo);
canvas { border: 1px solid black; }
<canvas id="c"></canvas>
<script id="vs" type="not-js">
attribute vec4 position;
attribute vec4 color;
attribute vec3 normal;
uniform vec2 resolution;
varying vec4 v_color;
varying vec3 v_normal;
void main() {
gl_Position = position * vec4(resolution.y / resolution.x, 1, 1, 1);
v_color = color;
v_normal = normal;
}
</script>
<script id="fs" type="not-js">
precision mediump float;
varying vec4 v_color;
varying vec3 v_normal;
uniform vec3 lightDirection;
void main() {
float light = pow(abs(dot(v_normal, normalize(lightDirection))), 2.0);
gl_FragColor = vec4(v_color.xyz * light, v_color.a);
}
</script>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
PS: The picture you posted is actually drawing more triangles per rectangle. The division between green and pink is not straight.

There is gl.LINES that well, pretty much draw connected lines. So lineTo(x,y,z) would just be add one more vertex to the VBO you use to store the lines data. moveTo(x,y,z) would be just to create a "break" between the lines. This can be accomplished with a new drawArrays call.

Related

How to get sphere intersection points with P5.js?

I am using p5.js to create a sphere
What I am interested in is getting the points coordinates of the shapes used to modelize the sphere.
Is it possible to do so?
Basically, I would like to get the series of points used to draw the triangles that modelize the sphere.
You can use the spherical coordinates (two angles and radius) to cartesian coordinates (x,y,z) conversion formula to compute points on a sphere:
(Image source: wikipedia)
If you think of the two angles as latitude(lat), longitude(lon) angles on our globe and a constant radius, in the JS you can look at this formula as:
var x = radius * cos(lat) * sin(lon);
var y = radius * sin(lat) * sin(lon);
var z = radius * cos(lon);
Here's a basic sketch to illustrate the idea:
var radius = 120;
var latDetail = 0.243;
var lonDetail = 0.15;
function setup() {
createCanvas(300, 300, WEBGL);
strokeWeight(9);
}
function draw() {
background(255);
orbitControl();
beginShape(POINTS);
// iterate through lat, lon angles (in radians)
for(var lat = 0; lat <= PI; lat += latDetail){
for(var lon = 0; lon <= TWO_PI; lon += lonDetail){
// for each sperical coordinate (lat, lon angles, radius)
// convert to cartesian (x, y, z)
var x = radius * cos(lat) * sin(lon);
var y = radius * sin(lat) * sin(lon);
var z = radius * cos(lon);
// render each point
vertex(x, y, z);
}
}
endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>
Have a play with the latDetail, lonDetail variables which define how dense/sparse the sphere's parallels/meridians will be.
Having a quick look online for UV Spheres, Daniel Sieger's Generating Spheres article is neat!
Even though the code is c++, the syntax is similar enough to understand:
// generate vertices per stack / slice
for (int i = 0; i < n_stacks - 1; i++)
{
auto phi = M_PI * double(i + 1) / double(n_stacks);
for (int j = 0; j < n_slices; j++)
{
auto theta = 2.0 * M_PI * double(j) / double(n_slices);
auto x = std::sin(phi) * std::cos(theta);
auto y = std::cos(phi);
auto z = std::sin(phi) * std::sin(theta);
mesh.add_vertex(Point(x, y, z));
}
}
Pretty much the same formula (withouth the radius scalar) and a counter to for the number of segments on each angle (instead of an angle increment).
Here's a p5.js port:
var radius = 120;
var uSegments = 12;
var vSegments = 12;
// sliders
var uSegmentsSlider;
var vSegmentsSlider;
function setup() {
createCanvas(300, 300, WEBGL);
strokeWeight(9);
uSegmentsSlider = createSlider(3, 36, 12, 1);
vSegmentsSlider = createSlider(3, 36, 12, 1);
uSegmentsSlider.position(10, 10);
vSegmentsSlider.position(10, 30);
createP('U').position(145, -3);
createP('V').position(145, 17);
}
function draw() {
// read slider values
uSegments = uSegmentsSlider.value();
vSegments = vSegmentsSlider.value();
background(255);
orbitControl();
beginShape(POINTS);
// iterate through u, v segments
for(var u = 0; u < uSegments; u++){
var phi = PI * (u + 1) / uSegments;
for(var v = 0; v < vSegments; v++){
var theta = TWO_PI * v / vSegments;
// for each sperical coordinate (lat, lon angles, radius)
// convert to cartesian (x, y, z)
var x = radius * cos(theta) * sin(phi);
var y = radius * sin(theta) * sin(phi);
var z = radius * cos(phi);
// render each point
vertex(x, y, z);
}
}
endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>
Update Turns out p5.Vector.fromAngles() does this for you.
e.g.
// iterate through u, v segments
for(var u = 0; u < uSegments; u++){
var phi = PI * (u + 1) / uSegments;
for(var v = 0; v < vSegments; v++){
var theta = TWO_PI * v / vSegments;
// for each sperical coordinate (lat, lon angles, radius)
// convert to cartesian (x, y, z)
let p = p5.Vector.fromAngles(phi, theta, radius);
// render each point
vertex(p.x, p.y, p.z);
}
}
The above isn't great because it's allocating a new p5.Vector on each call (not recommended in draw()), but hopefully the idea is illustrated and you can pre-calculate the points in setup() then simply render in draw()

How to draw parallel edges (arrows) between vertices with canvas?

I'm working on a flow-network visualization with Javascript.
Vertices are represented as circles and edges are represented as arrows.
Here is my Edge class:
function Edge(u, v) {
this.u = u; // start vertex
this.v = v; // end vertex
this.draw = function() {
var x1 = u.x;
var y1 = u.y;
var x2 = v.x;
var y2 = v.y;
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
var dx = x1 - x2;
var dy = y1 - y2;
var length = Math.sqrt(dx * dx + dy * dy);
x1 = x1 - Math.round(dx / ((length / (radius))));
y1 = y1 - Math.round(dy / ((length / (radius))));
x2 = x2 + Math.round(dx / ((length / (radius))));
y2 = y2 + Math.round(dy / ((length / (radius))));
// calculate the angle of the edge
var deg = (Math.atan(dy / dx)) * 180.0 / Math.PI;
if (dx < 0) {
deg += 180.0;
}
if (deg < 0) {
deg += 360.0;
}
// calculate the angle for the two triangle points
var deg1 = ((deg + 25 + 90) % 360) * Math.PI * 2 / 360.0;
var deg2 = ((deg + 335 + 90) % 360) * Math.PI * 2 / 360.0;
// calculate the triangle points
var arrowx = [];
var arrowy = [];
arrowx[0] = x2;
arrowy[0] = y2;
arrowx[1] = Math.round(x2 + 12 * Math.sin(deg1));
arrowy[1] = Math.round(y2 - 12 * Math.cos(deg1));
arrowx[2] = Math.round(x2 + 12 * Math.sin(deg2));
arrowy[2] = Math.round(y2 - 12 * Math.cos(deg2));
context.beginPath();
context.moveTo(arrowx[0], arrowy[0]);
context.lineTo(arrowx[1], arrowy[1]);
context.lineTo(arrowx[2], arrowy[2]);
context.closePath();
context.stroke();
context.fillStyle = "black";
context.fill();
};
}
Given the code
var canvas = document.getElementById('canvas'); // canvas element
var context = canvas.getContext("2d");
context.lineWidth = 1;
context.strokeStyle = "black";
var radius = 20; // vertex radius
var u = {
x: 50,
y: 80
};
var v = {
x: 150,
y: 200
};
var e = new Edge(u, v);
e.draw();
The draw() function will draw an edge between two vertices like this:
If we add the code
var k = new Edge(v, u);
k.draw();
We will get:
but I want to draw edges both directions as following:
(sorry for my bad paint skills)
Of course the vertices and the edge directions are not fixed.
A working example (with drawing vertex fucntion) on JSFiddle:
https://jsfiddle.net/Romansko/0fu01oec/18/
Aligning axis to a line.
It can make everything a little easier if you rotate the rendering to align with the line. Once you do that it is then easy to draw above or below the line as that is just in the y direction and along the line is the x direction.
Thus if you have a line
const line = {
p1 : { x : ? , y : ? },
p2 : { x : ? , y : ? },
};
Convert it to a vector and normalise that vector
// as vector from p1 to p2
var nx = line.p2.x - line.p1.x;
var ny = line.p2.y - line.p1.y;
// then get length
const len = Math.sqrt(nx * nx + ny * ny);
// use the length to normalise the vector
nx /= len;
ny /= len;
The normalised vector represents the new x axis we want to render along, and the y axis is at 90 deg to that. We can use setTransform to set both axis and the origin (0,0) point at the start of the line.
ctx.setTransform(
nx, ny, // the x axis
-ny, nx, // the y axis at 90 deg to the x axis
line.p1.x, line.p1.y // the origin (0,0)
)
Now rendering the line and arrow heads is easy as they are axis aligned
ctx.beginPath();
ctx.lineTo(0,0); // start of line
ctx.lineTo(len,0); // end of line
ctx.stroke();
// add the arrow head
ctx.beginPath();
ctx.lineTo(len,0); // tip of arrow
ctx.lineTo(len - 10, 10);
ctx.lineTo(len - 10, -10);
ctx.fill();
To render two lines offset from the center
var offset = 10;
ctx.beginPath();
ctx.lineTo(0,offset); // start of line
ctx.lineTo(len,offset); // end of line
ctx.moveTo(0,-offset); // start of second line
ctx.lineTo(len,-offset); // end of second line
ctx.stroke();
// add the arrow head
ctx.beginPath();
ctx.lineTo(len,offset); // tip of arrow
ctx.lineTo(len - 10, offset+10);
ctx.lineTo(len - 10, offset-10);
ctx.fill();
offset = -10;
// add second arrow head
ctx.beginPath();
ctx.lineTo(0,offset); // tip of arrow
ctx.lineTo(10, offset+10);
ctx.lineTo(10, offset-10);
ctx.fill();
And you can reset the transform with
ctx.setTransform(1,0,0,1,0,0); // restore default transform

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

How to find the control point of a quadratic curve using a parabola?

I can't figure out how to draw a parabola which is having a equation as y^2 = 4ax
So I have both end points i.e. P0, P2, however I can't figure out how to find control point to put in quadraticCurveTo() function.
To match a quadratic Bezier to this parabola formula and assuming origin is 0, you can use place the control point at -y0 or -y1 from one of the end points.
Example
First, lets rearrange the formula:
y2 = 4ax
to:
x = y2 / 4a
so we can plot from bottom down.
In this case we can simply boil down everything and use the inverse of y and mid x as control point.
The general principle though, is to find the tangents of the endpoints. Then where the lines from those intersect the control-point should be placed. If you want the mathematical steps on how to find the intersection I would recommend taking a look at Erik Man's answer here (which happened to be posted today but breaks down the math in much more details).
So, if we plot it within the window of a canvas (black is parabola, red is quadratic curve):
var ctx = document.querySelector("canvas").getContext("2d"),
w = ctx.canvas.width, h = ctx.canvas.height;
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.translate(0, 6);
// formula
function f(y, a) {return y * y / (a * 4)};
var a = 80;
plotWindow();
function plotWindow() {
ctx.clearRect(0, -6, w, h);
ctx.fillStyle = "#000";
// plot parabola using formula
for(var i = 0; i < w; i++) {
var y = f(i - w * 0.5, a);
ctx.fillRect(i - 2, y - 2, 4, 4);
}
// plot parabola using quadratic curve:
var x0 = 0;
var y0 = f(-w * 0.5, a);
var x1 = w;
var y1 = f( w * 0.5, a);
var cx = x1 * 0.5; // control point is center for x
var cy = -y0; // control point is -y0 for y assuming top of parabola = 0
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.quadraticCurveTo(cx, cy, x1, y1);
ctx.stroke();
// plot a
ctx.fillStyle = "blue";
ctx.fillRect(cx - 3, a - 3, 6, 6);
ctx.fillText("a=" + a, cx + 6, a + 5)
}
// slider
document.querySelector("input").onchange = function() {
a = +this.value;
plotWindow();
};
canvas {border:1px solid #777}
<script src="https://cdn.rawgit.com/epistemex/slider-feedback/master/sliderfeedback.min.js"></script>
<label>a: <input type="range" min=10 max=172 value=80></label><br>
<canvas width=600 height=190></canvas>

Rotating canvas about axis problems

I am using canvas 3d to draw a 3d graph in which i can plot points such as (1,5,4), (-8,6,-2) etc.So i am able to draw in all positive and negative x,y and z axis.I also have rotation effect by using arrow keys.
Instructions for rotation:
The z-axis extends out from the center of the screen.
To rotate about the x-axis, press the up/down arrow keys.
To rotate about the y-axis, press the left/right arrow keys.
To rotate about the z-axis, press the ctrl+left/ctrl+down arrow keys.
I can plot the point by specifying points in the text field i provided.
Now the problem is that for example if i plot(5,5,2) it will plot properly.But if i rotate x axis first and then y axis then point will be plotted properly. The problem comes if i rotate y-axis first and then x-axis.the point will be wrongly plotted.
Easy method to find the problem i encountered:
This can be easily find out if you go on plotting the same point repeatedly.The point should be plotted above the same point so that only single point is visible.But in my case the same point( for ex(5,5,2) is drawn at different place in canvas while rotating.This problem only comes if i rotate y-axis first and then x-axis or if i rotate z axis first and then y-axis. So what is the mistake i have done in coding.I am new to this canvas 3d and java script.So please help.
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<title>Canvas Surface Rotation</title>
<style>
body {
text-align: center;
}
canvas {
border: 1px solid black;
}
</style>
<script>
var p1;
var p2;
var p3;
var p4;
var p5;
var p6;
var xangle=0;
var yangle=0;
var zangle=0;
var constants = {
canvasWidth: 600, // In pixels.
canvasHeight: 600, // In pixels.
leftArrow: 37,
upArrow: 38,
rightArrow: 39,
downArrow: 40,
xMin: -10, // These four max/min values define a square on the xy-plane that the surface will be plotted over.
xMax: 10,
yMin: -10,
yMax: 10,
xDelta: 0.06, // Make smaller for more surface points.
yDelta: 0.06, // Make smaller for more surface points.
colorMap: ["#000080"], // There are eleven possible "vertical" color values for the surface, based on the last row of http://www.cs.siena.edu/~lederman/truck/AdvanceDesignTrucks/html_color_chart.gif
pointWidth: 2, // The size of a rendered surface point (i.e., rectangle width and height) in pixels.
dTheta: 0.05, // The angle delta, in radians, by which to rotate the surface per key press.
surfaceScale: 24 // An empirically derived constant that makes the surface a good size for the given canvas size.
};
// These are constants too but I've removed them from the above constants literal to ease typing and improve clarity.
var X = 0;
var Y = 1;
var Z = 2;
// -----------------------------------------------------------------------------------------------------
var controlKeyPressed = false; // Shared between processKeyDown() and processKeyUp().
var surface = new Surface(); // A set of points (in vector format) representing the surface.
// -----------------------------------------------------------------------------------------------------
function point(x, y, z)
/*
Given a (x, y, z) surface point, returns the 3 x 1 vector form of the point.
*/
{
return [x, y, z]; // Return a 3 x 1 vector representing a traditional (x, y, z) surface point. This vector form eases matrix multiplication.
}
// -----------------------------------------------------------------------------------------------------
function Surface()
/*
A surface is a list of (x, y, z) points, in 3 x 1 vector format. This is a constructor function.
*/
{
this.points = [];
// An array of surface points in vector format. That is, each element of this array is a 3 x 1 array, as in [ [x1, y1, z1], [x2, y2, z2], [x3, y3, z3], ... ]
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.equation = function(x, y)
/*
Given the point (x, y), returns the associated z-coordinate based on the provided surface equation, of the form z = f(x, y).
*/
{
var d = Math.sqrt(x*x + y*y); // The distance d of the xy-point from the z-axis.
return 4*(Math.sin(d) / d); // Return the z-coordinate for the point (x, y, z).
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.generate = function()
/*
Creates a list of (x, y, z) points (in 3 x 1 vector format) representing the surface.
*/
{
var i = 0;
for (var x = constants.xMin; x <= constants.xMax; x += constants.xDelta)
{
for (var y = constants.yMin; y <= constants.yMax; y += constants.yDelta)
{
this.points[i] = point(x, y, this.equation(x, y)); // Store a surface point (in vector format) into the list of surface points.
++i;
}
}
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.color = function()
/*
The color of a surface point is a function of its z-coordinate height.
*/
{
var z; // The z-coordinate for a given surface point (x, y, z).
this.zMin = this.zMax = this.points[0][Z]; // A starting value. Note that zMin and zMax are custom properties that could possibly be useful if this code is extended later.
for (var i = 0; i < this.points.length; i++)
{
z = this.points[i][Z];
if (z < this.zMin) { this.zMin = z; }
if (z > this.zMax) { this.zMax = z; }
}
var zDelta = Math.abs(this.zMax - this.zMin) / constants.colorMap.length;
for (var i = 0; i < this.points.length; i++)
{
this.points[i].color = constants.colorMap[ Math.floor( (this.points[i][Z]-this.zMin)/zDelta ) ];
}
/* Note that the prior FOR loop is functionally equivalent to the follow (much less elegant) loop:
for (var i = 0; i < this.points.length; i++)
{
if (this.points[i][Z] <= this.zMin + zDelta) {this.points[i].color = "#060";}
else if (this.points[i][Z] <= this.zMin + 2*zDelta) {this.points[i].color = "#090";}
else if (this.points[i][Z] <= this.zMin + 3*zDelta) {this.points[i].color = "#0C0";}
else if (this.points[i][Z] <= this.zMin + 4*zDelta) {this.points[i].color = "#0F0";}
else if (this.points[i][Z] <= this.zMin + 5*zDelta) {this.points[i].color = "#9F0";}
else if (this.points[i][Z] <= this.zMin + 6*zDelta) {this.points[i].color = "#9C0";}
else if (this.points[i][Z] <= this.zMin + 7*zDelta) {this.points[i].color = "#990";}
else if (this.points[i][Z] <= this.zMin + 8*zDelta) {this.points[i].color = "#960";}
else if (this.points[i][Z] <= this.zMin + 9*zDelta) {this.points[i].color = "#930";}
else if (this.points[i][Z] <= this.zMin + 10*zDelta) {this.points[i].color = "#900";}
else {this.points[i].color = "#C00";}
}
*/
}
// -----------------------------------------------------------------------------------------------------
function update(){
document.querySelector("#xa").innerHTML = xangle;
document.querySelector("#ya").innerHTML = yangle;
document.querySelector("#za").innerHTML = zangle;
}
function appendCanvasElement()
/*
Creates and then appends the "myCanvas" canvas element to the DOM.
*/
{
var canvasElement = document.createElement('canvas');
canvasElement.width = constants.canvasWidth;
canvasElement.height = constants.canvasHeight;
canvasElement.id = "myCanvas";
canvasElement.getContext('2d').translate(constants.canvasWidth/2, constants.canvasHeight/2); // Translate the surface's origin to the center of the canvas.
document.body.appendChild(canvasElement); // Make the canvas element a child of the body element.
}
//------------------------------------------------------------------------------------------------------
Surface.prototype.sortByZIndex = function(A, B)
{
return A[Z] - B[Z]; // Determines if point A is behind, in front of, or at the same level as point B (with respect to the z-axis).
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.draw = function()
{
var myCanvas = document.getElementById("myCanvas"); // Required for Firefox.
var ctx = myCanvas.getContext("2d");
var res;
var xm;
// this.points = surface.points.sort(surface.sortByZIndex); // Sort the set of points based on relative z-axis position. If the points are visibly small, you can sort of get away with removing this step.
for (var i = 0; i < this.points.length; i++)
{
ctx.fillStyle = this.points[i].color;
ctx.fillRect(this.points[i][X] * constants.surfaceScale, this.points[i][Y] * constants.surfaceScale, constants.pointWidth, constants.pointWidth);
}
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.font="12px Arial";
ctx.fillStyle = "#000000";
ctx.fillText("X",this.points[p1][X] * constants.surfaceScale, this.points[p1][Y] * constants.surfaceScale);
var c=document.getElementById("myCanvas");
var ctx1=c.getContext("2d");
ctx1.font="12px Arial";
ctx1.fillText("Y",this.points[p2][X] * constants.surfaceScale, this.points[p2][Y] * constants.surfaceScale);
var c=document.getElementById("myCanvas");
var ctx1=c.getContext("2d");
ctx1.font="12px Arial";
ctx1.fillText("Z",this.points[p3][X] * constants.surfaceScale, this.points[p3][Y] * constants.surfaceScale);
var c=document.getElementById("myCanvas");
var ctx1=c.getContext("2d");
ctx1.font="12px Arial";
ctx1.fillText("-Y",this.points[p4][X] * constants.surfaceScale, this.points[p4][Y] * constants.surfaceScale);
var c=document.getElementById("myCanvas");
var ctx1=c.getContext("2d");
ctx1.font="12px Arial";
ctx1.fillText("-Z",this.points[p5][X] * constants.surfaceScale, this.points[p5][Y] * constants.surfaceScale);
var c=document.getElementById("myCanvas");
var ctx1=c.getContext("2d");
ctx1.font="12px Arial";
ctx1.fillText("-X",this.points[p6][X] * constants.surfaceScale, this.points[p6][Y] * constants.surfaceScale);
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.multi = function(R)
/*
Assumes that R is a 3 x 3 matrix and that this.points (i.e., P) is a 3 x n matrix. This method performs P = R * P.
*/
{
var Px = 0, Py = 0, Pz = 0; // Variables to hold temporary results.
var P = this.points; // P is a pointer to the set of surface points (i.e., the set of 3 x 1 vectors).
var sum; // The sum for each row/column matrix product.
for (var V = 0; V < P.length; V++) // For all 3 x 1 vectors in the point list.
{
Px = P[V][X], Py = P[V][Y], Pz = P[V][Z];
for (var Rrow = 0; Rrow < 3; Rrow++) // For each row in the R matrix.
{
sum = (R[Rrow][X] * Px) + (R[Rrow][Y] * Py) + (R[Rrow][Z] * Pz);
P[V][Rrow] = sum;
}
}
}
Surface.prototype.multipt = function(R)
/*
Assumes that R is a 3 x 3 matrix and that this.points (i.e., P) is a 3 x n matrix. This method performs P = R * P.
*/
{
var Px = 0, Py = 0, Pz = 0; // Variables to hold temporary results.
var P = this.points; // P is a pointer to the set of surface points (i.e., the set of 3 x 1 vectors).
var sum; // The sum for each row/column matrix product.
for (var V = P.length-1; V < P.length; V++) // For all 3 x 1 vectors in the point list.
{
Px = P[V][X], Py = P[V][Y], Pz = P[V][Z];
for (var Rrow = 0; Rrow < 3; Rrow++) // For each row in the R matrix.
{
sum = (R[Rrow][X] * Px) + (R[Rrow][Y] * Py) + (R[Rrow][Z] * Pz);
P[V][Rrow] = sum;
}
}
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.erase = function()
{
var myCanvas = document.getElementById("myCanvas"); // Required for Firefox.
var ctx = myCanvas.getContext("2d");
ctx.clearRect(-constants.canvasWidth/2, -constants.canvasHeight/2, myCanvas.width, myCanvas.height);
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.xRotate = function(sign)
/*
Assumes "sign" is either 1 or -1, which is used to rotate the surface "clockwise" or "counterclockwise".
*/
{
var Rx = [ [0, 0, 0],
[0, 0, 0],
[0, 0, 0] ]; // Create an initialized 3 x 3 rotation matrix.
Rx[0][0] = 1;
Rx[0][1] = 0; // Redundant but helps with clarity.
Rx[0][2] = 0;
Rx[1][0] = 0;
Rx[1][1] = Math.cos( sign*constants.dTheta );
Rx[1][2] = -Math.sin( sign*constants.dTheta );
Rx[2][0] = 0;
Rx[2][1] = Math.sin( sign*constants.dTheta );
Rx[2][2] = Math.cos( sign*constants.dTheta );
this.multi(Rx); // If P is the set of surface points, then this method performs the matrix multiplcation: Rx * P
this.erase(); // Note that one could use two canvases to speed things up, which also eliminates the need to erase.
this.draw();
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.yRotate = function(sign)
/*
Assumes "sign" is either 1 or -1, which is used to rotate the surface "clockwise" or "counterclockwise".
*/
{
var Ry = [ [0, 0, 0],
[0, 0, 0],
[0, 0, 0] ]; // Create an initialized 3 x 3 rotation matrix.
Ry[0][0] = Math.cos( sign*constants.dTheta );
Ry[0][1] = 0; // Redundant but helps with clarity.
Ry[0][2] = Math.sin( sign*constants.dTheta );
Ry[1][0] = 0;
Ry[1][1] = 1;
Ry[1][2] = 0;
Ry[2][0] = -Math.sin( sign*constants.dTheta );
Ry[2][1] = 0;
Ry[2][2] = Math.cos( sign*constants.dTheta );
this.multi(Ry); // If P is the set of surface points, then this method performs the matrix multiplcation: Rx * P
this.erase(); // Note that one could use two canvases to speed things up, which also eliminates the need to erase.
this.draw();
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.zRotate = function(sign)
/*
Assumes "sign" is either 1 or -1, which is used to rotate the surface "clockwise" or "counterclockwise".
*/
{
var Rz = [ [0, 0, 0],
[0, 0, 0],
[0, 0, 0] ]; // Create an initialized 3 x 3 rotation matrix.
Rz[0][0] = Math.cos( sign*constants.dTheta );
Rz[0][1] = -Math.sin( sign*constants.dTheta );
Rz[0][2] = 0; // Redundant but helps with clarity.
Rz[1][0] = Math.sin( sign*constants.dTheta );
Rz[1][1] = Math.cos( sign*constants.dTheta );
Rz[1][2] = 0;
Rz[2][0] = 0
Rz[2][1] = 0;
Rz[2][2] = 1;
this.multi(Rz); // If P is the set of surface points, then this method performs the matrix multiplcation: Rx * P
this.erase(); // Note that one could use two canvases to speed things up, which also eliminates the need to erase.
this.draw();
}
Surface.prototype.xRotatept = function()
{
var Rx = [ [0, 0, 0],
[0, 0, 0],
[0, 0, 0] ];
Rx[0][0] = 1;
Rx[0][1] = 0;
Rx[0][2] = 0;
Rx[1][0] = 0;
Rx[1][1] = Math.cos(xangle);
Rx[1][2] = -Math.sin(xangle);
Rx[2][0] = 0;
Rx[2][1] = Math.sin(xangle);
Rx[2][2] = Math.cos(xangle);
this.multipt(Rx);
this.erase();
this.draw();
}
Surface.prototype.yRotatept = function()
{
var Ry = [ [0, 0, 0],
[0, 0, 0],
[0, 0, 0] ];
Ry[0][0] = Math.cos(yangle);
Ry[0][1] = 0;
Ry[0][2] = Math.sin(yangle);
Ry[1][0] = 0;
Ry[1][1] = 1;
Ry[1][2] = 0;
Ry[2][0] = -Math.sin(yangle);
Ry[2][1] = 0;
Ry[2][2] = Math.cos(yangle);
this.multipt(Ry);
this.erase();
this.draw();
}
Surface.prototype.zRotatept = function()
{
var Rz = [ [0, 0, 0],
[0, 0, 0],
[0, 0, 0] ];
Rz[0][0] = Math.cos(zangle);
Rz[0][1] = -Math.sin(zangle);
Rz[0][2] = 0;
Rz[1][0] = Math.sin(zangle);
Rz[1][1] = Math.cos(zangle);
Rz[1][2] = 0;
Rz[2][0] = 0
Rz[2][1] = 0;
Rz[2][2] = 1;
this.multipt(Rz);
this.erase();
this.draw();
}
// -----------------------------------------------------------------------------------------------------
function processKeyDown(evt)
{
if (evt.ctrlKey)
{
switch (evt.keyCode)
{
case constants.upArrow:
// No operation other than preventing the default behavior of the arrow key.
evt.preventDefault(); // This prevents the default behavior of the arrow keys, which is to scroll the browser window when scroll bars are present. The user can still scroll the window with the mouse.
break;
case constants.downArrow:
// No operation other than preventing the default behavior of the arrow key.
evt.preventDefault();
break;
case constants.leftArrow:
// console.log("ctrl+leftArrow");
zangle=zangle-0.05;
update();
if(zangle<=-2*Math.PI)
{
zangle=0;
}
surface.zRotate(-1); // The sign determines if the surface rotates "clockwise" or "counterclockwise".
evt.preventDefault();
break;
case constants.rightArrow:
// console.log("ctrl+rightArrow");
zangle=zangle+0.05;
update();
if(zangle>=2*Math.PI)
{
zangle=0;
}
surface.zRotate(1);
evt.preventDefault();
break;
}
return; // When the control key is pressed, only the left and right arrows have meaning, no need to process any other key strokes (i.e., bail now).
}
// Assert: The control key is not pressed.
switch (evt.keyCode)
{
case constants.upArrow:
// console.log("upArrow");
xangle=xangle+0.05;
update();
if(xangle>=2*Math.PI)
{
xangle=0;
}
surface.xRotate(1);
evt.preventDefault();
break;
case constants.downArrow:
// console.log("downArrow");
xangle=xangle-0.05;
update();
if(xangle<=-2*Math.PI)
{
xangle=0;
}
surface.xRotate(-1);
evt.preventDefault();
break;
case constants.leftArrow:
// console.log("leftArrow");
yangle=yangle-0.05;
update();
if(yangle<=-2*Math.PI)
{
yangle=0;
}
surface.yRotate(-1);
evt.preventDefault();
break;
case constants.rightArrow:
// console.log("rightArrow");
yangle=yangle+0.05;
update();
if(yangle>=2*Math.PI)
{
yangle=0;
}
surface.yRotate(1);
evt.preventDefault();
break;
}
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.plot = function(x, y, z)
/*
add the point (x, y, z) (in 3 x 1 vector format) to the surface.
*/
{
this.points.push(point(x, y, z)); // Store a surface point
var x=0;
for (var x = constants.xMin; x <= constants.xMax; x += constants.xDelta)
{
this.points.push(point(x, 0, 0));
}
p6=1;
p1=this.points.length-1;
p4=this.points.length;
/*var y=-0.2
for (var x = constants.xMax+1; x <= constants.xMax+2; x += constants.xDelta)
{
this.points.push(point(x, y, 0));
y=y+0.002
}*/
/*for (var x = constants.xMax+1; x <= constants.xMax+2; x += constants.xDelta)
{
this.points.push(point(11, 0, 0))
}*/
for (var x = constants.xMin; x <= constants.xMax; x += constants.yDelta)
{
this.points.push(point(0, x, 0));
}
p2=this.points.length-1;
p5=this.points.length;
for (var x = constants.xMin; x <= constants.xMax; x += constants.yDelta)
{
this.points.push(point(0,0,x));
}
p3=this.points.length-1;
}
Surface.prototype.plot1 = function(x, y, z)
/*
add the point (x, y, z) (in 3 x 1 vector format) to the surface.
*/
{
this.points.push(point(x, y, z)); // Store a surface point
surface.xRotatept();
surface.yRotatept();
surface.zRotatept();
this.draw();
}
function onloadInit()
{
appendCanvasElement(); // Create and append the canvas element to the DOM.
surface.draw(); // Draw the surface on the canvas.
document.addEventListener('keydown', processKeyDown, false); // Used to detect if the control key has been pressed.
}
// -----------------------------------------------------------------------------------------------------
//surface.generate(); // Creates the set of points reprsenting the surface. Must be called before color().
surface.plot(0,0,0);
surface.color(); // Based on the min and max z-coordinate values, chooses colors for each point based on the point's z-ccordinate value (i.e., height).
window.addEventListener('load', onloadInit, false); // Perform processing that must occur after the page has fully loaded.
</script>
</head>
<body>
<table align="center">
<tr><td>
<h5 style="color:#606">Enter the value of (X,Y,Z)</h5>
<input type="text" value="5" class="num-input" width="50" size="2" id="x-input">
<input type="text" value="5" class="num-input" width="50" size="2" id="y-input">
<input type="text" value="2" class="num-input" width="50" size="2" id="z-input">
<input type="button" value="Plot Point" onClick="surface.plot1(document.getElementById('x-input').value,document.getElementById('y-input').value,document.getElementById('z-input').value); ">
</td></tr></table>
<table align="center"> <tr><td>
<span id="xa">0</span>deg<br>
<span id="ya">0</span>deg<br>
<span id="za">0</span>deg</td></tr></table>
</body>
</html>
The final output of rotations along multiple axis can vary depending on the order that you rotate the axis'. What you need to do is keep track of the total rotation along each axis (as three numbers, not using matrices). And each time you update a rotation value, apply all three total rotations to an identity matrix in the correct order (try x,y,z). Always use the same order. Then use this to transform your coordinates.
here is my opinion:
JAVASCRIPT
var canvas = document.getElementById("myCanvas");
var ctx2 = canvas.getContext("2d");
ctx2.fillStyle='#333';
ctx2.fillRect(50,50,100,100);
var ctx = canvas.getContext("2d");
ctx.fillStyle='red';
var deg = Math.PI/180;
ctx.save();
ctx.translate(100, 100);
ctx.rotate(45 * deg);
ctx.fillRect(-50,-50,100,100);
ctx.restore();
ctx2 is the old position and ctx is the new position of the shape. You have to translate the shape with the same x,y coordinates according to where you want position your shape. Then you have to enter values to ctx.fillRect(x,y,w,h);keep x and y as the -ve values (half of height and width to keep it on the diagonal to the canvas otherwise change to manipulate it). and h, w as your desired values.
DEMO

Categories