I have a snap svg animation which animates a bunch of circles, and draws a line between them if they are within a certain proximity of each other. However, I realize that there is a lot of optimizing I can do, but I'm not exactly sure how to do it. I feel like it would be useful to
have a good example of proximity detection in snap
have some more information on optimizing animations in snap svg. It hasn't been easy to find.
Here is a working example of the animation:
http://jsfiddle.net/heaversm/sbj4W/1/
and here are the things I believe can be optimized:
Each circle calls its own animation function - the circles have all been added to a group, and I'm guessing there is a way to apply random motion to all members of a group that is more performant than call a function for each and every element within the group.
for (var i=0; i<this.drawingConfig.circles.amount;i++){
...
this.animateSingle(circleShape);
}
The proximity function is awkward - for each circle, for each update cycle, I have to loop through an array of all the other circles and find out if the X and Y coordinates are close enough to draw a line to. Plus, that means you're getting duplicate lines, because each circle will draw a line to its neighbors, instead of having a single shared line between the two.
for (var i=0;i<circles.length;i++){
var nextCircle = circles[i].node;
var nextCircleX = nextCircle.cx.baseVal.value;
var distance = Math.abs(nextCircleX-thisCircleX);
var proximity = mainModule.drawingConfig.circles.proximity;
if (distance < proximity){
var nextCircleY = nextCircle.cy.baseVal.value;
var thisCircleY = shape.node.cy.baseVal.value;
var distanceY = Math.abs(nextCircleY - thisCircleY);
if (distanceY < proximity){
var line = mainModule.s.line(thisCircleX, thisCircleY, nextCircleX, nextCircleY).attr({stroke: '#a6a8ab', strokeWidth: '1px'});
mainModule.drawingConfig.circles.circleGroup.add(line);
}
}
}
Correspondingly, I each circle's animation function clears all the lines on the screen. Ideally all the circles would be sharing one update function, and in that function, you'd clear the lines.
Snap.animate(startX, animX, function (val) {
var lines = Snap.selectAll('line');
lines.remove();
...
}, mainModule.drawingConfig.circles.animTime);
Right now, I can tell the renderer can't keep up with all of the various animations / loops. Any help optimizing the above things (or anything else you can see that I'm doing weird, would be greatly appreciated.
I cleaned this up by running only one animation loop, on a timer every 10ms, and animated the position of the circles by just giving them a slope and, each update, continuing them further along that slope. You can see an updated fiddle here:
http://jsfiddle.net/heaversm/fJ6fj/
var mainModule = {
s: Snap("#svg"),
drawingConfig: {
circles: {
amount: 20,
sizeMin: 10,
sizeMax: 20,
proximity: 100,
circleGroup: null,
circleArray: [],
animTime: 2000
},
canvas: {
width: 800,
height: 600
}
},
init: function(){
//this.sizeCanvas();
this.makeCircles();
},
sizeCanvas: function(){
$('#svg').width(800).height(600);
},
makeCircles: function(){
this.drawingConfig.circles.circleGroup = this.s.g();
for (var i=0; i<this.drawingConfig.circles.amount;i++){
var circleX = this.randomNumber(0, this.drawingConfig.canvas.width);
var circleY = this.randomNumber(0, this.drawingConfig.canvas.height);
var circleRadius = this.randomNumber(this.drawingConfig.circles.sizeMin,this.drawingConfig.circles.sizeMax);
var circleFill = '#'+Math.floor(Math.random()*16777215).toString(16);
var circleShape = this.s.circle(circleX, circleY, circleRadius);
circleShape.attr({
fill: circleFill
});
this.drawingConfig.circles.circleGroup.add(circleShape);
var circleIncline = this.setIncline();
var circleObj = { incline: circleIncline, shape: circleShape };
this.drawingConfig.circles.circleArray.push(circleObj);
}
this.update();
},
setIncline: function(){
return { incX: this.randomNumber(-5,5), incY: this.randomNumber(-5,5) }
},
update: function(){
var lines = Snap.selectAll('line');
lines.remove();
for (var i=0; i<this.drawingConfig.circles.amount; i++){
var circle = this.drawingConfig.circles.circleArray[i];
var circleX = circle.shape.node.cx.animVal.value;
var circleY = circle.shape.node.cy.animVal.value;
this.move(circle,circleX,circleY);
for (var j=0;j<i;j++){
if (i != j){
var circle2 = this.drawingConfig.circles.circleArray[j];
var circle2X = circle2.shape.node.cx.animVal.value;
var circle2Y = circle2.shape.node.cy.animVal.value;
var dist = mainModule.distance(circleX,circleY,circle2X,circle2Y);
if (dist <= mainModule.drawingConfig.circles.proximity){ //
var lineWeight = 10/dist;
var line = mainModule.s.line(circleX, circleY, circle2X, circle2Y).attr({stroke: '#a6a8ab', strokeWidth: '1px'});
}
if (dist <= 10) { //collision
circle.incline = mainModule.setIncline();
circle2.incline = mainModule.setIncline();
}
}
}
}
setTimeout(function(){ mainModule.update(); },10);
},
distance: function(circleX,circleY,circle2X,circle2Y){
var distX = circle2X - circleX;
var distY = circle2Y - circleY;
distX = distX*distX;
distY = distY*distY;
return Math.sqrt(distX + distY);
},
move: function(circle,curX,curY){
if (curX > this.drawingConfig.canvas.width || curX < 0) {
circle.incline.incX = -circle.incline.incX;
}
if (curY > this.drawingConfig.canvas.height || curY < 0) {
circle.incline.incY = -circle.incline.incY;
}
curX = curX + circle.incline.incX;
curY = curY + circle.incline.incY;
if (curX > this.drawingConfig.canvas.width) {
curX = this.drawingConfig.canvas.width;
circle.incline = this.setIncline();
} else if (curX < 0) {
curX = 0;
circle.incline = this.setIncline();
}
if (curY > this.drawingConfig.canvas.height) {
curY = this.drawingConfig.canvas.height;
circle.incline = this.setIncline();
} else if (curY < 0) {
curY = 0;
circle.incline = this.setIncline();
}
circle.shape.attr({ cx: curX, cy: curY });
},
randomNumber: function(min,max){
return Math.floor(Math.random()*(max-min+1)+min);
},
getBounds: function(shape){
shapeBox = shape.node.getBoundingClientRect();
}
}
mainModule.init();
Related
I made this red line in JavaScript that goes to closest target (balloon 1 to 3) to the player but I need to make it so that it moves like a laser starting from player position into the target position. I thought about multiple ways of implementing this with no luck.
function Tick() {
// Erase the sprite from its current location.
eraseSprite();
for (var i = 0; i < lasers.length; i++) {
lasers[i].x += lasers[i].direction.x * laserSpeed;
lasers[i].y += lasers[i].direction.y * laserSpeed;
//Hit detection here
}
function detectCharClosest() {
var ballon1char = {
x: balloon1X,
y: balloon1Y
};
var ballon2char = {
x: balloon2X,
y: balloon2Y
};
var ballon3char = {
x: balloon3X,
y: balloon3Y,
};
ballon1char.distanceFromPlayer = Math.sqrt((CharX - balloon1X) ** 2 + (CharY - balloon1Y) ** 2);
ballon2char.distanceFromPlayer = Math.sqrt((CharX - balloon2X) ** 2 + (CharY - balloon2Y) ** 2);
ballon3char.distanceFromPlayer = Math.sqrt((CharX - balloon3X) ** 2 + (CharY - balloon3Y) ** 2);
var minDistance = Math.min(
ballon1char.distanceFromPlayer,
ballon2char.distanceFromPlayer,
ballon3char.distanceFromPlayer);
console.log(ballon1char);
console.log(ballon2char);
console.log(ballon3char);
for (let i = 0; i < 3; i++) {
if (minDistance == ballon1char.distanceFromPlayer)
return ballon1char
if (minDistance == ballon2char.distanceFromPlayer)
return ballon2char
if (minDistance == ballon3char.distanceFromPlayer)
return ballon3char
}
}
function loadComplete() {
console.log("Load is complete.");
canvas = document.getElementById("theCanvas");
ctx = canvas.getContext("2d");
myInterval = self.setInterval(function () { Tick() }, INTERVAL);
myInterval = self.setInterval(function () { laserTicker(detectCharClosest()) }, 2000);
function laserTicker(balloon) {
//gets the closest ballon to go to
laserDo(balloon);
}
function laserDo(balloon) {
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "#F44336"; // "red";
ctx.moveTo(CharX + 16, CharY + 16);
ctx.lineTo(balloon.x, balloon.y);
// lasers.push({x: })
ctx.stroke();
}
I didn't put all of my code here so If something doesn't make sense please tell me. I'm still new to JavaScript and learning it. One way I thought I could make this work was by taking the distance between the player and the target and dividing it by the speed on the x and y axis then changing having it start from the player position and keeps on adding up on both axis until it reaches the target. That didn't work out though. If you have any suggestions then please tell me.
Thanks
I'm trying to convert one of the Paper.js library examples (http://paperjs.org/examples/smoothing/) from PaperScript to Javascript. Following the documentation, I have
Made the scope global
Installed the event handlers onFrame and onResize
Created a tool and installed the event handlers onMouseMove and onMouseDown
But the canvas is not shown. I only see a couple of small blue lines: AFAIK the problem lies in the view.onFrame() function, since commenting that out at least I can see the shape, but not interact with it. The JS console dosen't show any error. What is missing?
// Make the paper scope global, by injecting it into window
paper.install(window);
window.onload = function () {
// Setup directly from canvas id:
paper.setup('myCanvas');
// Create tool
tool = new Tool();
var width, height, center;
var points = 10;
var smooth = true;
var path = new Path();
var mousePos = view.center / 2;
var pathHeight = mousePos.y;
path.fillColor = 'black';
initializePath();
function initializePath() {
center = view.center;
width = view.size.width;
height = view.size.height / 2;
path.segments = [];
path.add(view.bounds.bottomLeft);
for (var i = 1; i < points; i++) {
var point = new Point(width / points * i, center.y);
path.add(point);
}
path.add(view.bounds.bottomRight);
path.fullySelected = true;
}
view.onFrame = function (event) {
pathHeight += (center.y - mousePos.y - pathHeight) / 10;
for (var i = 1; i < points; i++) {
var sinSeed = event.count + (i + i % 10) * 100;
var sinHeight = Math.sin(sinSeed / 200) * pathHeight;
var yPos = Math.sin(sinSeed / 100) * sinHeight + height;
path.segments[i].point.y = yPos;
}
if (smooth)
path.smooth({ type: 'continuous' });
}
tool.onMouseMove = function (event) {
mousePos = event.point;
}
tool.onMouseDown = function (event) {
smooth = !smooth;
if (!smooth) {
// If smooth has been turned off, we need to reset
// the handles of the path:
for (var i = 0, l = path.segments.length; i < l; i++) {
var segment = path.segments[i];
segment.handleIn = segment.handleOut = null;
}
}
}
// Reposition the path whenever the window is resized:
view.onResize = function (event) {
initializePath();
}
}
To try it: https://jsfiddle.net/1rtkbp9s/
Found the solution (credits to Stefan Krüger of the Paper.js Google Group):
var mousePos = view.center / 2;
Should have been:
var mousePos = view.center.divide(2);
The fact is that Math functions should be used instead of operators for Point and Size object... and I didn't realize that view.center IS a Point object: http://paperjs.org/reference/view/#center
I'm trying to build an animated graph with paper.js that can react to different input. So I want to smoothly animate one point vertically to a different point.
I've looked at different examples and the closest ones to mine is this one:
paper.tool.onMouseDown = function(event) {
x = event.event.offsetX;
y = event.event.offsetY;
paper.view.attach('frame', moveSeg);
}
var x;
var y;
function moveSeg(event) {
event.count = 1;
if(event.count <= 100) {
myPath.firstSegment.point._x += (x / 100);
myPath.firstSegment.point._y += (y / 100);
for (var i = 0; i < points - 1; i++) {
var segment = myPath.segments[i];
var nextSegment = segment.next;
var vector = new paper.Point(segment.point.x - nextSegment.point.x,segment.point.y - nextSegment.point.y);
vector.length = length;
nextSegment.point = new paper.Point(segment.point.x - vector.x,segment.point.y - vector.y);
}
myPath.smooth();
}
}
This Code animates one Point to the click position, but I couldn't change it to my needs.
What I need is:
var aim = [120, 100];
var target = aim;
// how many frames does it take to reach a target
var steps = 200;
// Segment I want to move
myPath.segments[3].point.x
And then I dont know how to write the loop that will produce a smooth animation.
example of the graph:
I worked out the answer. The following steps in paperscript:
Generate Path
Set aim for the point
OnFrame Event that does the moving (eased)
for further animations just change the currentAim variable.
var myPath = new Path({
segments: [[0,100],[50,100],[100,100]]});
// styling
myPath.strokeColor = '#c4c4c4'; // red
myPath.strokeWidth = 8;
myPath.strokeJoin = 'round';
myPath.smooth();
// where the middle dot should go
var currentAim = [100,100];
// Speed
var steps = 10;
//Animation
function onFrame(event) {
dX1 = (currentAim[0] - myPath.segments[1].point.x )/steps;
dY1 = (currentAim[1] - myPath.segments[1].point.y )/steps;
myPath.segments[1].point.x += dX1;
myPath.segments[1].point.y += dY1;
}
I'm writing a simple game in javascript and I'm wondering what the best way to handle collisions between the player and the world objects.
<script>
var isJumping = false;
var isFalling = false;
var w = 1;
var recwidth = 400;
var recheight = 400;
var xpos = 50;
var ypos = 279;
window.onload = function() {
var FPS = 30;
var ground = new myObject();
setInterval(function() {
clear();
draw();
ground.draw(0, 325);
ground.draw(125,325)
}, 1000/FPS);
};
function myObject(){
this.draw = function drawground(groundx, groundy){
var canvas = document.getElementById('canvas')
var context = canvas.getContext('2d');
//context.fillRect(xpos,ypos,100,100);
var img=new Image()
img.src="ground.png"
img.onload = function() {
context.drawImage(img,groundx,groundy)}
}
};
function jump()
{
var t=.1;
isJumping=true;
var jumpint= setInterval(function() {
yup = 12*t-(5*t*t);
ypos= ypos - yup;
t = t + .1
if(yup < 0)
{
isJumping = false;
isFalling = true;
clearInterval(jumpint);
jumpint = 0;
fall();
return;
}
}, 20);
}
function fall()
{
t=.10
var fallint= setInterval(function() {
ydown = (5*t*t);
ypos= ypos + ydown;
t = t + .1
if(ypos > 275)
{
isFalling == false;
clearInterval(fallint);
fallint = 0;
return;
}
}, 20);
}
function changex(x){
xpos = xpos + (x);
//clear();
//draw();
}
function changey(y){
ypos = ypos + (y);
//clear();
//draw();
}
function draw(){
var canvas = document.getElementById('canvas')
var context = canvas.getContext('2d');
var img=new Image()
img.src="character.png"
img.onload = function() {
context.drawImage(img,xpos,ypos)}
}
function clear(){
var canvas = document.getElementById('canvas')
var context = canvas.getContext('2d');
context.clearRect(0,0, canvas.width, canvas.height);
}
document.onkeydown = function(event) {
var keyCode;
if(event == null)
{
keyCode = window.event.keyCode;
}
else
{
keyCode = event.keyCode;
}
switch(keyCode)
{
// left
case 37:
//left
changex(-5);
break;
// up
case 38:
// action when pressing up key
jump();
break;
// right
case 39:
// action when pressing right key
changex(5);
break;
// down
case 40:
// action when pressing down key
changey(5);
break;
default:
break;
}
}
</script>
So, as you can see I'm creating two objects so far, and the player stops falling at any arbitrary point. I feel collisions at this stage wont be too difficult, but once I start adding more I feel it's going to get more difficult. I'm not going to be using the instance of the object with the same image for each instance of the object, so at some point I'm going to change the myobject function to be able to accept the image as a parameter, and then checking for collisions will be a bit more tricky. I also plan on making this into a side scroller, so once one end the map is hit it changes into the next area, which is going to cause performance issues. If I'm checking for collisions on every single object in the entire game every interval I imagine things are going to get slow. What is going to be the best way to limit the number of collisions checked? Obviously, if the object isn't on screen there is no need to check it, but is there a way to limit that. I'm thinking of making an array for every frame of the game, and filling that array with it's objects. Then, only check the array the of the frame the player is currently in. Is this feasible or still going to cause too many issues? Any help is greatly appreciated.
If you want pixel perfect collisions, I have some plain javascript code that worked for me with canvas2d rendering context.
function collide(sprite, sprite2, minOpacity=1) {
// Rectangular bounding box collision
if (sprite.x < sprite2.x + sprite2.width && sprite.x + sprite.width > sprite2.x && sprite.y < sprite2.y + sprite2.height && sprite.y + sprite.height > sprite2.y) {
// Finds the x and width of the overlapping area
var overlapX = (this.rect.x > other.rect.x) ? [this.rect.x, (other.rect.x + other.rect.width) - this.rect.x + 1] : [other.rect.x, (this.rect.x + this.rect.width) - other.rect.x + 1];
// Finds the y and height of the overlapping area
var overlapY = (this.rect.y + this.rect.height > other.rect.y + other.rect.height) ? [this.rect.y, (other.rect.y + other.rect.height) - this.rect.y + 1] : [other.rect.y, (this.rect.y + this.rect.height) - other.rect.y + 1];
// Creates a canvas to draw sprite.image to
var spriteImageCanvas = new OffscreenCanvas(overlapX[0] + overlapX[1], overlapY[0] + overlapY[1]);
var spriteImageCanvasContext = spriteImageCanvas.getContext("2d");
// Draws sprite.image to spriteImageCanvasContext
spriteImageCanvasContext.drawImage(this.image, sprite.x, sprite.y, sprite.width, sprite.height);
// Creates a canvas to draw sprite2.image to
var sprite2ImageCanvas = new OffscreenCanvas(overlapX[0] + overlapX[1], overlapY[0] + overlapY[1]);
var sprite2ImageCanvasContext = otherImageCanvas.getContext("2d");
// Draws sprite2.image to sprite2ImageCanvasContext
sprite2ImageCanvasContext.drawImage(sprite2.image, sprite2.x, sprite2.y, sprite2.width, sprite2.height);
// Loops through the x coordinates in the overlapping area
for (var x = overlapX[0]; x <= overlapX[0] + overlapX[1]; x++) {
// Loops through the y coordinates in the overlapping area
for (var y = overlapY[0]; y <= overlapY[0] + overlapY[1]; y++) {
if (/* Checks if the pixel at [x, y] in the sprite image has an opacity over minOpacity input */ thisImageCanvasContext.getImageData(x, y, 1, 1).data[3] >= minOpacity && /* Checks if the pixel at [x, y] in the sprite2 image has an opacity over minOpacity input */ otherImageCanvasContext.getImageData(x, y, 1, 1).data[3] >= minOpacity) {
return true;
};
};
};
};
}
Or if you just want rectangular collision, use the first if statement in the function.
I have a demo page where I am looking at the performance of around 300 points that an object should snap to when being dragged.
http://jsfiddle.net/digiguru/rVFje/
I have optimized it by loading the bounds of each "snap" point into 4 arrays when the drag is started.
var circleT = [];
var circleR = [];
var circleB = [];
var circleL = [];
var start = function (event) {
this.ox = this.attr("cx");
this.oy = this.attr("cy");
var threshold = 15;
for (var myCircle in circles) {
circleT[myCircle] = circles[myCircle].attr("cy") - threshold;
circleR[myCircle] = circles[myCircle].attr("cx") + threshold;
circleB[myCircle] = circles[myCircle].attr("cy") + threshold;
circleL[myCircle] = circles[myCircle].attr("cx") - threshold;
}
circle.animate({ r: 20,fill: "#319F40", "stroke-width": 1 }, 200);
},
Then in the move event we use the following to calculate the dragged object from each of the snap points...
move = function (mx, my) {
var inrange = false;
var mouseCX = this.ox + mx;
var mouseCY = this.oy + my;
var lockX = 0;
var lockY = 0;
for (var myCircle in circles) {
if ((circleT[myCircle] < mouseCY
&& circleB[myCircle] > mouseCY )
&& (circleR[myCircle] > mouseCX
&& circleL[myCircle] < mouseCX )) {
inrange = true;
lockX = circles[myCircle].attr("cx");
lockY = circles[myCircle].attr("cy");
}
}
if (inrange) {
this.attr({
cx: lockX ,
cy: lockY
});
} else {
this.attr({
cx: mouseCX ,
cy: mouseCY
});
}
},
Genrally performance, is good, but you can notice frames dropping on slightly older IE browsers (IE8 and below). Is there a ninja way to improve performance at all? Perhaps I can improve the 4 if statements? Would using another javascript library like processing JS yeild better results?
Currently you consider every circle on every iteration. You could improve performance by considering fewer circles using a technique similar to this.
http://en.wikipedia.org/wiki/Quadtree
Basically, create bounding boxes for collections of circles. If the circle you are dragging is out of bounds then don't consider any circle within the bounding box. I hope this helps.