Repeat shapes with boundary detection - html5, canvas, javascript - javascript

I am wanting to create a simple abstract pattern using the html5 canvas tag and javascript. I have worked out the guts of what I want it to do using some variables, functions and objects, but with the boundary detection that I have employed I am wanting each particular shape to go back to its starting position when it goes out of the screen (and thus loop the animation).
So with that being my question, here is my code. Also any other structure tips are appreciated as I am new to OO in Javascript.
See my progress here: http://helloauan.com/apps/test/
Cheers!

I'm not really sure if what you mean exactly is, once the big white diagnal lines are all the way off the top right corner of page, that's when you want them to start back at the bottom left ? right?
What you need to do is check if the line is beyond the width and height of the canvas, and in your case, the window itself since the canvas fills the browser window. So you need to do a series of conditionals. You check if the line x + line width is > canvas width and line.y + line height is > canvas height. If both are true then set the x and y of the line to - what it is at that time. So something like:
if( line.x + line.width > canvas.width && line.y + line.height < 0) {
line.x = -0;
line.y = canvasHeight + line.height;
}
This is how I recycle circles that come in from the right side of the screen and once they exit the left side they start over on the right.
if( d.x + d.radius < 0 ) {
d.radius = 5+(Math.random()*10);
d.x = cwidth + d.radius;
d.y = Math.floor(Math.random()*cheight);
d.vX = -5-(Math.random()*5);
}
The first thing is just psuedo, you should take a look at a thing I made to use as a starting point for things like this. The structure of your code could use some more organization, canvas gets real complex real quick.
Using the arrow keys, move the square off any one of the 4 sides and see it come in on opposite side.
http://anti-code.com/games/envy/envy.html
Fork if you want: https://github.com/jaredwilli/envy

Related

How do I make this array of objects 'spring' into random positions on the canvas?

I'm trying to make an array of images 'spring' onto the canvas from the bottom of the screen and then land in random positions, like this image here:enter image description here (this is a screenshot of my canvas after you remove the physics)
Here is my attempt so far:
https://editor.p5js.org/holographicleah/sketches/DUY0EDnqN
I like the animation of the spring that i've managed, but I want the cats to be scattered across the whole screen like in the image above. I understand that i'm affecting the same 'force' on all of the objects, so it's natural that they all end up at the same height at the top of the screen. How could I randomise it so that they end up everywhere? Should I have used some kind of lerp to absolute positions instead? Open to trying something different if needs be. Still a beginner to code really so classes are still new to me!
Inspiration for this code came from both https://www.youtube.com/watch?v=Rr-5HiXquhw&t=937s for the spring physics and https://www.youtube.com/watch?v=cl-mHFCGzYk&t=149s for the 'particles'. I've adapted what I can but I've hit an experience wall!
You already have the x position covered in your linked code. In order to randomize the y position, change your constructor() method, by adding this line:
this.randomf = random(Math.floor(height/2) - 50);
Then, in the update() method, add this line:
this.pos.y += this.vel + this.randomf;
With the first line, you're giving individual objects a property which tells them what their (randomly chosen) y limit should be.
With the second line, you're limiting the y position. You would need to adjust it a bit, to fit your use case.
And some advice - with a large number of objects springing up, you might want to consider limiting the number of cycles, by dropping the updates, once the velocity falls below a certain value. Something like this:
update(){
if(this.vel <= 0.009) {
let force = - spring * this.pos.y;
this.vel+= force;
this.pos.y += this.vel;
this.vel*=0.9;
}
}

Pixel Collision Detection Not Working

Here is my game plnkr.
(Edit: another plnkr with one static monster instead of multiple dynamic ones)
Enter or the button will restart the game.
Can anyone tell why the collision detection algorithm taken from here is not working? It seems to detect a hit not accurately (too widely). The demo on their site works great but I'm not sure what I'm doing wrong.
Most relevant piece of code (inside update function):
// Are they touching?
if (heroImage.width) {
var heroImageData = ctx.getImageData(heroImage.x, heroImage.y, heroImage.width, heroImage.height);
var monsterImageData;
for (var i = 0; i < monsters.length; i++) {
var monster = monsters[i];
monster.x += monster.directionVector.x;
monster.y += monster.directionVector.y;
monsterImageData = ctx.getImageData(monster.monsterImage.x, monster.monsterImage.y, monster.monsterImage.width, monster.monsterImage.height);
if (isPixelCollision(heroImageData, hero.x, hero.y, monsterImageData, monster.x, monster.y)) {
stop();
}
}
}
As #GameAlchemist pointed out you're taking ImageData for monster and hero from the canvas background, which has already been painted with the background image. Thus will always have alpha value 255 (Opaque).
Which is being checked in the collision function
if (
( pixels [((pixelX - x ) + (pixelY - y ) * w ) * 4 + 3 /*RGBA, alpha # 4*/] !== 0/*alpha zero expected*/ ) &&
( pixels2[((pixelX - x2) + (pixelY - y2) * w2) * 4 + 3 /*RGBA, alpha # 4*/] !== 0/*alpha zero expected*/ )
) {
return true;
}
Instead both the ImageData should be generated by drawing these images to a canvas with nothing painted. Even after doing that collision algorithm doesn't seem to work too well.
I have created two variables monsterImageData and heroImageData to hold the imageData these variable are loaded only once.
There's a new canvas in HTML file id=testCanvas. This is used to get image data values for monster and heroes.
Here is the plunker link for modified code.
Your hero image is 71x68px and has a lot of transparent space around the outside. I'm guessing if you crop this to just fit the image it will reduce the space between collisions.
You are taking the imageData on the game's drawing context, so since you have a background, there's no transparent pixel at all, so your pixel collision detection returns always true - > you are just doing a bounding box check, in fact.
The idea of the algorithm is to compare two static imageData that only need to be computed once (getImageData is a costly operation).
A few advices :
• load your images before launching the game.
• redim (crop) your image, it has a lot of void, as #Quantumplate noticed.
• compute only once the imageData of your sprites on the context before the launch of the game. Do not forget to clearRect() the canvas before the drawImage + getImageData. This is the way to solve your bug.
• get rid of the
if (xDiff < 4 && yDiff < 4) {
and the corresponding else. This 'optimisation' is pointless. The point of using pixel detection is to be precise. Redim (crop) your image is more important to win a lot of time (but do you need to ... ?? )
• Rq : How poorly written is the pixel detection algorithm !!! 1) To round a number, it's using !! 5 different methods (round, <<0, ~~, 0 |, ? : ) !!! 2) It loops on X first when CPU cache prefers on Y first, and many other things... But now if that works...
Here's an alternate (more efficient) pixel perfect collision test...
Preparation: For each image you want to test for collisions
As mentioned, trim any excess transparent pixels off the edges of your image,
Resize a canvas to the image size, (you can reuse 1 canvas for multiple images)
Draw the image on the canvas,
Get all the pixel info for the canvas: context.getImageData,
Make an array containing only alpha information: false if transparent, otherwise true.
To do a pixel-perfect collision test
Do a quick test to see if the image rects are colliding. If not, you're done.
// r1 & r2 are rect objects {x:,y:,w:.h:}
function rectsColliding(r1,r2){
return(!(
r1.x > r2.x+r2.w ||
r1.x+r1.w < r2.x ||
r1.y > r2.y+r2.h ||
r1.y+r1.h < r2.y
));
}
Calculate the intersecting rect of the 2 images
// r1 & r2 are rect objects {x:,y:,w:.h:}
function intersectingRect(r1,r2){
var x=Math.max(r1.x,r2.x);
var y=Math.max(r1.y,r2.y);
var xx=Math.min(r1.x+r1.w,r2.x+r2.w);
var yy=Math.min(r1.y+r1.h,r2.y+r2.h);
return({x:x,y:y,w:xx-x,h:yy-y});
}
Compare the intersecting pixels in both alpha arrays. If both arrays have a non-transparent pixel at the same location then there is a collision. Be sure to normalize against the origin (x=0,y=0) by offsetting your comparisons.
// warning untested code -- might need tweaking
var i=intersectingRect(r1,r2);
var offX=Math.min(r1.x,r2.x);
var offY=Math.min(r1.y,r2.y);
for(var x=i.x-offX; x<=(i.x-offX)+i.w; x++{
for(var y=i.y-offY; y<=(i.y-offY)+i.h; y++{
if(
// x must be valid for both arrays
x<alphaArray1[y].length && x<alphaArray2[y].length &&
// y must be valid for both arrays
y<alphaArray1.length && y<alphaArray2.length &&
// collision is true if both arrays have common non-transparent alpha
alphaArray1[x,y] && alphaArray2[x,y]
){
return(true);
}
}}
return(false);

multiple DIV collision detection in Javascript/JQuery

Working on a little "zombies" or "tag you're it" or "ew! you got cooties"-styled game where each AI object (a person, basically) runs around randomly. There is an initial object that is "it" or "infected" and as it moves about the screen and touches/overlaps/collides with another object it should change the touched object to the same color as the object that touched it. Newly infected objects can continue to infect other objects they randomly collide with, until - in principle - the whole population is the same color as the first infected object. (I'll worry about fancier AI where infected actively hunt nearby objects or healthy objects can avoid infected objects, later).
But after looking at various similar questions in StackOverflow that generally deal with 2 DIVs colliding, or use some sort of jQuery draggable detection trick, I'm still at a bit of a loss as to how to build upon those ideas to scale up a simple "if I am touching/overlapping/colliding with another object it should get infected too" that can be applied to a large number of elements on the page, say... less than 100 so as not to drag the browser down.
I basically get as far as determining position and widths/heights of the objects so that I know how much space they take, but then the brain goes 'bzzzzt' when trying to develop a function that checks over all the population for collisions.
Got the population moving around randomly without trouble - see JSFiddle https://jsfiddle.net/digitalmouse/5tvyjhjL/1/ for the related code. Affected function should be in the 'animateDiv()', seen below to make the stackoverflow question asking editor happy that I included some code in my question. :)
function animateDiv($target) {
var newq = makeNewPosition($target.parent());
var oldq = $target.offset();
var speed = calcSpeed([oldq.top, oldq.left], newq);
// I believe collision should be dealt with here,
// just before moving an object
$target.animate({
top: newq[0],
left: newq[1]
}, speed, function () {
animateDiv($target);
});
}
Any hints, tricks, adaptations, or code snippets that push me in the right direction are appreciated.
a quick, down and dirty solution (there are more complex algorithms) would be to use:
document.elementFromPoint(x, y);
It gets the element at the position specified. The full spec can be found here.
Assuming your 'zombies' are rectangular, you could call this for each corner, and if you get a hit, that isn't the background or the element you're checking, you've got a collision...
EDIT:
An alternate method, even 'downer and dirtier' than above, but stupidly quick, would be to get the centre points of the two objects to check, then find their absolute displacements in X and Y. If the differences are less than the sum of half their widths and heights then they are overlapping. It's by no means pix perfect, but it should be able to handle a large number objects really quickly.
EDIT 2:
First off, we need to get the centres of each object (to check)
// Values for main object
// pop these in vars as we'll need them again in a sec...
hw = object.style.width >> 1; // half width of object
hh = object.style.height >> 1; // (bit shift is faster than / 2)
cx = object.style.left + hw; // centre point in x
cy = object.style.top + hh; // and in y
// repeat for secondary object
If you don't know / store the width and height you can use:
object.getBoundingClientRect();
which returns a 'rect' object with the fields left, top, right and bottom.
Now we check proximity...
xDif = Math.abs(cx - cx1); // where cx1 is centre of object to check against
if(xDif > hw + hw1) return false; // there is no possibility of a collision!
// if we get here, there's a possible collision, so...
yDif = Math.abs(cy - cy1);
if(yDif > hh + hh1) return false; // no collision - bug out.
else {
// handle collision here...
}
Danny

Increasing the speed of a ball in javascript

What I'm trying to do is simply make a ball rebound from a wall. Everything works OK, except the fact I want to be able to increase the speed of movement. Literally, the speed is how much 'x-value' is added (measured in px) to the ball's current position. The thing is, when I'm increasing the var speed, the ball floats out of the bounds, because the rebounding is checked by the difference between the bound and the current position of the ball.
--------------------------------------update-----------------------------------------
I've used the technique suggested by Mekka, but still did something wrong.The ball doesn't float outside anymore, yet something "pushes it out" of the bounds for several pixels/"doesn't let the ball float several more pixels to reach the bounds".
My new code looks like this:
// the bounds-describing object
var border={
X:[8,302], // left and right borders in px
Y:[8,302], // top and bottom borders in px
indX:1, //border index for array Х
indY:0, //border index for array Y
changeInd:function(n){return this[n] = +!this[n]; } // function to change the index
};
if($("#ball").position().left + speed > border.X[1] || $("#ball").position().left + speed < border.X[0]){
var distX = "+=" + (border.X[border.indX] - $("#ball").position().left);
var distY = "-=" + ((border.X[border.indX] - $("#ball").position().left) * k);
$("#ball").css("left", distX);
$("#ball").css("top", distY);
border.changeInd("indX");
speed = -speed;
}
if($("#ball").position().top + k > border.Y[1] || $("#ball").position().top + k < border.Y[0]){
var distX = "+=" + ((border.Y[border.indY] - $("#ball").position().top) / k);
var distY = "+=" + (border.Y[border.indY] - $("#ball").position().top);
$("#ball").css("left", distX);
$("#ball").css("top", distY);
border.changeInd("indY");
k = -k;
}
Another problem is that my code's math is incorrect sometimes, the reason of which I absolutely can't figure out. To test it, try 45 degrees with different speed.
The question is: how can I improve the 'collision-checking' process or even apply some other technique to do this?
the whole code can be found here:
http://jsfiddle.net/au99f/16/
You're very close! The answer is actually hinted at in your question. You're currently using the absolute value of the distance to the boundary to determine when to change direction. This defines a "magic zone" where the ball can change direction that is about 6 pixels wide (given your speed of 3). When you increase speed to something higher (like 10), you could jump right over this magic zone.
A better way to do this would be to test if the next jump would put the ball completely outside the bounds. So this check is not based on a constant (like 3) but on the speed of the ball itself. You can also see how much the ball would have travelled out of bounds to determine how far to move the ball in the opposite direction. In other words, if your speed is 10, and the ball is 3 pixels from the right edge on step 8, then on step 9, the ball would be 7 pixels from the right edge, traveling left. Be wary of edge cases (ball could land exactly on bounds).

Is there any way to get these 'canvas' lines drawn in one loop?

I am simulating a page turn effect in html5 canvas.
On each page I am drawing lines to simulate lined paper.
These lines are drawn as the page is turned and in order to give natural perspective I am drawing them using quadratic curves based of several factors (page turn progress, closeness to the center of the page etc.. etc...)
The effect is very natural and looks great but I am looking for ways to optimize this.
Currently I am drawing every line twice, once for the actual line and once for a tiny highlight 1px below this line. I am doing this like so:
// render lines (shadows)
self.context.lineWidth = 0.35;
var midpage = (self.PAGE_HEIGHT)/2;
self.context.strokeStyle = 'rgba(0,0,0,1)';
self.context.beginPath();
for(i=3; i < 21; i++){
var lineX = (self.PAGE_HEIGHT/22)*i;
var curveX = (midpage - lineX) / (self.PAGE_HEIGHT);
self.context.moveTo(foldX, lineX);
self.context.quadraticCurveTo(foldX, lineX + ((-verticalOutdent*4) * curveX), foldX - foldWidth - Math.abs(offset.x), lineX + ((-verticalOutdent*2) * curveX));
}
self.context.stroke();
// render lines (highlights)
self.context.strokeStyle = 'rgba(255,255,255,0.5)';
self.context.beginPath();
for(i=3; i < 21; i++){
var lineX = (self.PAGE_HEIGHT/22)*i;
var curveX = (midpage - lineX) / (self.PAGE_HEIGHT);
self.context.moveTo(foldX, lineX+2);
self.context.quadraticCurveTo(foldX, lineX + ((-verticalOutdent*4) * curveX) + 1, foldX - foldWidth - Math.abs(offset.x), lineX + ((-verticalOutdent*2) * curveX) + 1);
}
self.context.stroke();
As you can see I am opening a path, looping through each line, then drawing the path. Then I repeat the whole process for the 'highlight' lines.
Is there any way to combine both of these operations into a single loop without drawing each line individually within the loop which would actually be far more expensive?
This is a micro-optimization, I am well aware of this. However this project is a personal exercise for me in order to learn html5 canvas performance best practices/optimizations.
Thanks in advance for any suggestions/comments
Paths can be stroked as many times as you like, they're not cleared when you call .stroke(), so:
create your path (as above)
.stroke() it
translate the context
change the colours
.stroke() it again
EDIT tried this myself - it didn't work - the second copy of the path didn't notice the translation of the coordinate space :(
It apparently would work if the path was created using new Path() as documented in the (draft) specification instead of the "current default path" but that doesn't appear to be supported in Chrome yet.

Categories

Resources