Changing the Direction of an Object in a Diagonal orientation - javascript

I'm trying to make an object move in the shape of a triangle however, I'm running into some trouble with changing the orientation. I've been referencing my lecture notes but I can't seem to change what I want.
function aniLoop(){
clear();
circlesUpdate(aC);
requestAnimationFrame(aniLoop)
}
function circlesUpdate(a){
for (var i = 0; i<a.length; i++){
circle(a[i]);
updateData(a[i]);
}
}
function circle(o){
ctx.beginPath();
ctx.arc(o.x, o.y, o.r, 0, 2*Math.PI);
ctx.fillStyle = "hsla("+o.c+", 100%, 50%, "+o.a+")";
ctx.fill();
}
function randn(r){
return Math.random()*r - r/2
}
function rand(r){
return Math.random()*r
}
I'm trying to make a loop of the circle to move in a triangle starting from w/2 and h/5 of the canvas size.
function createData(num){
for (var i = 0; i<num; i++){
aC.push({
x: w/2,
dx: randn(0),
y: h/5,
dy: 2+rand(3),
r: 50,
c: 200+rand(60),
a: 0.5,
})
}
}
function clear(){
ctx.clearRect(0, 0, w, h);
}
function updateData(o){
var i;
o.x += o.dx;
o.y += o.dy;
o.a -= o.da;
if(o.x > w || o.x < 0){
o.dx *= -1;
}
if(o.y > h || o.y < 0){
o.dy *= -1;
}
}
I know that the function is set up so that the circle starts from w/2,h/5 and moves down then bounces back up, but I'm not sure how to change that so the circle moves in a triangle orientation from the top corner to the bottom left to the bottom right and back to the top corner.

If I understood correctly...
You want to create an animation where a circle moves in a triangle loop.
I see too much code on that example, no clue why we need random for this, also it does not show how/where the createData function is used...
Here is what I would do:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var i = 0, n = 1
var points = [[10,10], [80,10], [10,80]]
var speed = [ [1,0], [-1, 1], [0,-1]]
var p = points[i]
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(p[0], p[1], 5, 2* Math.PI, 0);
ctx.fill()
p = [p[0]+speed[i][0], p[1]+speed[i][1]]
if (Math.hypot(p[0]-points[n][0], p[1]-points[n][1]) < 2) {
i = n
n = (n+1) % points.length
}
}
setInterval(draw, 30);
<canvas id="canvas" width=150 height=150></canvas>
You don't really specify what type of triangle so for simplicity, I coded a right triangle...
On the points = [[10,10], [80,10], [10,80]]
those are the corners of our triangle
And on speed = [ [1,0], [-1, 1], [0,-1]]
that is the speed for our object (wanna go faster change those 1 to 2)
We have minimal math:
p = [p[0]+speed[i][0], p[1]+speed[i][1]]
Just increasing the position by current speed
And on the if statement:
if (Math.hypot(p[0]-points[n][0], p[1]-points[n][1]) < 2)
We are just checking the distance to the next point, if we are less than 2 pixels away we will change our "current" index to next i = n

Related

Static vector field with classic arrows at every point on p5.js

The goal is to have an illustration of a vector field with arrows (i.e. segments with chevron at the tip) at each point on a 2D or 3D grid with p5.js. The reason is that I see a lot of generative art using it, and the flow fields (?) look cool, so why not use them for the depiction of a physics vector field with or without motion.
I have no clue about p5.js, but a quick online search shows me that I can generate positional vectors that start at the upper left-hand corner, and lines (segments) joining the origin to their ending points or going from one positional vector to another:
function setup() {
createCanvas(500, 500);
}
function draw() {
background(0);
let vec1 = createVector(100,100);
let vec2 = createVector(30,150);
//First vector
strokeWeight(10);
stroke(250,250,250);
line(0,0,vec1.x,vec1.y);
//Second vector
strokeWeight(10);
stroke(250,0,250);
line(0,0,vec2.x,vec2.y);
//Difference vector
strokeWeight(5);
stroke(0,250,250);
line(vec1.x,vec1.y,vec2.x,vec2.y);
}
Now I would like to add the arrowheads and make them beautiful. The ideal, long-term perfection would be the material in 3Blue1Brown:
I found an answer here, which allows to, for instance draw a vector between two points (or the ends of two positional vectors):
function setup() {
createCanvas(500, 500);
}
function draw() {
background(240);
let v0 = createVector(250,250); //Beginning point at center canvas.
let v1 = createVector(50,50); //Ending point at the tip of this positional vec.
drawArrow(v0, v1, 'red'); //Function that draws a red vector from vec0 to vec1.
}
// draw an arrow for a vector at a given base position
function drawArrow(base, vec, myColor) {
stroke(myColor);
strokeWeight(4);
fill(myColor);
translate(base.x, base.y); //Will transport the object line (below) to the tip of the positional vector v1
line(0, 0, vec.x, vec.y); //The line from the O to the tip of v1
rotate(vec.heading()); //Rotates the following triangle the angle of v1
let arrowSize = 7; // Determines size of the vector arrowhead (triangle).
translate(vec.mag() - arrowSize, 0); //Will translate a triangle below by the modulus of v1
triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
}
Or here a vector from the center of the canvas to the tip of the mouse:
function setup() {
createCanvas(500, 500);
}
function draw() {
background(240);
let v0 = createVector(250,250);
let v1 = createVector(mouseX - 250, mouseY - 250);
drawArrow(v0, v1, 'red');
}
// draw an arrow for a vector at a given base position
function drawArrow(base, vec, myColor) {
stroke(myColor);
strokeWeight(4);
fill(myColor);
translate(base.x, base.y);
line(0, 0, vec.x, vec.y);
rotate(vec.heading());
let arrowSize = 7;
translate(vec.mag() - arrowSize, 0);
triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
}
And here is not just one vector, but an actual (random) vector field created with the above code tweaking the lines here by Daniel Shiffman:
var inc = 0.1;
scl = 35;
var cols,rows;
function setup() {
createCanvas(500,500);
cols = floor(width/scl);
rows = floor(height/scl);
}
function draw() {
background(255);
var yoff = 0;
loadPixels();
for (var y = 0; y < rows; y++) {
var xoff = 0;
for (var x = 0; x < cols; x++) {
var index = (x + y * width)*4;
var angle = noise(xoff,yoff) * TWO_PI;
var v = p5.Vector.fromAngle(angle);
xoff +- inc;
fill('blue');
stroke('blue');
push();
translate(x*scl,y*scl);
rotate(v.heading());
line(0,0,0.5*scl,0);
let arrowSize = 7;
translate(0.5*scl - arrowSize, 0);
triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
pop();
}
yoff += inc;
}
}
Unfortunately, the origin at the upper-left hand corner renders physical fields mathematically expressed rather misleading, conveying the intuition the the field flows counter-clockwise:
var inc = 0.1;
scl = 35;
var cols,rows;
function setup() {
createCanvas(500,500);
cols = floor(width/scl);
rows = floor(height/scl);
}
function draw() {
background(255);
var yoff = 0;
loadPixels();
for (var y = 0; y < rows; y++) {
var xoff = 0;
for (var x = 0; x < cols; x++) {
var index = (x + y * width)*4;
var angle = noise(xoff,yoff) * TWO_PI;
//var v = createVector(sin(x)+cos(y),sin(x)*cos(y));
var v = createVector(y,-x);
xoff +- inc;
fill('blue');
stroke('blue');
push();
translate(x*scl,y*scl);
rotate(v.heading());
line(0,0,0.5*scl,0);
let arrowSize = 7;
translate(0.5*scl - arrowSize, 0);
triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
pop();
}
yoff += inc;
}
}
The most common Cartesian coordinates with negative and positive values and four quadrants would show how this field is really flowing clockwise:
But this seems to have an almost simple fix by resetting the counters to range from minus the number of rows (and columns) to plus the number of rows (and columns), as opposed to starting from zero. In addition, there is a need to flip the direction of increasing values in the y axis, and the origin needs to be translated to the middle of the canvas (height/2,height/2) if square:
var inc = 0.1;
scl = 35;
var cols,rows;
function setup() {
createCanvas(500,500);
cols = floor(width/scl);
rows = floor(height/scl);
}
function draw() {
translate(height/2, height/2); //moves the origin to bottom left
scale(1, -1); //flips the y values so y increases "up"
background(255);
var yoff = 0;
loadPixels();
for (var y = -rows; y < rows; y++) {
var xoff = 0;
for (var x =- cols; x < cols; x++) {
var index = (x + y * width)*4;
var angle = noise(xoff,yoff) * TWO_PI;
//var v = createVector(sin(x)+cos(y),sin(x)*cos(y));
var v = createVector(y,-x);
xoff +- inc;
fill('blue');
stroke('blue');
push();
translate(x*scl,y*scl);
rotate(v.heading());
line(0,0,0.5*scl,0);
let arrowSize = 7;
translate(0.5*scl - arrowSize, 0);
triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
pop();
}
yoff += inc;
}
}

How to avoid repeated?

Good day,
I am generating some circles with colors, sizes and positions. All of this things randomly.
But, my problem is that I do not want them to collide, so that no circle is inside another, not even a little bit.
The logic explained in detail within the code, I would like to know why the failure and why the infinite loop.
The important functions are:
checkSeparation and setPositions
window.addEventListener("load", draw);
function draw() {
var canvas = document.getElementById("balls"), // Get canvas
ctx = canvas.getContext("2d"); // Context
canvas.width = document.body.clientWidth; // Set canvas width
canvas.height = document.documentElement.scrollHeight; // Height
var cW = canvas.width, cH = canvas.height; // Save in vars
ctx.fillStyle = "#fff022"; // Paint background
ctx.fillRect(0, 0, cW, cH); // Coordinates to paint
var arrayOfBalls = createBalls(); // create all balls
setPositions(arrayOfBalls, cW, cH);
arrayOfBalls.forEach(ball => { // iterate balls to draw
ctx.beginPath(); // start the paint
ctx.fillStyle = ball.color;
ctx.arc(ball.x, ball.y, ball.radius, 0, (Math.PI/180) * 360, false); // draw the circle
ctx.fill(); // fill
ctx.closePath(); // end the paint
});
}
function Ball() {
this.x = 0; // x position of Ball
this.y = 0; // y position of Ball
this.radius = Math.floor(Math.random() * ( 30 - 10 + 1) + 10);
this.color = "";
}
Ball.prototype.setColor = function(){
for(var j = 0, hex = "0123456789ABCDEF", max = hex.length,
random, str = ""; j <= 6; j++, random = Math.floor(Math.random() * max), str += hex[random])
this.color = "#" + str;
};
function random(val, min) {
return Math.floor(Math.random() * val + min); // Random number
}
function checkSeparation(value, radius, toCompare) {
var min = value - radius, // Min border of circle
max = value + radius; // Max border of circle
// Why ? e.g => x position of circle + this radius it will be its right edge
for(; min <= max; min++) {
if(toCompare.includes(min)) return false;
/*
Since all the positions previously obtained, I add them to the array, in order to have a reference when verifying the other positions and that they do NOT collide.
Here I check if they collide.
In the range of:
[pos x - its radius, pos x + its radius]
*/
}
return true; // If they never collided, it returns true
}
function createBalls() {
var maxBalls = 50, // number of balls
balls = []; // array of balls
for(var j = 0; j < maxBalls; j++) { // create 50 balls
var newBall = new Ball(); // create ball
newBall.setColor(); // set the ball color
balls.push(newBall); //push the ball to the array of balls
}
return balls; // return all balls to draw later
}
function setPositions(balls, canvasW, canvasH) {
var savedPosX = [], // to save x pos of balls
savedPosY = []; // to save y pos of balls
for(var start = 0, max = balls.length; start < max; start++) {
var current = balls[start], // current ball
randomX = random(canvasW, current.radius), // get random value for x pos
randomY = random(canvasH, current.radius); // get random value for y pos
if(checkSeparation(randomX, current.radius, savedPosX)) {
current.x = randomX; // If it position, along with your radio does not touch another circle, I add the position
} else {
// start--; continue;
console.log("X: The above code causes an infinite loop");
}
if(checkSeparation(randomY, current.radius, savedPosY)) {
current.y = randomY;
} else {
// start--; continue;
console.log("Y: The above code causes an infinite loop");
}
}
}
body,html {
margin: 0; border: 0; padding: 0; overflow: hidden;
}
<canvas id="balls"></canvas>
In your code, you test possible collisions by means of arrays of already used x and y positions, but you never add new positions to these arrays. You also check the x and y coordinates separately, which means you are really testing a collision of a bounding box.
Two circles collide when the distance between their centres is smaller than the sum of their radii, so you could use:
function collides(balls, n, x, y, r) {
for (let i = 0; i < n; i++) {
let ball = balls[i];
let dx = ball.x - x;
let dy = ball.y - y;
let dd = dx*dx + dy*dy;
let rr = r + ball.radius;
if (dd < rr * rr) return true;
}
return false;
}
function setPositions(balls, canvasW, canvasH) {
for (let i = 0, max = balls.length; i < max; i++) {
let ball = balls[i],
r = ball.radius,
maxTries = 20;
ball.x = -canvasW;
ball.y = -canvasH;
for (let tries = 0; tries = maxTries; tries++) {
let x = random(canvasW - 2*r, r),
y = random(canvasH - 2*r, r);
if (!collides(balls, i, x, y, r)) {
ball.x = x;
ball.y = y;
break;
}
}
}
}
This is reasonably fast for 50 balls, but will be slow if you have more balls. In that case, some spatial data structures can speed up the collision search.
You must also guard against the case that no good place can be found. The code above gives up after 20 tries and moves the ball outside the visible canvas. You can improve the chances of placing balls by sorting the balls by radius and plaing the large balls first.
Finally, you add one hex digit too many to your random colour. (That for loop, where everything happens in the loop control is horrible, by the way.)

Acceleration setInterval in Jquery

I am trying to create a bouncing ball (on a canvas). I would love if the ball could accelerate when going up and down. No idea how to do this with setInterval. Here is my code:
setInterval(function animate() {
ctx.clearRect( 0, 0, canvas.width, canvas.height);
if (movement1 === true) {
dotHeight += 1;
if (dotHeight >= 100) movement1 = false;
} else {
dotHeight -= 1;
if (dotHeight <= 0) movement1 = true;
}
ctx.beginPath();
ctx.arc(canvas.width / 2, (canvas.height / 2) + dotHeight, dotSize, 0, 2 * Math.PI);
ctx.fillStyle = "white";
ctx.fill();
}, 4);
This results in a linear movement. I would love to have a natural movement. Basically starting fast and getting slower when reaching the top and vice versa.
You should have both a speed and a gravity (or acceleration) variable:
speed tells you how many units (pixels here) is going to travel your object in the current update.
gravity tells you by how many units is speed increased on each update.
You want a constant gravity so that speed is increasing the same amount of pixels on each update. That will give you a variable speed, so that your object (dot here) is travelling longer or shorter distances on each update, depending on the direction it is travelling.
To make the dot bounce just change the direction of its speed once it reaches the floor. You just need to multiply it by -1 or, instead of that, you could multiply it by a bouncingFactor (-1 < bouncingFactor < 0) so that it loses energy on each bounce:
Here you can see a working example:
var canvas = document.getElementById("canvas");
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
var ctx = canvas.getContext("2d");
var frames = 0; // Frame counter just to make sure you don't crash your browser while editing code!
// DOT STUFF:
var dotSize = 20;
var dotMinY = 0 + dotSize; // Start position
var dotMaxY = canvas.height - dotSize; // Floor
var dotY = dotMinY;
var dotSpeed = 0;
var dotLastBounceSpeed = 0; // You can use this to determine whether the ball is still bouncing enough to be visible by the user.
var center = canvas.width / 2; // Try to take every operation you can out of the animate function.
var pi2 = 2 * Math.PI;
// WORLD STUFF:
var gravity = .5;
var bounceFactor = .8; // If < 1, bouncing absorbs energy so ball won't go as high as it was before.
// MAIN ANIMATION LOOP:
function animate() {
ctx.clearRect( 0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(center, dotY, dotSize, 0, pi2);
ctx.fillStyle = "red";
ctx.fill();
// First, dotSpeed += gravity is calculated and that returns the new value for dotSpeed
// then, that new value is added to dotY.
dotY += dotSpeed += gravity;
if(dotY >= dotMaxY ) {
dotY = dotMaxY;
dotSpeed *= -bounceFactor;
}
var dotCurrentBounceSpeed = Math.round(dotSpeed * 100); // Takes two decimal digits.
if(frames++ < 5000 && dotLastBounceSpeed != dotCurrentBounceSpeed) {
dotLastBounceSpeed = dotCurrentBounceSpeed;
setTimeout(animate, 16); // 1000/60 = 16.6666...
}
else alert("Animation end. Took " + frames + " frames.");
}
animate();
html, body, #canvas {
position:relative;
width: 100%;
height: 100%;
margin: 0;
overflow:hidden;
}
<canvas id="canvas"></canvas>
You should also consider using requestAnimationFrame insted of setTimeout. From the MDN doc:
The Window.requestAnimationFrame() method tells the browser that you
wish to perform an animation and requests that the browser call a
specified function to update an animation before the next repaint. The
method takes as an argument a callback to be invoked before the
repaint.
The same example with requestAnimationFrame:
var canvas = document.getElementById("canvas");
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
var ctx = canvas.getContext("2d");
var frames = 0; // Frame counter just to make sure you don't crash your browser while editing code!
// DOT STUFF:
var dotSize = 20;
var dotMinY = 0 + dotSize; // Start position
var dotMaxY = canvas.height - dotSize; // Floor
var dotY = dotMinY;
var dotSpeed = 0;
var dotLastBounceSpeed = 0; // You can use this to determine whether the ball is still bouncing enough to be visible by the user.
var center = canvas.width / 2; // Try to take every operation you can out of the animate function.
var pi2 = 2 * Math.PI;
// WORLD STUFF:
var gravity = .5;
var bounceFactor = .8; // If < 1, bouncing absorbs energy so ball won't go as high as it was before.
// MAIN ANIMATION LOOP:
function animate() {
ctx.clearRect( 0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(center, dotY, dotSize, 0, pi2);
ctx.fillStyle = "red";
ctx.fill();
// First, dotSpeed += gravity is calculated and that returns the new value for dotSpeed
// then, that new value is added to dotY.
dotY += dotSpeed += gravity;
if(dotY >= dotMaxY ) {
dotY = dotMaxY;
dotSpeed *= -bounceFactor;
}
var dotCurrentBounceSpeed = Math.round(dotSpeed * 100); // Takes two decimal digits.
if(frames++ < 5000 && dotLastBounceSpeed != dotCurrentBounceSpeed) {
dotLastBounceSpeed = dotCurrentBounceSpeed;
//setTimeout(animate, 10);
window.requestAnimationFrame(animate); // Better!!
}
else alert("Animation end. Took " + frames + " frames.");
}
animate();
html, body, #canvas {
position:relative;
width: 100%;
height: 100%;
margin: 0;
overflow:hidden;
}
<canvas id="canvas"></canvas>
As you can see, you only need to change one line of code! However, you may need a polyfill so that you fall back to setTimeout if the browser does not support requestAnimationFrame.
You can learn more about requestAnimationFrame in this post. It explains the basics and also how to set a custom frame rate.
The basic principle is to use a velocity variable as opposed to a constant height increment. So instead of dotHeight += 1 or dotHeight -= 1 you would do dotHeight += dotVelocity, where you define dotVelocity, and subtract it by a constant value (gravity) whenever the ball is in the air.
var dotHeight = 0;
var dotVelocity = 3; // start out moving up, like in your example
var gravity = .1; // you can adjust this constant for stronger/weaker gravity
setInterval(function animate() {
ctx.clearRect( 0, 0, canvas.width, canvas.height);
if (dotHeight > 0) { // if it hit the ground, stop movement
dotVelocity -= gravity;
dotHeight += dotVelocity;
}
ctx.beginPath();
ctx.arc(canvas.width / 2, (canvas.height / 2) + dotHeight, dotSize, 0, 2 * Math.PI);
ctx.fillStyle = "white";
ctx.fill();
}, 4);
You could use a speed variable instead of the constant 1 to determine how "far" to move the ball, like this:
var speed = 1;
setInterval(function animate() {
ctx.clearRect( 0, 0, canvas.width, canvas.height);
if (movement1 === true) {
dotHeight += speed;
if (dotHeight >= 100) movement1 = false;
// make it faster
speed += 1;
} else {
dotHeight -= speed;
if (dotHeight <= 0) movement1 = true;
// slow down
speed -= 1;
}
ctx.beginPath();
ctx.arc(canvas.width / 2, (canvas.height / 2) + dotHeight, dotSize, 0, 2 * Math.PI);
ctx.fillStyle = "white";
ctx.fill();
}, 4);

How to create a camera view in canvas that will follow a players rotation and rotation?

I'm trying to create a game in canvas with javascript where you control a spaceship and have it so that the canvas will translate and rotate to make it appear like the spaceship is staying stationary and not rotating.
Any help would be greatly appreciated.
window.addEventListener("load",eventWindowLoaded, false);
function eventWindowLoaded() {
canvasApp();
}
function canvasSupport() {
return Modernizr.canvas;
}
function canvasApp() {
if (!canvasSupport()) {
return;
}
var theCanvas = document.getElementById("myCanvas");
var height = theCanvas.height; //get the heigth of the canvas
var width = theCanvas.width; //get the width of the canvas
var context = theCanvas.getContext("2d"); //get the context
var then = Date.now();
var bgImage = new Image();
var stars = new Array;
bgImage.onload = function() {
context.translate(width/2,height/2);
main();
}
var rocket = {
xLoc: 0,
yLoc: 0,
score : 0,
damage : 0,
speed : 20,
angle : 0,
rotSpeed : 1,
rotChange: 0,
pointX: 0,
pointY: 0,
setScore : function(newScore){
this.score = newScore;
}
}
function Star(){
var dLoc = 100;
this.xLoc = rocket.pointX+ dLoc - Math.random()*2*dLoc;
this.yLoc = rocket.pointY + dLoc - Math.random()*2*dLoc;
//console.log(rocket.xLoc+" "+rocket.yLoc);
this.draw = function(){
drawStar(this.xLoc,this.yLoc,20,5,.5);
}
}
//var stars = new Array;
var drawStars = function(){
context.fillStyle = "yellow";
if (typeof stars !== 'undefined'){
//console.log("working");
for(var i=0;i< stars.length ;i++){
stars[i].draw();
}
}
}
var getDistance = function(x1,y1,x2,y2){
var distance = Math.sqrt(Math.pow((x2-x1),2)+Math.pow((y2-y1),2));
return distance;
}
var updateStars = function(){
var numStars = 10;
while(stars.length<numStars){
stars[stars.length] = new Star();
}
for(var i=0; i<stars.length; i++){
var tempDist = getDistance(rocket.pointX,rocket.pointY,stars[i].xLoc,stars[i].yLoc);
if(i == 0){
//console.log(tempDist);
}
if(tempDist > 100){
stars[i] = new Star();
}
}
}
function drawRocket(xLoc,yLoc, rWidth, rHeight){
var angle = rocket.angle;
var xVals = [xLoc,xLoc+(rWidth/2),xLoc+(rWidth/2),xLoc-(rWidth/2),xLoc-(rWidth/2),xLoc];
var yVals = [yLoc,yLoc+(rHeight/3),yLoc+rHeight,yLoc+rHeight,yLoc+(rHeight/3),yLoc];
for(var i = 0; i < xVals.length; i++){
xVals[i] -= xLoc;
yVals[i] -= yLoc+rHeight;
if(i == 0){
console.log(yVals[i]);
}
var tempXVal = xVals[i]*Math.cos(angle) - yVals[i]*Math.sin(angle);
var tempYVal = xVals[i]*Math.sin(angle) + yVals[i]*Math.cos(angle);
xVals[i] = tempXVal + xLoc;
yVals[i] = tempYVal+(yLoc+rHeight);
}
rocket.pointX = xVals[0];
rocket.pointY = yVals[0];
//rocket.yLoc = yVals[0];
//next rotate
context.beginPath();
context.moveTo(xVals[0],yVals[0])
for(var i = 1; i < xVals.length; i++){
context.lineTo(xVals[i],yVals[i]);
}
context.closePath();
context.lineWidth = 5;
context.strokeStyle = 'blue';
context.stroke();
}
var world = {
//pixels per second
startTime: Date.now(),
speed: 50,
startX:width/2,
startY:height/2,
originX: 0,
originY: 0,
xDist: 0,
yDist: 0,
rotationSpeed: 20,
angle: 0,
distance: 0,
calcOrigins : function(){
world.originX = -world.distance*Math.sin(world.angle*Math.PI/180);
world.originY = -world.distance*Math.cos(world.angle*Math.PI/180);
}
};
var keysDown = {};
addEventListener("keydown", function (e) {
keysDown[e.keyCode] = true;
}, false);
addEventListener("keyup", function (e) {
delete keysDown[e.keyCode];
}, false);
var update = function(modifier) {
if (37 in keysDown) { // Player holding left
rocket.angle -= rocket.rotSpeed* modifier;
rocket.rotChange = - rocket.rotSpeed* modifier;
//console.log("left");
}
if (39 in keysDown) { // Player holding right
rocket.angle += rocket.rotSpeed* modifier;
rocket.rotChange = rocket.rotSpeed* modifier;
//console.log("right");
}
};
var render = function (modifier) {
context.clearRect(-width*10,-height*10,width*20,height*20);
var dX = (rocket.speed*modifier)*Math.sin(rocket.angle);
var dY = (rocket.speed*modifier)*Math.cos(rocket.angle);
rocket.xLoc += dX;
rocket.yLoc -= dY;
updateStars();
drawStars();
context.translate(-dX,dY);
context.save();
context.translate(-rocket.pointX,-rocket.pointY);
context.translate(rocket.pointX,rocket.pointY);
drawRocket(rocket.xLoc,rocket.yLoc,50,200);
context.fillStyle = "red";
context.fillRect(rocket.pointX,rocket.pointY,15,5);
//context.restore(); // restores the coordinate system back to (0,0)
context.fillStyle = "green";
context.fillRect(0,0,10,10);
context.rotate(rocket.angle);
context.restore();
};
function drawStar(x, y, r, p, m)
{
context.save();
context.beginPath();
context.translate(x, y);
context.moveTo(0,0-r);
for (var i = 0; i < p; i++)
{
context.rotate(Math.PI / p);
context.lineTo(0, 0 - (r*m));
context.rotate(Math.PI / p);
context.lineTo(0, 0 - r);
}
context.fill();
context.restore();
}
// the game loop
function main(){
requestAnimationFrame(main);
var now = Date.now();
var delta = now - then;
update(delta / 1000);
//now = Date.now();
//delta = now - then;
render(delta / 1000);
then = now;
// Request to do this again ASAP
}
var w = window;
var requestAnimationFrame = w.requestAnimationFrame || w.webkitRequestAnimationFrame || w.msRequestAnimationFrame || w.mozRequestAnimationFrame;
//start the game loop
//gameLoop();
//event listenters
bgImage.src = "images/background.jpg";
} //canvasApp()
Origin
When you need to rotate something in canvas it will always rotate around origin, or center for the grid if you like where the x and y axis crosses.
You may find my answer here useful as well
By default the origin is in the top left corner at (0, 0) in the bitmap.
So in order to rotate content around a (x,y) point the origin must first be translated to that point, then rotated and finally (and usually) translated back. Now things can be drawn in the normal order and they will all be drawn rotated relative to that rotation point:
ctx.translate(rotateCenterX, rotateCenterY);
ctx.rotate(angleInRadians);
ctx.translate(-rotateCenterX, -rotateCenterY);
Absolute angles and positions
Sometimes it's easier to keep track if an absolute angle is used rather than using an angle that you accumulate over time.
translate(), transform(), rotate() etc. are accumulative methods; they add to the previous transform. We can set absolute transforms using setTransform() (the last two arguments are for translation):
ctx.setTransform(1, 0, 0, 1, rotateCenterX, rotateCenterY); // absolute
ctx.rotate(absoluteAngleInRadians);
ctx.translate(-rotateCenterX, -rotateCenterY);
The rotateCenterX/Y will represent the position of the ship which is drawn untransformed. Also here absolute transforms can be a better choice as you can do the rotation using absolute angles, draw background, reset transformations and then draw in the ship at rotateCenterX/Y:
ctx.setTransform(1, 0, 0, 1, rotateCenterX, rotateCenterY);
ctx.rotate(absoluteAngleInRadians);
ctx.translate(-rotateCenterX, -rotateCenterY);
// update scene/background etc.
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transforms
ctx.drawImage(ship, rotateCenterX, rotateCenterY);
(Depending on orders of things you could replace the first line here with just translate() as the transforms are reset later, see demo for example).
This allows you to move the ship around without worrying about current transforms, when a rotation is needed use the ship's current position as center for translation and rotation.
And a final note: the angle you would use for rotation would of course be the counter-angle that should be represented (ie. ctx.rotate(-angle);).
Space demo ("random" movements and rotations)
The red "meteors" are dropping in one direction (from top), but as the ship "navigates" around they will change direction relative to our top view angle. Camera will be fixed on the ship's position.
(ignore the messy part - it's just for the demo setup, and I hate scrollbars... focus on the center part :) )
var img = new Image();
img.onload = function() {
var ctx = document.querySelector("canvas").getContext("2d"),
w = 600, h = 400, meteors = [], count = 35, i = 0, x = w * 0.5, y, a = 0, a2 = 0;
ctx.canvas.width = w; ctx.canvas.height = h; ctx.fillStyle = "#555";
while(i++ < count) meteors.push(new Meteor());
(function loop() {
ctx.clearRect(0, 0, w, h);
y = h * 0.5 + 30 + Math.sin((a+=0.01) % Math.PI*2) * 60; // ship's y and origin's y
// translate to center of ship, rotate, translate back, render bg, reset, draw ship
ctx.translate(x, y); // translate to origin
ctx.rotate(Math.sin((a2+=0.005) % Math.PI) - Math.PI*0.25); // rotate some angle
ctx.translate(-x, -y); // translate back
ctx.beginPath(); // render some moving meteors for the demo
for(var i = 0; i < count; i++) meteors[i].update(ctx); ctx.fill();
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transforms
ctx.drawImage(img, x - 32, y); // draw ship as normal
requestAnimationFrame(loop); // loop animation
})();
};
function Meteor() { // just some moving object..
var size = 5 + 35 * Math.random(), x = Math.random() * 600, y = -200;
this.update = function(ctx) {
ctx.moveTo(x + size, y); ctx.arc(x, y, size, 0, 6.28);
y += size * 0.5; if (y > 600) y = -200;
};
}
img.src = "http://i.imgur.com/67KQykW.png?1";
body {background:#333} canvas {background:#000}
<canvas></canvas>

How to detect shape on a transparent canvas?

I'm looking for a method of detecting a shape in a transparent PNG.
For example, I will create a transparent canvas of 940x680, then place a fully opaque object somewhere in that canvas.
I want to be able to detect the size (w, h), and top + left location of that object.
Here is an example of the original image:
Here is an example of what I would like to achieve (Bounding box overlay, with top + left margin data):
I've found a resource that does some transparency detection, but I'm not sure how I scale something like this to what I'm looking for.
var imgData,
width = 200,
height = 200;
$('#mask').bind('mousemove', function(ev){
if(!imgData){ initCanvas(); }
var imgPos = $(this).offset(),
mousePos = {x : ev.pageX - imgPos.left, y : ev.pageY - imgPos.top},
pixelPos = 4*(mousePos.x + height*mousePos.y),
alpha = imgData.data[pixelPos+3];
$('#opacity').text('Opacity = ' + ((100*alpha/255) << 0) + '%');
});
function initCanvas(){
var canvas = $('<canvas width="'+width+'" height="'+height+'" />')[0],
ctx = canvas.getContext('2d');
ctx.drawImage($('#mask')[0], 0, 0);
imgData = ctx.getImageData(0, 0, width, height);
}
Fiddle
What you need to do:
Get the buffer
Get a 32-bits reference of that buffer (If your other pixels are transparent then you can use a Uint32Array buffer to iterate).
Scan 0 - width to find x1 edge
Scan width - 0 to find x2 edge
Scan 0 - height to find y1 edge
Scan height - 0 to find y2 edge
These scans can be combined but for simplicity I'll show each step separately.
Online demo of this can be found here.
Result:
When image is loaded draw it in (if the image is small then the rest of this example would be waste as you would know the coordinates when drawing it - assuming here the image you draw is large with a small image inside it)
(note: this is a non-optimized version for the sake of simplicity)
ctx.drawImage(this, 0, 0, w, h);
var idata = ctx.getImageData(0, 0, w, h), // get image data for canvas
buffer = idata.data, // get buffer (unnes. step)
buffer32 = new Uint32Array(buffer.buffer), // get a 32-bit representation
x, y, // iterators
x1 = w, y1 = h, x2 = 0, y2 = 0; // min/max values
Then scan each edge. For left edge you scan from 0 to width for each line (non optimized):
// get left edge
for(y = 0; y < h; y++) { // line by line
for(x = 0; x < w; x++) { // 0 to width
if (buffer32[x + y * w] > 0) { // non-transparent pixel?
if (x < x1) x1 = x; // if less than current min update
}
}
}
For the right edge you just reverse x iterator:
// get right edge
for(y = 0; y < h; y++) { // line by line
for(x = w; x >= 0; x--) { // from width to 0
if (buffer32[x + y * w] > 0) {
if (x > x2) x2 = x;
}
}
}
And the same is for top and bottom edges just that the iterators are reversed:
// get top edge
for(x = 0; x < w; x++) {
for(y = 0; y < h; y++) {
if (buffer32[x + y * w] > 0) {
if (y < y1) y1 = y;
}
}
}
// get bottom edge
for(x = 0; x < w; x++) {
for(y = h; y >= 0; y--) {
if (buffer32[x + y * w] > 0) {
if (y > y2) y2 = y;
}
}
}
The resulting region is then:
ctx.strokeRect(x1, y1, x2-x1, y2-y1);
There are various optimizations you could implement but they depend entirely on the scenario such as if you know approximate placement then you don't have to iterate all lines/columns.
You could do a brute force guess of he placement by skipping x number of pixels and when you found a non-transparent pixel you could make a max search area based on that and so forth, but that is out of scope here.
Hope this helps!
I was in need of something similar to this, just recently. Although the question is answered, I wanted to post my code for a future reference.
In my case, I'm drawing a (font) icon on a blank/transparent canvas, and want to get the bounding box. Even if I know the height of the icon (using font-size, i.e., height), I can't know the width. So I have to calculate it manually.
I'm not sure if there's a clever way to calculate this. First thing that popped into my head was doing it the hard way: manually checking every pixel, and that's what I did.
I think the code is pretty self-explanatory, so I won't do any explanation. I tried to keep the code as clean as possible.
/* Layer 3: The App */
let canvas = document.querySelector("#canvas");
let input = document.querySelector("#input");
let output = document.querySelector("#output");
canvas.width = 256;
canvas.height = 256;
let context = canvas.getContext("2d");
context.font = "200px Arial, sans-serif";
let drawnLetter = null;
drawLetter(input.value);
function drawLetter(letter) {
letter = letter ? letter[0] : null;
if (!letter) {
// clear canvas
context.clearRect(0, 0, canvas.width, canvas.height);
output.textContent = null;
return;
}
if (letter == drawnLetter) {
return;
}
drawnLetter = letter;
// clear canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// draw letter
context.fillText(letter, 50, canvas.height - 50);
// find edges
let boundingBox = findEdges(context);
// mark the edges
context.beginPath();
context.rect(boundingBox.left, boundingBox.top, boundingBox.width, boundingBox.height);
context.lineWidth = 2;
context.strokeStyle = "red";
context.stroke();
// output the values
output.textContent = JSON.stringify(boundingBox, null, " ");
}
/* Layer 2: Interacting with canvas */
function findEdges(context) {
let left = findLeftEdge(context);
let right = findRightEdge(context);
let top = findTopEdge(context);
let bottom = findBottomEdge(context);
// right and bottom are relative to top left (0,0)
return {
left,
top,
right,
bottom,
width : right - left,
height : bottom - top,
};
}
function findLeftEdge(context) {
let imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
let emptyPixel = [0, 0, 0, 0].join();
for (let x = 0; x < context.canvas.width; x++) {
for (let y = 0; y < context.canvas.height; y++) {
let pixel = getPixel(imageData, x, y).join();
if (pixel != emptyPixel) {
return x;
}
}
}
}
function findRightEdge(context) {
let imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
let emptyPixel = [0, 0, 0, 0].join();
for (let x = context.canvas.width - 1; x >= 0; x--) {
for (let y = 0; y < context.canvas.height; y++) {
let pixel = getPixel(imageData, x, y).join();
if (pixel != emptyPixel) {
return x;
}
}
}
}
function findTopEdge(context) {
let imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
let emptyPixel = [0, 0, 0, 0].join();
for (let y = 0; y < context.canvas.height; y++) {
for (let x = 0; x < context.canvas.width; x++) {
let pixel = getPixel(imageData, x, y).join();
if (pixel != emptyPixel) {
return y;
}
}
}
}
function findBottomEdge(context) {
let imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
let emptyPixel = [0, 0, 0, 0].join();
for (let y = context.canvas.height - 1; y >= 0; y--) {
for (let x = 0; x < context.canvas.width; x++) {
let pixel = getPixel(imageData, x, y).join();
if (pixel != emptyPixel) {
return y;
}
}
}
}
/* Layer 1: Interacting with ImageData */
/**
* Returns the pixel array at the specified position.
*/
function getPixel(imageData, x, y) {
return getPixelByIndex(imageData, pos2index(imageData, x, y));
}
/**
* Returns the RGBA values at the specified index.
*/
function getPixelByIndex(imageData, index) {
return [
imageData.data[index + 0],
imageData.data[index + 1],
imageData.data[index + 2],
imageData.data[index + 3],
];
}
/**
* Returns the index of a position.
*/
function pos2index(imageData, x, y) {
return 4 * (y * imageData.width + x);
}
body {
background-color: hsl(0, 0%, 95%);
}
canvas {
background: white;
image-rendering: pixelated;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEXMzMz////TjRV2AAAAEUlEQVQI12P4z8CAFWEX/Q8Afr8P8erzE9cAAAAASUVORK5CYII=);
zoom: 0.8; /* this counters the scale up (125%) of my screen; can be removed */
}
input {
padding: 0.2em;
margin-top: 0.5em;
}
<canvas id="canvas"></canvas>
<br>
<input type="text" id="input" placeholder="type a letter" value="A" onkeyup="drawLetter(this.value)" />
<pre id="output"></pre>

Categories

Resources