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;
}
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
Please see attached image. you can see first slide with canvas masking to animate like second slide.
Well I have no clue what language you wanted, so Javascript it is, but the principles are the same for whatever SDK or language you use.
Easy to do. Use requestAnimationFrame to make it smooth. Create a set of shapes that you can animate each from and just draw them to the canvas context as normal using moveTo, lineTo or which ever path function you want. Instead of calling fill or stroke call ctx.clip() then draw the image and it will only be displayed in the clipping areas.
Below is a rather kludged example but it will give you the basics, you can improve it to meet your needs.
var canvas = document.getElementById("canV"); // get the canvas
var ctx = canvas.getContext("2d"); // get the context
var blobs = []; // array to hold blobs
var width = canvas.width;
var height = canvas.height;
var blobPCount = 16; // how bumpy the blobs are
var wobbla = 0; // adds a bit of a wobble
var growSpeed = 2; // grow speed 0.1 very slow 10 mega fast
var cludgeFactor = 0.25; // used to work out when its all done
var cludgeFactorA = 1-cludgeFactor; // its a bit of a cludge hence the name
// a wiki commons image
var imageURL = "https://upload.wikimedia.org/wikipedia/commons/e/ee/Baby_Cuttlefish2_%285589806913%29.jpg";
var image = new Image(); // create the image
image.src = imageURL; // load it
var done = false; // flag is true when done
function addBlob(){ // adds a blob
var b = {};
b.x = Math.random()*width; // find a random pos for it
b.y = Math.random()*height;
b.points = []; // create a set of pointy in a circle that will grow outward
for(var i = 0; i < Math.PI*2;i+= (Math.PI*2)/blobPCount){
var p = {};
// mess up the perfection a little
var dir= (i+((Math.PI*2)/(blobPCount*3))*(Math.random()-0.5))*(1+2/blobPCount);
p.dx = Math.cos(dir); // the delta x and y
p.dy = Math.sin(dir);
p.x = p.dx * 5; // the starting size
p.y = p.dy * 5;
p.dx *= growSpeed; // set the speed
p.dy *= growSpeed;
b.points.push(p); // add the point
}
blobs.push(b); // and the blob
}
function growBlobs(){ // grows the blob
var i;
for(i = 0; i < blobs.length; i++){ // for each blob
var b = blobs[i];
var pc = b.points.length;
for(var j = 0; j < pc; j++){ // grow the points
var p = b.points[j];
p.x += p.dx+p.dx*Math.random()*0.2; // move the point with a liitle random
p.y += p.dy+p.dy*Math.random()*0.2;
}
}
}
// creates the clipping mask
function createClipMask(){
var i;
ctx.beginPath(); // begin a path
wobbla += 0.2; // add some wobbla
var inside = false; // the flag to test done
for(i = 0; i < blobs.length; i++){ // for each blob
var b = blobs[i];
var pc = b.points.length; // get len
for(var j = 0; j < pc-1; j++){ // do eacj point
var p = b.points[j];
var x = b.x+p.x + Math.sin(wobbla+i+j*0.2)*10; // get a point
var y = b.y+p.y + Math.cos(wobbla+i+j*0.2)*10;
if(j === 0){
ctx.moveTo(x,y); // move to the first point
}else{
j ++; // all other points as a second order bexier
p = b.points[j];
var x1 = b.x +p.x*0.75 + Math.sin(wobbla+i+j*0.2)*10; // add the wobble
var y1 = b.y +p.y*0.75 + Math.cos(wobbla+i+j*0.2)*10;
ctx.quadraticCurveTo(x,y,x1,y1); // create tke bezier path
// test if the points are inside the screen by cludge factor
if(!inside && x > width*cludgeFactor && x < width*cludgeFactorA &&
y > height*cludgeFactor && y < height*cludgeFactorA){
inside = true;
}
}
}
ctx.closePath(); // done with this blob close the path
}
// all blobs done so set the clip
ctx.clip();
if(!inside){ // if no points inside the cludge area the we are done
done = true;
}
}
// make a semi random number of blobs
var numberBlobs = Math.ceil(Math.random()^5) +3;
for(var i = 0; i < numberBlobs; i++){
addBlob();
}
// do the update
function update(){
if(done){ // if done draw the image
ctx.drawImage(image,0,0,width,height);
return; // return stops the rendering
}
ctx.fillStyle = "white"; // draw the white
ctx.fillRect(0,0,width,height);
ctx.save(); // save the current ctx state
if(image.complete){ // has the image loaded
growBlobs(); // yes so grow blobs
createClipMask(); // create the clip
ctx.drawImage(image,0,0,width,height); //draw the clipped image
}
ctx.restore(); // dont need the clip anymore so restore the ctx state
window.requestAnimationFrame (update); //request a new anim frame
}
update(); // start it all going
.canC {
width:500px;
height:400px;
}
<canvas class="canC" id="canV" width=500 height=400></canvas>
Also I had to guess what you wanted so hope I guessed right. Any question please do ask.
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.