Is it possible to make an object orbit around another object that goes from behind and then to the front?
I've seen it being done with rotation animations that do a full 360 around the perimeter, but was wondering if it was possible to do it at an angle.
I couldn't find any resources that could do this, so I've included an image example of what I want to accomplish. The red line would be an object orbiting the blue circle.
Thanks so much - I really appreciate the help!
I figured I'd just write up a solution using the <canvas>
var x, y, scale, state, // Variables we'll use later.
canvas = document.getElementById("canvas"), // Get the canvas,
ctx = canvas.getContext("2d"), // And it's context.
counter = 0, // Counter to increment for the sin / cos functions.
width = 350, // Canvas width.
height = 200, // Canvas height.
centerX = width / 2, // X-axis center position.
centerY = height / 2, // Y-axis center position.
orbit = { // Settings for the orbiting planet:
width: 150, // Orbit width,
height: 50, // Orbit height,
size: 10 // Orbiting planet's size.
};
canvas.width = width; // Set the width and height of the canvas.
canvas.height = height;
function update(){
state = counter / 75; // Decrease the speed of the planet for a nice smooth animation.
x = centerX + Math.sin(state) * orbit.width; // Orbiting planet x position.
y = centerY + Math.cos(state) * orbit.height; // Orbiting planet y position.
scale = (Math.cos(state) + 2) * orbit.size; // Orbiting planet size.
ctx.clearRect(0, 0, width, height); // Clear the canvas
// If the orbiting planet is before the center one, draw the center one first.
(y > centerY) && drawPlanet();
drawPlanet("#f00", x, y, scale); // Draw the orbiting planet.
(y <= centerY) && drawPlanet();
counter++;
}
// Draw a planet. Without parameters, this will draw a black planet at the center.
function drawPlanet(color, x, y, size){
ctx.fillStyle = color || "#000";
ctx.beginPath();
ctx.arc(x || centerX,
y || centerY,
size || 50,
0,
Math.PI * 2);
ctx.fill();
}
// Execute `update` every 10 ms.
setInterval(update, 10);
<canvas id="canvas"></canvas>
If you want to change the roation direction of the orbiting planet, just replace:
x = centerX + Math.sin(state) * orbit.width;
y = centerY + Math.cos(state) * orbit.height;
With:
x = centerX + Math.cos(state) * orbit.width;
y = centerY + Math.sin(state) * orbit.height;
// ^ Those got switched.
The speed of the orbit can be changed by modifying the 75 in:
state = counter / 75;
Related
Let's say we're dynamically drawing a fractal on a canvas. Since we don't know how big the fractal is going to be, at some point we'd need to scale (zoom out) the canvas to fit our fractal in there.
How do we do that? How to scale it:
Properly, so that it perfectly fits the drawing we have, and
So that the coordinates stay the same, and our fractal calculation doesn't need to use the scale value (meaning, return x, not return x * scale, if possible)
What if the fractal grows in all directions and we have negative values?
See the tiny example below.
var $canvas = document.querySelector('canvas'),
ctx = $canvas.getContext('2d'),
lastX = 0,
lastY = 0;
drawLoop();
function drawLoop() {
var newX = lastX + 30,
newY = lastY + 30;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(newX, newY);
ctx.stroke();
lastX = newX;
lastY = newY;
setTimeout(drawLoop, 1000);
}
<canvas width="100" height="100" style="border: 1px solid #ccc;"></canvas>
You can scale, translate, and rotate the drawing coordinates via the canvas transform.
If you have the min and max coordinates of your drawing
Example:
const min = {x: 100, y: 200};
const max = {x: 10009, y: 10000};
You can make it fit the canvas as follows
const width = canvas.width;
const height = canvas.height;
// get a scale to best fit the canvas
const scale = Math.min(width / (max.x - min.x), height / (max.y - min.y));
// get a origin so that the drawing is centered on the canvas
const top = (height - (max.y - min.y)) / 2;
const left = (width - (max.x - min.x)) / 2;
// set the transform so that you can draw to the canvas
ctx.setTransform(scale, 0, 0, scale, left, top);
// draw something
ctx.strokeRect(min.x, min.y, max.x - min.x, max.y - min.y);
If you do not know the size of the drawing area at the start then you will need to save drawing coordinates as you go. When the min and max change you then recalculate the transform, clear the canvas and redraw. There is no other way if you do not know the size at the beginning.
This is what I have right now:
I want to make it like this:
The blue line as you can see passes through the center(400,400). The start of the blue line is not fixed, it moves according to data that the user enter.
How do I add this blue line and make it pass through the center?
Sorry for my bad English, working on that :)
Halfon
Use mathematics.
Let the center co-ordinates be (Cx, Cy), which in your case are (400, 400). Let the user's co-ordinates be (Ux, Uy).
The equation of the line passing through the center from (Ux, Uy) would be given by the equation:
y - Cy = slope * (x - Cx), where slope = (Cy - Uy) / (Cx - Ux).
Now, to draw the line from Ux to some x co-ordinate, say Px, simply use the equation to calculate Py = Slope * (Px - Cx) + Cy, then draw the line from (Ux, Uy) to (Px, Py).
context.beginPath();
context.moveTo(Ux, Uy);
context.lineTo(Px, Py);
context.stroke();
Simply draw the line through the center point and extend its length. It's completely unclear what the length of the line is because you failed to provide any code, so I'm just doubling it in the example.
To calculate the end points, just subtract the starting x/y coordinates from the doubled x/y coordinates of the central point.
I wrote you a dynamic example which takes the mouse coordinates as a starting position, but the same principle applies if you only have a single static point from the user input. Try it here:
var canvas = document.getElementById('c');
var context = canvas.getContext('2d');
var centerPoint;
function setSize() {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
centerPoint = {x: canvas.width / 2, y: canvas.height / 2};
}
canvas.addEventListener('mousemove', function(e) {
canvas.width = canvas.width;
context.beginPath();
context.moveTo(e.offsetX, e.offsetY);
context.lineTo(centerPoint.x * 2 - e.offsetX, centerPoint.y * 2 - e.offsetY);
context.lineWidth = 3;
context.strokeStyle = '#0000ff';
context.stroke();
})
canvas.addEventListener('mousedown', function(e) {
centerPoint = {x: e.offsetX, y: e.offsetY};
canvas.width = canvas.width;
})
window.addEventListener('resize', setSize);
setSize()
canvas {width: 100%;height: 100%;position: absolute;}
body {margin: 0}
p {position: absolute; pointer-events: none}
<canvas id="c"></canvas>
<p>Click anywhere to set the center point (dead center by default)</p>
I currently have a bunch of circles that i fill with a image inside a "box", they are bouncing and colliding. Now I want them to rotate.
I have tried to get this to work but I only seem to be able to rotate the entire canvas, I only want the balls to rotate.
This is my render-function:
var img = new Image;
img.onload = function() {
render();
console.log("asdsad");
}
img.src = "xxx.png";
function render() {
var ball;
for (var i = 0; i <balls.length; i++) {
ball = balls[i];
ball.x = ball.nextx;
ball.y = ball.nexty;
context.drawImage(img, ball.x - (img.width/2), ball.y - (img.height/2));
}
}
GOAL: Get these balls to rotate.
EDIT: I have tried something like this, obviously I'm doing something wrong.
context.rotate(Math.PI/180); // Should this be another value? Either way, entire canvas rotates.
context.drawImage(img, ball.x - (img.width/2), ball.y - (img.height/2));
//context.rotate(-Math.PI/180)
context.restore();
You are almost there with your code. You are only forgetting the actual angle:
If you want something easy to wrap the head around and use angles in degrees you can do:
context.rotate(angle * Math.PI / 180);
a trick here is to pre-calculate Math.PI / 180 and use that to calculate:
var deg2rad = Math.PI / 180;
...
context.rotate(angle * deg2rad);
Then draw the balls.
You probably already know but just in case (as it isn't in the example you provided) - To use restore you must first use save:
context.save();
context.rotate(angle * Math.PI / 180);
context.drawImage(img, ball.x - (img.width * 0.5), ball.y - (img.height * 0.5));
context.restore();
Update:
I made an example with a single ball. Just apply the principle for many.
ONLINE DEMO HERE
The main loop goes like this:
/// pre-setup done (see demo)
function loop() {
/// change direction if at some edge
if (x < r || x > ez.width - r) {
dx = -dx;
angleDelta = -angleDelta; /// dummy bounce
}
if (y < r || y > ez.height - r) {
dy = -dy;
}
/// clear previous ball
ctx.clearRect(x - r - 2, y - r - 2, img.width + 4, img.height + 4);
/// move and rotate
x += dx;
y += dy;
angle += angleDelta;
/// save context
ctx.save();
/// move to ball's center position
ctx.translate(x, y);
/// rotate at balls center position
ctx.rotate(angle);
/// draw ball offset so center is center of image
ctx.drawImage(img, -r, -r);
/// reset translation and rotation
ctx.restore();
///again
requestAnimationFrame(loop);
}
You said you have tried:
context.rotate(Math.PI/180);
As known in mathematics PI = 180 degree and if you do PI / 180 = 1 degree it will do nothing except a little.
So if you want to rotate the canvas 90 degree you have to write:
context.rotate(Math.PI/2);
if you want 180 degree you have to:
context.rotate(Math.PI);
and carry on.
with some calculation you will be able to rotate it to any degree you want.
if this does not work you can try this alternative
this function take the arguments and buld the image for you in a simple way that you can understand it might help someone out their.
this function help you
function drawImg(img, pX, pY, oX, oY, w, h, rot , context2D) {
context2D.save();
context2D.translate(pX+oX, pY+oY);
context2D.rotate(rot * Math.PI / 180);
context2D.drawImage(img, 0, 0, w, h, -(oX), -(oY), w, h);
context2D.restore();
}
Summary:
img: the image object
pX: the x position of the image
pY: the y position of the image
oX: how far across the image that the origin is
oY: how far down the image that the origin is
w: width of the image
h: height of the image
rot: angle of rotation
context2D: the context that have been build from the canvas
Here is my question : I have an animation, that builds are circle. See : http://jsfiddle.net/2TUnE/
JavaScript:
var currentEndAngle = 0
var currentStartAngle = 0;
var currentColor = 'black';
setInterval(draw, 50);
function draw() { /***************/
var can = document.getElementById('canvas1'); // GET LE CANVAS
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var startAngle = currentStartAngle * Math.PI;
var endAngle = (currentEndAngle) * Math.PI;
currentEndAngle = currentEndAngle + 0.01;
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
context.lineWidth = 15;
// line color
context.strokeStyle = currentColor;
context.stroke();
/************************************************/
}
When the circle is completely drawn, I would like it to start erasing, the same way it was created (so slowly removes the black). Once the whole circle is erased, i would create the black circle again, creating some kind of "waiting / loading" effect.
What i tried to do, is check if the currentEndAngle is 2 (so the circle is complete), and then move the startAngle, but it didn't work.
Any idea?
Thanks!
EDIT : Forgot to say, the animation is gonna be over an image, so it has to be "transparent" and not white
Look whats up in this JSFiddle: http://jsfiddle.net/fNTsA/
This method is basically your code, only we use a modulo to control state. Checking if the radius is 2 is only half-right, to toggle drawing white or drawing black you should do half the radius modulo 2. The first time around you have floor(0..2/2) % 2 == 0, the second you have floor(2..4/2) % 2 == 1, and so on.
Also because the line is antialiased, it helps to have the start angle overwrite what's been drawn already, otherwise you get extra white lines you probably don't want. For the same reason, when drawing the white circle, you should draw a slightly thicker line (smaller radius, thicker line). Otherwise the antialiasing leaves behind some schmutz -- a faint outline of the erased circle.
I put the radius and width into globals which you'd put at the top:
var lineRadius = 75;
var lineWidth = 15;
And likewise this is my modulo thing, pretty standard:
currentStartAngle = currentEndAngle - 0.01;
currentEndAngle = currentEndAngle + 0.01;
if (Math.floor(currentStartAngle / 2) % 2) {
currentColor = "white";
radius = lineRadius - 1;
width = lineWidth + 3;
} else {
currentColor = "black";
radius = lineRadius;
width = lineWidth;
}
Fun challenge! Try the following (updated fiddle here). I've tried to include plenty of comments to show my thinking.
// Moved these to global scope as you don't want to re-declare
// them in your draw method each time your animation loop runs
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
// Use objects to hold our draw and erase props
var drawProps = {
startAngle: 0,
speed: 2,
color: 'black',
counterClockwise: false,
globalCompositeOperation: context.globalCompositeOperation,
lineWidth: 15
};
var eraseProps = {
startAngle: 360,
speed: -2,
color: 'white',
counterClockwise: true,
globalCompositeOperation: "destination-out",
lineWidth: 17 // artefacts appear unless we increase lineWidth for erase
};
// Let's work in degrees as they're easier for humans to understand
var degrees = 0;
var props = drawProps;
// start the animation loop
setInterval(draw, 50);
function draw() { /***************/
degrees += props.speed;
context.beginPath();
context.arc(
x,
y,
radius,
getRadians(props.startAngle),
getRadians(degrees),
props.counterClockwise
);
context.lineWidth = props.lineWidth;
context.strokeStyle = props.color;
context.stroke();
// Start erasing when we hit 360 degrees
if (degrees >= 360) {
context.closePath();
props = eraseProps;
context.globalCompositeOperation = props.globalCompositeOperation;
}
// Start drawing again when we get back to 0 degrees
if (degrees <= 0) {
canvas.width = canvas.width; // Clear the canvas for better performance (I think)
context.closePath();
props = drawProps;
context.globalCompositeOperation = props.globalCompositeOperation;
}
/************************************************/
}
// Helper method to convert degrees to radians
function getRadians(degrees) {
return degrees * (Math.PI / 180);
}
I have a quadratic curve that I use to create a slice of a piechart. The slice is situated in an axis of x and y, with the center point at (0,0). The radius is variable at radiusX and radiusY. This slice travels 90 degrees.
I need to split this slice into 3 seperate slices (each having 30 degree angle) and have them match whatever curve their parent slice had.
The following images show possible examples of the slice. Black circles adjust the size/shape of the slice:
Here is the function I've made but it's just not working correctly:
//globalPosX and globalPosY equal whatever position each of the two large black circles have repectively.
var canvas = document.getElementById('CV_slices');
var context = canvas.getContext('2d');
var cenX = canvas.width/2;
var cenY = canvas.height/2;
var blackDotX = globalPosX - cenX;
var blackDotY = cenY - globalPosY;
var endX;
var endY;
var controlX;
var controlY;
//set first slice
var startCoOrds = {
x: cenX ,
y: globalPosY
};
for (i=1; i < 4; i++) {
//make end(x,y) of previous slice the start(x,y) for the next slice.
endX = startCoOrds.x - (blackDotX*Math.sin(30));
endY = startCoOrds.y + (blackDotY*Math.cos(30));
//set position of control point using position of start/end positions (at the moment only adjustibng using +10 -10 at end)
controlX = ((endX - startCoOrds.x) /2) + (startCoOrds.x) + 10;
controlY = ((endY - startCoOrds.y) / 2) + (startCoOrds.y) - 10;
// draw slice
context.save();
context.beginPath();
context.moveTo(cenX, cenY);
context.lineTo(startCoOrds.x, startCoOrds.y);
context.quadraticCurveTo(controlX, controlY, endX, endY);
context.lineTo(cenX, cenY);
//make end(x,y) of previous slice the start(x,y) for the next slice
startCoOrds.x = endX;
startCoOrds.y = endY;
context.closePath();
context.globalAlpha = 0.1;
context.fillStyle = "#333333";
context.fill();
context.lineWidth = 2;
context.strokeStyle = "#ffffff";
context.stroke();
context.restore();
}
Use the closest "blackDot" as the radius of a circle,
using the circle, divide your quadrant into 3 (see wiki)
then scale the points by a ratio of the distance between 0,0 and the "blackDot"'s
In effect your arc is a quadrant of a circle that has been scaled in the x or y axis.