Make a rotate function in javascript - javascript

I am looking at this website to learn how to make a custom rotating function (simply for fun). I am running my code at khan academy because it is easy for me to practice natural simulations with code. So far I have
// I would like to try and make 3d objects
//used this as a reference http://petercollingridge.appspot.com/3D-tutorial/rotating-objects
//make nodes
var node = function(x,y){
this.x = x;
this.y = y;
};
node.prototype.draw = function(){
fill(0, 0, 0);
ellipse(this.x,this.y,5,5);
};
//make and edge
var edge = function(n_1,n_2){//this n_1, and n_2 are arbitrary names for input params
this.n_1 = n_1;
this.n_2 = n_2;
};
//draw the edge
edge.prototype.draw = function(){
fill(0, 0, 0);
line(this.n_1.x,this.n_1.y, this.n_2.x, this.n_2.y);
};
//a center would be much eaiser... I will make squares with centers and diameters instead!
var square = function(x,y,d){
this.x = x;
this.y = y;
this.d = d;
//the radius
var r = this.d/2;
//make the nodes
var n1 = new node(this.x -r ,this.y +r);
var n2 = new node(this.x -r ,this.y -r);
var n3 = new node(this.x +r ,this.y -r);
var n4 = new node(this.x +r ,this.y +r);
var nArray = [n1,n2,n3,n4];
this.nArray = nArray;
//make the edges
var e1 = new edge(n1,n2);
var e2 = new edge(n2,n3);
var e3 = new edge(n3,n4);
var e4 = new edge(n4,n1);
var eArray = [e1,e2,e3,e4];
this.eArray = eArray;
};
//make new squares
var s1 = new square(125,15,20);
var s2 = new square(185,15,20);
square.prototype.draw = function() {
//draw everything
for(var i = 0; i < this.nArray.length; i++){
this.nArray[i].draw();
}
for(var j = 0; j < this.eArray.length; j++){
this.eArray[j].draw();
}
};
square.prototype.rotate2D = function(theta){
//how much we want it to change is theta
var sin_t = sin(theta);
var cos_t = cos(theta);
//we need the original x and y, since this.x and this.y will be changed
var x = this.nArray[0].x;
var y = this.nArray[0].y;
//remember trig? x' = x * cos(beta) - y * sin(beta)
// y' = y * cos(beta) - x * sin(beta)
this.nArray[0].x = x * cos_t - y * sin_t;
this.nArray[0].y = (y * cos_t) + (x * sin_t);
text(x,200,200);
};
s2.rotate2D(-3);
s2.draw();
//draw shapes
draw = function() {
//fill(255, 255, 255);
//rect(0, 0, width, height);
s1.draw();
//s2.rotate2D(3);
//s2.draw();
};
The issue is clearly in my
square.prototype.rotate2D
function. The shape should rotate around the x and y value of the node but for some reason it seems to be rotating around 0,0. Not Sure why this is, I have spent several hours trying to figure this out. Any help is appreciated. Also I feel like my general program structure is bad and I have some unnecessary code, so let me know if there are any optimizations or a better structure I can use as well.

Finally figured it out. Somewhat close to what enhzflep said.
for (var n = 0; n < this.nArray.length; n++) {
var node = this.nArray[n];
var x = this.nArray[n].x - this.x;
var y = this.nArray[n].y - this.y;
node.x = x * cos_t - y * sin_t + this.x;
node.y = y * cos_t + x * sin_t + this.y;
}

Related

Three.js global heightmap is not showing the expected result

I wanted to create a model of earth using a global 4k height map that I found online. I found this open source script that can do this.
function createGeometryFromMap() {
var depth = 512;
var width = 512;
var spacingX = 3;
var spacingZ = 3;
var heightOffset = 2;
var canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 512;
var ctx = canvas.getContext('2d');
var img = new Image();
img.src = "assets/earth.jpg";
img.onload = function () {
// draw on canvas
ctx.drawImage(img, 0, 0);
var pixel = ctx.getImageData(0, 0, width, depth);
var geom = new THREE.Geometry;
var output = [];
for (var x = 0; x < depth; x++) {
for (var z = 0; z < width; z++) {
// get pixel
// since we're grayscale, we only need one element
var yValue = pixel.data[z * 4 + (depth * x * 4)] / heightOffset;
var vertex = new THREE.Vector3(x * spacingX, yValue, z * spacingZ);
geom.vertices.push(vertex);
}
}
// we create a rectangle between four vertices, and we do
// that as two triangles.
for (var z = 0; z < depth - 1; z++) {
for (var x = 0; x < width - 1; x++) {
// we need to point to the position in the array
// a - - b
// | x |
// c - - d
var a = x + z * width;
var b = (x + 1) + (z * width);
var c = x + ((z + 1) * width);
var d = (x + 1) + ((z + 1) * width);
var face1 = new THREE.Face3(a, b, d);
var face2 = new THREE.Face3(d, c, a);
face1.color = new THREE.Color(scale(getHighPoint(geom, face1)).hex());
face2.color = new THREE.Color(scale(getHighPoint(geom, face2)).hex())
geom.faces.push(face1);
geom.faces.push(face2);
}
}
geom.computeVertexNormals(true);
geom.computeFaceNormals();
geom.computeBoundingBox();
var zMax = geom.boundingBox.max.z;
var xMax = geom.boundingBox.max.x;
var mesh = new THREE.Mesh(geom, new THREE.MeshLambertMaterial({
vertexColors: THREE.FaceColors,
color: 0x666666,
shading: THREE.NoShading
}));
mesh.translateX(-xMax / 2);
mesh.translateZ(-zMax / 2);
scene.add(mesh);
mesh.name = 'valley';
};
}
function getHighPoint(geometry, face) {
var v1 = geometry.vertices[face.a].y;
var v2 = geometry.vertices[face.b].y;
var v3 = geometry.vertices[face.c].y;
return Math.max(v1, v2, v3);
}
When I tried the demo heightmaps of Grand Canyon and Hawaii that came with the download, they seemed to be fine. However, when I tried to implement my global heightmap into this, the result was not displaying what I needed.
This is the terrain of Grand Canyon:
This is the global heightmap that I am using:
And this is the result I am getting for the 3D terrain of the world:
It's obvious that something is wrong, because that is not the world.
When you tell your 2D canvas context to .drawImage(), it's going to draw a 4000 pixels image over a 512 pixels canvas. That's how it's defined in the MDN documents if you only use three img, dx, dy arguments.
You could either:
Draw the Earth image smaller to fit inside your 512x512 pixels canvas by using the 4th and 5th arguments of dWidth, dHeight.
Make your canvas larger to match the width and height dimensions of your Earth image.

EaselJS how to update text

I'm trying to update the circles and text above each circle when mouseoveer event happens, however I only successfully update the circles, but not sure about how to update the text.
for (var i = 0; i < 100; i++) {
var circle = new createjs.Shape();
var r = 7;
var x = window.innerWidth * Math.random();
var y = window.innerHeight * Math.random();
var color = colors[Math.floor(i % colors.length)];
var alpha = 0.2 + Math.random() * 0.5;
circle.color = color;
circle.alpha = alpha;
circle.radius = r;
circle.graphics.beginFill(color).drawCircle(0, 0, r);
circle.x = x;
circle.y = y;
var txt = new createjs.Text(i.toString(), "11px Arial", "#FFF");
txt.x = x - 4;
txt.y = y - 18;
var con = new createjs.Container();
con.addChild(circle, txt);
con.addEventListener("mouseover", function(event) {
event.target.graphics.clear().beginFill(event.target.color).drawCircle(0, 0, 15).endFill();
event.target.text = "dsfasd";
stage.update(event);
});
stage.addChild(con);
}
In your listener, target refers to the container itself. But event.target.text is not a reference to the text object added as a child inside it. In order to access the second child, you need to do event.target.children[1].

How to chage the color of shapes with easeljs

function initCircles() {
circles = [];
for (var i = 0; i < 100; i++) {
var circle = new createjs.Shape();
var r = 7;
var x = window.innerWidth * Math.random();
var y = window.innerHeight * Math.random();
var color = colors[Math.floor(i % colors.length)];
var alpha = 0.2 + Math.random() * 0.5;
circle.alpha = alpha;
circle.radius = r;
circle.graphics.beginFill(color).drawCircle(0, 0, r);
circle.x = x;
circle.y = y;
circles.push(circle);
circle.movement = 'float';
circle.addEventListener("mouseover", function(event) {
circle.graphics.clear().beginFill("red").drawRect(0, 0, 50, 60).endFill();
stage.update(event);
});
stage.addChild(circle);
}
}
I'm trying to add a mouseover listener on the little circles I create on the page, I hope that once I place the cursor on the circle, it becomes a rectangle. However, the rectangle always appear where some other circle exists rather than the circle I point to.
Save your beginFill command, and change it later:
// your other code above
var fillCmd = circle.graphics.beginFill(color).command; // Note the .command at the end
circle.graphics.drawCircle(0, 0, r);
// your other code below
// Later
fillCmd.style = "ff0000";
Here is an article about it, and here are the docs -
Admittedly, this could be documented better. :)
The problem is that you are referencing a mutable variable inside of the closure. There are a couple of ways to solve that.
1) Either somehow reference the circle from the event variable inside of the nested function (if the event has support for that), or
2) Bind the value of the variable inside another function, e.g:
function initCircles() {
circles = [];
for (var i = 0; i < 100; i++) {
var circle = new createjs.Shape();
var r = 7;
var x = window.innerWidth * Math.random();
var y = window.innerHeight * Math.random();
var color = colors[Math.floor(i % colors.length)];
var alpha = 0.2 + Math.random() * 0.5;
circle.alpha = alpha;
circle.radius = r;
circle.graphics.beginFill(color).drawCircle(0, 0, r);
circle.x = x;
circle.y = y;
circles.push(circle);
circle.movement = 'float';
addEventListener(circle);
stage.addChild(circle);
}
function addEventListener(circle) {
circle.addEventListener("mouseover", function(event) {
circle.graphics.clear().beginFill("red").drawRect(0, 0, 50, 60).endFill();
stage.update(event);
});
}
}

How to speed up nested for loops in IE?

I want to filter an image with a predefined filter mask in JavaScript using the HTML5 Canvas Element.
I found a solution which works fine:
//define source canvas
var srccanv = document.getElementById("src_canvas");
var ctx = srccanv.getContext("2d");
var w = srccanv.width;
var h = srccanv.height;
//just draw something into the canvas
ctx.beginPath();
ctx.fillStyle="gray";
ctx.fillRect(0,0,w,h);
ctx.lineWidth = 15;
ctx.strokeStyle = "lightgray";
ctx.moveTo(0,0);
ctx.lineTo(300,150);
ctx.stroke();
//define destination canvas
var dstcanv = document.getElementById("dst_canvas");
var dctx = dstcanv.getContext("2d");
var dstImageData = dctx.getImageData(0,0,dstcanv.width,dstcanv.height);
var dst = dstImageData.data;
//filtermask
var filtermask = [-1,-1,-1,0,0,0,1,1,1];
var side = Math.round(Math.sqrt(filtermask.length));
var halfSide = Math.floor(side/2);
var srcImageData = ctx.getImageData(0,0,w,h);
var src = srcImageData.data;
var sw = w;
var sh = h;
console.time('convolution');
// go through the destination image pixels
for (var y=1; y<h-1; y++) {
for (var x=1; x<w-1; x++) {
var sy = y;
var sx = x;
var dstOff = (y*w+x)*4;
// calculate the weighed sum of the source image pixels that
// fall under the convolution matrix
var r=0, g=0, b=0, a=0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = sy + cy - halfSide;
var scx = sx + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
var srcOff = (scy*sw+scx)*4;
var wt = filtermask[cy*side+cx];
r += src[srcOff] * wt;
g += src[srcOff+1] * wt;
b += src[srcOff+2] * wt;
a += src[srcOff+3];
}
}
}
dst[dstOff] = r;
dst[dstOff+1] = g;
dst[dstOff+2] = b;
dst[dstOff+3] = 255;
}
}
console.timeEnd('convolution');
dctx.putImageData(dstImageData,0,0);
<canvas id="src_canvas"></canvas>
<canvas id="dst_canvas"></canvas>
or see this fiddle: https://jsfiddle.net/w0fuxt64/20/
But... I found out that the performance in IE11 is very bad. I tested my code with latest versions of firefox (38.5.2 ESR) and Chrome (47.0) and I got some results around 10-20ms for the filtering of an 300px by 150px canvas. (See time in developer console of your browser)
Testing with IE gave me results around 280ms! which is way too long to be useful for my purposes. Does anybody have any ideas how to dramatically improve this code for IE.
Thanks in advance
Beni

Algorithm for drawing a 5 point star

I'm currently working on a solution for drawing a standard 5-point star on the canvas using JavaScript. I'm part way there but can't figure it out entirely. I'd appreciate any tips or pointers anyone might have.
I made some changes to the code that Chris posted so it would work for me:
var alpha = (2 * Math.PI) / 10;
var radius = 12;
var starXY = [100,100]
canvasCtx.beginPath();
for(var i = 11; i != 0; i--)
{
var r = radius*(i % 2 + 1)/2;
var omega = alpha * i;
canvasCtx.lineTo((r * Math.sin(omega)) + starXY[0], (r * Math.cos(omega)) + starXY[1]);
}
canvasCtx.closePath();
canvasCtx.fillStyle = "#000";
canvasCtx.fill();
Hope it helps...
n point star, points are distributed evenly around a circle. Assume the first point is at 0,r (top), with the circle centred on 0,0, and that we can construct it from a series of triangles rotated by 2π/(2n+1):
Define a rotation function:
function rotate2D(vecArr, byRads) {
var mat = [ [Math.cos(byRads), -Math.sin(byRads)],
[Math.sin(byRads), Math.cos(byRads)] ];
var result = [];
for(var i=0; i < vecArr.length; ++i) {
result[i] = [ mat[0][0]*vecArr[i][0] + mat[0][1]*vecArr[i][1],
mat[1][0]*vecArr[i][0] + mat[1][1]*vecArr[i][1] ];
}
return result;
}
Construct a star by rotating n triangles:
function generateStarTriangles(numPoints, r) {
var triangleBase = r * Math.tan(Math.PI/numPoints);
var triangle = [ [0,r], [triangleBase/2,0], [-triangleBase/2,0], [0,r] ];
var result = [];
for(var i = 0; i < numPoints; ++i) {
result[i] = rotate2D(triangle, i*(2*Math.PI/numPoints));
}
return result;
}
Define a function to draw any given array of polygons:
function drawObj(ctx, obj, offset, flipVert) {
var sign=flipVert ? -1 : 1;
for(var objIdx=0; objIdx < obj.length; ++objIdx) {
var elem = obj[objIdx];
ctx.moveTo(elem[0][0] + offset[0], sign*elem[0][1] + offset[1]);
ctx.beginPath();
for(var vert=1; vert < elem.length; ++vert) {
ctx.lineTo(elem[vert][0] + offset[0], sign*elem[vert][1] + offset[1]);
}
ctx.fill();
}
}
Use the above to draw a 5 point star:
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
var offset = [canvas.width/2, canvas.height/2];
ctx.fillStyle="#000000";
var penta = generateStarTriangles(5, 200);
drawObj(ctx, penta, offset, true);
See it here http://jsbin.com/oyonos/2/
This is a problem where Turtle Geometry makes things simple:
5-point star:
repeat 5 times:
fwd 100,
right 144,
fwd 100,
left 72,
You need to draw the inner bits and a complete circle is 2 * PI radians. In the example below r is the radius of the encompassing circle. Code below is from an open source project (http://github.com/CIPowell/PhyloCanvas)
var alpha = (2 * Math.PI) / 10;
// works out the angle between each vertex (5 external + 5 internal = 10)
var r_point = r * 1.75; // r_point is the radius to the external point
for(var i = 11; i != 0; i--) // or i could = 10 and you could use closePath at the end
{
var ra = i % 2 == 1 ? rb: r;
var omega = alpha * i; //omega is the angle of the current point
//cx and cy are the center point of the star.
node.canvas.lineTo(cx + (ra * Math.sin(omega)), cy + (ra * Math.cos(omega)));
}
//Store or fill.
NB: This is one of those many ways to skin a cat things, I'm sure someone else has another way of doing it. Also, the reason for the decremental loop rather than the incremental is preformance. i != 0 is more efficient than i < 10 and i-- is more efficient than i++. But performance matters a lot for my code, it might not be so crucial for yours.
I was looking for such an algorithm myself and wondered if I could invent one myself. Turned out not to be too hard. So here is a small function to create stars and polygons, with options to set the number of point, outer radius, and inner radius (the latter does only apply to stars).
function makeStar(c, s, x, y , p, o, i) {
var ct = c.getContext('2d');
var points = p || 5;
var outer_radius = o || 100;
var inner_radius = i || 40;
var start_x = x || 100;
var start_y = y || 100;
var new_outer_RAD, half_new_outer_RAD;
var RAD_distance = ( 2 * Math.PI / points);
var RAD_half_PI = Math.PI /2;
var i;
ct.moveTo(start_x, start_y);
ct.beginPath();
for (i=0; i <= points; i++) {
new_outer_RAD = (i + 1) * RAD_distance;
half_new_outer_RAD = new_outer_RAD - (RAD_distance / 2);
if (s) {
ct.lineTo(start_x + Math.round(Math.cos(half_new_outer_RAD - RAD_half_PI) * inner_radius), start_y + Math.round(Math.sin(half_new_outer_RAD - RAD_half_PI) * inner_radius));
}
ct.lineTo(start_x + Math.round(Math.cos(new_outer_RAD - RAD_half_PI) * outer_radius), start_y + Math.round(Math.sin(new_outer_RAD - RAD_half_PI) * outer_radius));
}
ct.stroke();
}
var canvas = document.getElementById('canvas');
makeStar(canvas);
makeStar(canvas, true, 120,200, 7, 110, 40);

Categories