I'm working on a fish sprite animation. Currently when I add a piece of food then the sprite animation will move forward to eat it. But I am not able to make it swim smoothly toward the food. Sometimes the sprite animation will move up and down till it reach the food.
Here is how the sprite animation is moving towards the food:
Fish.prototype.chaseFood = function(index) {
if (this.xPos > foodArray[index].x + foodWidth) {
this.speedX = -1 * Math.abs(this.speedX);
} else if (this.xPos < foodArray[index].x) {
this.speedX = Math.abs(this.speedX);
}
if (this.yPos > foodArray[index].y + foodHeight) {
this.speedY = -1 * Math.abs(this.speedY);
} else if (this.yPos < foodArray[index].y) {
this.speedY = Math.abs(this.speedY);
}
};
Is there anyway to make it swim more smoothly towards the food and not moving up and down towards it.
I'd calculate angle between the fish and the food and make the fish move towards that angle.
Here are some helper functions to get you going:
function distanceBetweenPoints(a, b)
{
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function angleBetweenPoints(a, b)
{
return Math.atan2(b.y-a.y,b.x-a.x)*180/Math.PI;
}
Usage:
var angle = angleBetweenPoints({ x: fish.x, y: fish.y }, { x: food.x, y: food.y });
Then you can do something like:
fish.x += Math.sin(angle * Math.PI / 180) * 10;
fish.y += Math.cos(angle * Math.PI / 180) * 10;
I think you want your sprite to move in a direct line from one set of coordinates (it's location) to another set of coordinates (the food's location).
So if your if your x coordinates are a difference of 6 pixels and your y coordinates are a difference of 4, we want to keep the ratio of 6 to 4 when we increase our x and y offset for our happy little fish.
In this case, we could change the program to move x by 1 pixel at a time and then move y by 4/6 pixel at a time and thus go on a path straight towards the goal. If we were to increase the y by a full pixel, it would arrive directly below the target and then go straight up. This would be an indirect path and less realistic looking.
Actually I think it would arrive to the left of the goal and then go directly right in the old version if you use the 1 to 1 ratio, but I think you knew what I meant.
I'll try to adapt an example I have to your code:
int fishSpeed = 2;
float xOffset = fishSpeed;
float yOffset = fishSpeed;
float xDis = abs(this.xPos-(foodArray[index].x + foodWidth));
float yDis = abs(this.yPos-(foodArray[index].y + foodWidth));
//each offset changes depending on how far it is from goal
xOffset = xOffset * (xDis / (xDis + yDis));
yOffset = yOffset * (yDis / (xDis + yDis));
if(this.xPos > foodArray[index].x) this.xPos+=xOffset;
if(this.yPos > foodArray[index].y) this.yPos+=yOffset;
if(this.xPos < foodArray[index].x) this.xPos-=xOffset;
if(this.yPos < foodArray[index].y) this.yPos-=yOffset;
Sorry I couldn't make it work using your example. I don't know if it will help, but here is a complete .htm file that has a bunny wabbit that is controlled by the mouse and one that is chased directly by another wabbit. The wabbit will go directly for its goal. The file needs processing.js in the same directory.
<!DOCTYPE html>
<html>
<body bgcolor="lightblue" style="margin:0;">
<center>
<script src="processing.js"></script>
<script type="application/processing">
void setup(){
size(screen.width*.9,screen.height*.9);
blueWabbit = new wabbit(600,600,105);
pinkWabbit = new wabbit(100,100,100);
blueWabbit.blue = 255;
blueWabbit.red = 128;
blueWabbit.green = 128;
wabbitSpeed = 5;
}
void draw() {
float xOffset = wabbitSpeed;
float yOffset = wabbitSpeed;
float xDis = abs(pinkWabbit.xpos-blueWabbit.xpos);
float yDis = abs(pinkWabbit.ypos-blueWabbit.ypos);
xOffset = xOffset * (xDis / (xDis + yDis));
yOffset = yOffset * (yDis / (xDis + yDis));
if(pinkWabbit.xpos > blueWabbit.xpos) blueWabbit.xpos+=xOffset;
if(pinkWabbit.ypos > blueWabbit.ypos) blueWabbit.ypos+=yOffset;
if(pinkWabbit.xpos < blueWabbit.xpos) blueWabbit.xpos-=xOffset;
if(pinkWabbit.ypos < blueWabbit.ypos) blueWabbit.ypos-=yOffset;
if (xDis+yDis<wabbitSpeed){
for(int a =0;a<20; a++)babyWabbit();
}
else background(0,0,0,0);
pinkWabbit.show();
blueWabbit.show();
pinkWabbit.xpos = mouseX;
pinkWabbit.ypos = mouseY;
/*
fill(0);
text("blue x = "+(int)blueWabbit.xpos,10,10);
text("blue y = "+(int)blueWabbit.ypos,10,20);
text("pink x = "+pinkWabbit.xpos,10,30);
text("pink y = "+pinkWabbit.ypos,10,40);
text("xOffset = "+xOffset,10,50);
text("yOffset = "+yOffset,10,60);
text("xDis = "+(int)xDis,10,70);
text("yDis = "+(int)yDis,10,80);
*/
}
class wabbit {
//declare the properties that will be used as variables for the object
float xpos, ypos, diameter;
int red, blue, green;
//define the parameters for the creation of a new class
wabbit (float x, float y, float wSize) {
xpos = x;
ypos = y;
diameter = wSize;
//radius = .5 * diameter;
//make it pink if user did not define colors
if (!(0>red>256)) red = 255;
if (!(0>green>256))green = 200;
if (!(0>blue>256)) blue = 200;
}
void show() {
noStroke();
for(a = diameter; a > 0; a-=5){
fill(red-a,green-a,blue-a);
//belly and head
ellipse(xpos, ypos, a, a);
ellipse(xpos, ypos-diameter*.7, a*.7,a*.7);
//feets
ellipse(xpos-.2*diameter, ypos+diameter*.4, a*.4,a*.4);
ellipse(xpos+.2*diameter, ypos+diameter*.4, a*.4,a*.4);
//ears
ellipse(xpos-.2*diameter, ypos-diameter, a*.2,a*.8);
ellipse(xpos+.2*diameter, ypos-diameter, a*.2,a*.8);
}
}
}
void babyWabbit(){
noStroke();
var red=random(1,255);
var green=random(1,255);
var blue=random(1,255);
var xpos=random(1,width);
var ypos=random(1,height);
var diameter = random(20,80);
for(var a = diameter; a > 0; a-=5){
fill(red-a,green-a,blue-a);
//belly and head
ellipse(xpos, ypos, a, a);
ellipse(xpos, ypos-diameter*.7, a*.7,a*.7);
//feets
ellipse(xpos-.2*diameter, ypos+diameter*.4, a*.4,a*.4);
ellipse(xpos+.2*diameter, ypos+diameter*.4, a*.4,a*.4);
//ears
ellipse(xpos-.2*diameter, ypos-diameter, a*.2,a*.8);
ellipse(xpos+.2*diameter, ypos-diameter, a*.2,a*.8);
}
}
</script><canvas></canvas>
</body>
</html>
Related
In this example we use math functions to create a blurred copy of an image. The way we will blur the image is by scrambling pixels that are near each other.
We begin by creating a blank image and writing the loop to let us color each pixel in the image. For each pixel we will do one of two things: half the time, we will simply copy the pixel from the old picture into the new picture without changing anything. The other half of the time we will find a pixel nearby and copy that one instead.
Now we must figure out how to find a "nearby" pixel. We will define some value for how far away the new pixel will be (we used 10 pixels) and then we write a function that will give a (x,y) point that is a random amount between 0 and 10 pixels away in each direction. Before we use the new (x,y) point, we must check to be sure it is still a valid pixel position in the image. For example, we may be at a pixel that is on the very top of the image. Our random point generator tells us to go up by 3 pixels, but since we are on the top of the image (y = 0) we cannot very well go up by three pixels (y would be -3)! If the random number is too big (larger than the dimension -1) or too small (less than 0) then we will just use the closest number that is valid.
Once we have a valid pixel that is some amount away we use its red, green, and blue values as the new pixel's values.
function nearby(image, x, y) {
var newX = x + Math.random() * 10 - 5;
if (newX > image1.getWidth()) {
newX = newX - 5;
}
if (newX < 0) {
newX = newX + 5;
} else {
newX = newX;
}
var newY = y + Math.random() * 10 - 5;
if (newY > image1.getHeight()) {
newY = newY - 5;
}
if (newY < 0) {
newY = newY + 5;
} else {
newY = newY;
}
return image.getPixel(newX, newY);
}
function blur(image) {
for (var pixel of image.values()) {
x = pixel.getX();
y = pixel.getY();
var orgPixel = image.getPixel(x, y);
if (Math.random() < 0.5) {
var other = nearby(image, x, y);
output.setPixel(x, y, other);
} else {
output.setPixel(x, y, orgPixel);
}
}
return output;
}
var image1 = new SimpleImage("duvall.jpg");
var output = new SimpleImage(image1.getWidth(), image1.getHeight());
blur(image1);
print(output);
----i used one of the pictures in my file but any picture should do.
For reference, I'm talking about the dark-gray space in the upper left of Discord's Login Page. For anyone who can't access that link, here's a screenshot:
It has a number of effects that are really cool, the dots and (darker shadows) move with the mouse, but I'm more interested in the "wobbly edge" effect, and to a lesser extent the "fast wobble/scale in" on page load (scaling in the canvas on load would give a similar, if not "cheaper" effect).
Unfortunately, I can't produce much in the way of a MCVE, because I'm not really sure where to start. I tried digging through Discord's assets, but I'm not familiar enough to Webpack to be able to determine what's going on.
Everything I've been able to dig up on "animated wave/wobble" is CSS powered SVG or clip-path borders, I'd like to produce something a bit more organic.
Very interesting problem. I've scaled the blob down so it is visible in the preview below.
Here is a codepen as well at a larger size.
const SCALE = 0.25;
const TWO_PI = Math.PI * 2;
const HALF_PI = Math.PI / 2;
const canvas = document.createElement("canvas");
const c = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
class Blob {
constructor() {
this.wobbleIncrement = 0;
// use this to change the size of the blob
this.radius = 500;
// think of this as detail level
// number of conections in the `bezierSkin`
this.segments = 12;
this.step = HALF_PI / this.segments;
this.anchors = [];
this.radii = [];
this.thetaOff = [];
const bumpRadius = 100;
const halfBumpRadius = bumpRadius / 2;
for (let i = 0; i < this.segments + 2; i++) {
this.anchors.push(0, 0);
this.radii.push(Math.random() * bumpRadius - halfBumpRadius);
this.thetaOff.push(Math.random() * TWO_PI);
}
this.theta = 0;
this.thetaRamp = 0;
this.thetaRampDest = 12;
this.rampDamp = 25;
}
update() {
this.thetaRamp += (this.thetaRampDest - this.thetaRamp) / this.rampDamp;
this.theta += 0.03;
this.anchors = [0, this.radius];
for (let i = 0; i <= this.segments + 2; i++) {
const sine = Math.sin(this.thetaOff[i] + this.theta + this.thetaRamp);
const rad = this.radius + this.radii[i] * sine;
const theta = this.step * i;
const x = rad * Math.sin(theta);
const y = rad * Math.cos(theta);
this.anchors.push(x, y);
}
c.save();
c.translate(-10, -10);
c.scale(SCALE, SCALE);
c.fillStyle = "blue";
c.beginPath();
c.moveTo(0, 0);
bezierSkin(this.anchors, false);
c.lineTo(0, 0);
c.fill();
c.restore();
}
}
const blob = new Blob();
function loop() {
c.clearRect(0, 0, canvas.width, canvas.height);
blob.update();
window.requestAnimationFrame(loop);
}
loop();
// array of xy coords, closed boolean
function bezierSkin(bez, closed = true) {
const avg = calcAvgs(bez);
const leng = bez.length;
if (closed) {
c.moveTo(avg[0], avg[1]);
for (let i = 2; i < leng; i += 2) {
let n = i + 1;
c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
}
c.quadraticCurveTo(bez[0], bez[1], avg[0], avg[1]);
} else {
c.moveTo(bez[0], bez[1]);
c.lineTo(avg[0], avg[1]);
for (let i = 2; i < leng - 2; i += 2) {
let n = i + 1;
c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
}
c.lineTo(bez[leng - 2], bez[leng - 1]);
}
}
// create anchor points by averaging the control points
function calcAvgs(p) {
const avg = [];
const leng = p.length;
let prev;
for (let i = 2; i < leng; i++) {
prev = i - 2;
avg.push((p[prev] + p[i]) / 2);
}
// close
avg.push((p[0] + p[leng - 2]) / 2, (p[1] + p[leng - 1]) / 2);
return avg;
}
There are lots of things going on here. In order to create this effect you need a good working knowledge of how quadratic bezier curves are defined. Once you have that, there is an old trick that I've used many many times over the years. To generate smooth linked quadratic bezier curves, define a list of points and calculate their averages. Then use the points as control points and the new averaged points as anchor points. See the bezierSkin and calcAvgs functions.
With the ability to draw smooth bezier curves, the rest is about positioning the points in an arc and then animating them. For this we use a little math:
x = radius * sin(theta)
y = radius * cos(theta)
That converts polar to cartesian coordinates. Where theta is the angle on the circumference of a circle [0 - 2pi].
As for the animation, there is a good deal more going on here - I'll see if I have some more time this weekend to update the answer with more details and info, but hopefully this will be helpful.
The animation runs on a canvas and it is a simple bezier curve animation.
For organic feel, you should look at perlin noise, that was introduced when developing original Tron video FX.
You can find a good guide to understand perlin noise here.
In the example I've used https://github.com/josephg/noisejs
var c = $('canvas').get(0).getContext('2d');
var simplex = new SimplexNoise();
var t = 0;
function init() {
window.requestAnimationFrame(draw);
}
function draw() {
c.clearRect(0, 0, 600, 300);
c.strokeStyle="blue";
c.moveTo(100,100);
c.lineTo(300,100);
c.stroke();
// Draw a Bézier curve by using the same line cooridinates.
c.beginPath();
c.lineWidth="3";
c.strokeStyle="black";
c.moveTo(100,100);
c.bezierCurveTo((simplex.noise2D(t,t)+1)*200,(simplex.noise2D(t,t)+1)*200,(simplex.noise2D(t,t)+1)*200,0,300,100);
c.stroke();
// draw reference points
c.fillRect(100-5,100-5,10,10);
c.fillRect(200-5,200-5,10,10);
c.fillRect(200-5,0-5,10,10);
c.fillRect(300-5,100-5,10,10);
t+=0.001;
window.requestAnimationFrame(draw);
}
init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.js"></script>
<canvas width="600" height="300"></canvas>
Note: further investigation on Discord source code, I've pointed out that's is using https://www.npm.red/~epistemex libraries. Epistemex NPM packages are still online, while GitHub repos and profile does not exists anymore.
Note 2: Another approach could be relying on physics libraries like this demo, but it can be an overkill, if you just need a single effect.
I am a novice who is working on a simple JavaScript web page that using the processing.js plugin to generated moving graphics. I have started to work on collision detection and handling. I examine each graphic and change it's direction if it gets too close to any of the other graphics. The program is buggy because the graphics eventually have simultaneous collisions on more than one other graphic. This then creates an interesting (but undesired) effect of getting the graphics "stuck" inside one another.
I could work on a function to detect multiple collisions and handle them in a way that stops the graphical objects from getting inside each other. However, I feel like their is a more fundamental flaw in my approach. I don't want to create a band aide solution when their may be a more structural flaw in my collision handling approach.
Should I just write a function that handles the bug of multiple simultaneous collisions or should I go back to the drawing board with program structure?
<!DOCTYPE html>
<html>
<body bgcolor="lightblue" style="margin:0;">
<center>
<script src="processing.js"></script>
<script type="application/processing">
int orbNumber = 30;
void setup(){
h=[];
float t;
size(600,600);
frameRate(60);
//assign starting variables for floating objects
for(int a = 0; a < orbNumber; a++){
h[a] = new orb(0, 200, 1, 64);
h[a].xpos = random(1,width);
h[a].ypos = a * 100;
h[a].red = random(128,255);
h[a].blue = random(128,255);
h[a].green = random(128,255);
}
}
void draw() {
background(200,200,200,0);
//call the update method from the orb class to calculate postion and display graphic
for(int a = 0; a < orbNumber; a++)h[a].update();
//number the graphics
textFont("Arial", 22);
fill(255,255,255,80);
for(int a = 0; a < orbNumber; a++)text(a,h[a].xpos-6,h[a].ypos+6);
//for each graphic, check distance for each other graphic and change
//movement direction upon collisions
for(int a = 0; a < orbNumber; a++)for(int b = a+1; b < (orbNumber); b++){
if(circleCollision(h[a],h[b])){
t=h[a].xOffset;
h[a].xOffset=h[b].xOffset;
h[b].xOffset = t;
t=h[a].yOffset;
h[a].yOffset=h[b].yOffset;
h[b].yOffset = t;
h[a].update;
h[b].update;
h[a].imperfectEdge()
h[b].imperfectEdge()
}
}
}
function circleCollision(circle1,circle2){
var x1 = circle1.xpos;
var x2 = circle2.xpos;
var y1 = circle1.ypos;
var y2 = circle2.ypos;
var distance = sqrt( (x1-x2) * (x1-x2) + (y1-y2) * (y1-y2) );
var widths = circle1.radius + circle2.radius;
var t = false;
if(distance<widths)t = true;
//text("distance = " + distance,20,20);
return t;
}
class orb {
float xpos, ypos, speed, diameter, xOffset, yOffset, radius;
int red, blue, green;
orb (float x, float y, float s, float size) {
xpos = x;
ypos = y;
diameter = size;
xOffset = s;
yOffset = s;
radius = .5 * diameter;
}
void update() {
ypos += yOffset;
xpos += xOffset;
if(yOffset<0&&ypos<0+radius){
yOffset=-yOffset;
imperfectEdge();
}
if(yOffset>0&&ypos>height-radius){
yOffset=-yOffset;
imperfectEdge();
}
if(xOffset<0&&xpos<0+radius){
xOffset=-xOffset;
imperfectEdge();
}
if(xOffset>0&&xpos>width-radius){
xOffset=-xOffset;
imperfectEdge();
}
noStroke();
for(a = diameter; a > 0; a-=5){
fill(red-a,blue-a,green-a);
ellipse(xpos, ypos, a, a);
}
}
void imperfectEdge(){
xOffset=xOffset+random(-.1,.1);
yOffset=yOffset+random(-.1,.1);
}
}
</script><canvas></canvas>
</body>
</html>
I'd like to make an app where a ball moves at the angle your mouse hits it. So if you swipe your mouse down from top left quadrant at 30 degrees (I guess that would be 180-30 = angle of 150 degrees), it will knock the ball that way. I've been drawing my lines as such:
function drawAngles () {
var d = 50; //start line at (10, 20), move 50px away at angle of 30 degrees
var angle = 80 * Math.PI/180;
ctx.beginPath();
ctx.moveTo(300,0);
ctx.lineTo(300,600); //x, y
ctx.moveTo(0,300);
ctx.lineTo(600,300);
ctx.moveTo(300,300);
ctx.lineTo(600,100);
ctx.arc(300,300,300,0,2*Math.PI);
ctx.stroke();
}
But this doesn't give me an idea of what the angles are.
Then I move the ball at that angle (for now, I'm animating it without mouse interaction)
function getAngleX (x) {
return x = x + (50 * Math.cos(Math.PI/6));
}
function getAngleY(y) {
return y = y + (50 * Math.sin(Math.PI/6));
}
//just animate this box to move at an angle from center down at 30 degrees
$(".anotherBox").mouseenter(function(e) {
pos = $(this).position();
box2X = pos.left;
box2Y = pos.top;
$(this).animate({
//top : $(window).outerHeight(),
top : getAngleY(box2Y)+"px",
left: getAngleX(box2X)+"px",
}, "slow");
});
So how can I draw a line at a specified angle? I'd like to make sure my ball is following along that path.
You can use different approaches to achieve this but if you want to use the same basis to move and draw then this approach may suit well.
First we use a function to get step values for x and y based on the angle (in radians):
function getSteps(angle) {
var cos = Math.cos(angle),
sin = Math.sin(angle);
return {
x: cos -sin,
y: sin + cos
}
}
Then using these steps values we can scale them to get an end point, or scale them gradually to animate an object along the line. A simple loop could look like this (just for example):
function loop() {
var x = i * step.x, // scale using i
y = i * step.y;
ctx.fillRect(200 + x, 200 + y, 2, 2); // add to origin start point 200, 200
i += 1; // increase i
if (i < length) requestAnimationFrame(loop);
}
Live demo
If you just want to draw a line at a certain angle you can do the following instead:
function lineAtAngle(x1, y1, length, angle) {
ctx.moveTo(x1, y1);
ctx.lineTo(x1 + length * Math.cos(angle), y1 + length * Math.sin(angle));
}
then stroke it.
Hope this helps!
If i guess right, i think you want the mouse act like a baseball bat, and you need to measure the current mouse angle, that is to store previous mouse position and do some math.
You have also to keep track if you allready handled current collision, to avoid the ball being 'sticky' and follow the mouse.
http://jsfiddle.net/gamealchemist/z3U8g/
var ctx = cv.getContext('2d');
var ball = {
x:200, y:200,
r : 30,
vx : 0.4, vy:0.4
}
// when mouse moved that distance, ball speed norm will be 1
var speedNorm = 10;
var collisionOnGoing = false;
function collide() {
var dist = sq(ball.x - mx) + sq (ball.y-my);
// too far from ball ?
if (dist > sq(ball.r)) {
collisionOnGoing = false;
return;
}
// return if collision allready handled
if (collisionOnGoing) return;
var mouseDist =Math.sqrt( sq(mx-lastmx) + sq(my-lastmy) );
// no collision if mouse too slow
if (mouseDist<speedNorm/5) return;
// launch the ball in current direction
// with a speed relative to the mouse speed.
var mouseAngle = Math.atan2(my-lastmy, mx-lastmx);
ball.vx= (mouseDist / speedNorm ) * Math.cos(mouseAngle);
ball.vy= (mouseDist / speedNorm ) * Math.sin(mouseAngle);
collisionOnGoing = true;
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0,0,400,400);
// collide ball with mouse
collide();
// draw ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, 6.3);
ctx.fill();
ctx.closePath();
// move
ball.x+=ball.vx;
ball.y+=ball.vy;
// collide with screen
if (ball.x>400) ball.vx=-Math.abs(ball.vx);
if (ball.x<0) ball.vx=Math.abs(ball.vx);
if (ball.y>400) ball.vy=-Math.abs(ball.vy);
if (ball.y<0) ball.vy=Math.abs(ball.vy);
}
animate();
// --- Mouse handling ---
addEventListener('mousemove', mouseMove);
var mx=-1, my=-1, lastmx=-1, lastmy=-1;
var cvRect = cv.getBoundingClientRect();
var cvLeft = cvRect.left;
var cvTop = cvRect.top;
function mouseMove(e) {
lastmx = mx; lastmy=my;
mx=e.clientX - cvLeft;
my=e.clientY - cvTop;
}
function sq(x) { return x*x; }
I'm developing a user-interface for positioning an image on a google map.
I started from : http://overlay-tiler.googlecode.com/svn/trunk/upload.html which is pretty close to what I want.
But instead of 3 contact points I want a rotate tool, a scale tool and a translate tool (the later exists).
I tried to add a rotate tool but it doesn't work as I expected :
I put a dot on the left bottom corner that control the rotation (around the center of the image). The mouse drag the control dot and I calculate the 3 others points.
My code is based on the mover object but I changed the onMouseMove function :
overlaytiler.Rotater.prototype.rotateDot_ = function(dot, theta, origin) {
dot.x = ((dot.x - origin.x) * Math.cos(theta) - (dot.y - origin.y) * Math.sin(theta)) + origin.x;
dot.y = ((dot.x - origin.x) * Math.sin(theta) + (dot.y - origin.y) * Math.cos(theta)) + origin.y;
dot.render();
};
overlaytiler.Rotater.prototype.onMouseMove_ = function(e) {
var dots = this.controlDots_;
var center = overlaytiler.Rotater.prototype.getCenter_(dots);
// Diagonal length
var r = Math.sqrt(Math.pow(this.x - center.x, 2) + Math.pow(this.y - center.y, 2));
var old = {
x: this.x,
y: this.y
};
// Real position
var newPos = {
x: this.x + e.clientX - this.cx,
y: this.y + e.clientY - this.cy
}
var newR = Math.sqrt(Math.pow(newPos.x - center.x, 2) + Math.pow(newPos.y - center.y, 2));
var theta = - Math.acos((2 * r * r - (Math.pow(newPos.x - old.x, 2) + Math.pow(newPos.y - old.y, 2))) / (2 * r * r));
// Fixed distance position
this.x = (newPos.x - center.x) * (r / newR) + center.x;
this.y = (newPos.y - center.y) * (r / newR) + center.y;
dots[1].x = center.x + (center.x - this.x);
dots[1].y = center.y + (center.y - this.y);
dots[1].render();
overlaytiler.Rotater.prototype.rotateDot_(dots[2], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[0], theta, center);
// Render
this.render();
this.cx = e.clientX;
this.cy = e.clientY;
};
Unfortunately there is a problem with precision and angle sign.
http://jsbin.com/iQEbIzo/4/
After a few rotations the image is highly distorted and rotation is supported only in one direction.
I wonder how I can achieve a great precision and without any distortion.
Maybe my approach is useless here (try to move the corners at the right coordinates), I tried to rotate the image with the canvas but my attempts were unsuccessful.
Edit : Full working version : http://jsbin.com/iQEbIzo/7/
Here is my version of it. #efux and #Ben answers are far more complete and well designed however the maps don't scale in/out when you zoom in/out. Overlays very likely need to do this since they are used to put a "second map" or photograph over the existing map.
Here is the JSFiddle: http://jsfiddle.net/adelriosantiago/3tzzwmsx/4/
The code that does the drawing is the following:
DebugOverlay.prototype.draw = function() {
var overlayProjection = this.getProjection();
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
var div = this.div_;
div.style.left = sw.x + 'px';
div.style.top = ne.y + 'px';
div.style.width = (ne.x - sw.x) + 'px';
div.style.height = (sw.y - ne.y) + 'px';
div.style.transform = 'rotate(' + rot + 'deg)';
};
For sure this code could be implemented on efux and Ben code if needed but I haven't tried yet.
Note that the box marker does not updates its position when the rotation marker moves...
rotation is supported only in one direction
This is due to how you calculate the angle between two vectors.
It always gives you the same vector no matter if the mouse is right of the dot or not. I've found a solution in a german math board (unfortunately I cant access the site without using the cache of Google : cached version).
Note that in this example the angle α is on both sides the same and not as you would expect -α in the second one. To find out if the vector a is always on "the same side" of vector b you can use this formula.
ax*by - ay*bx
This is either positive or negative. You you simply can change the sign of the angle to α * -1.
I modified some parts of your code.
overlaytiler.Rotater.prototype.rotateDot_ = function(dot, theta, origin) {
// translate to origin
dot.x -= origin.x ;
dot.y -= origin.y ;
// perform rotation
newPos = {
x: dot.x*Math.cos(theta) - dot.y*Math.sin(theta),
y: dot.x*Math.sin(theta) + dot.y*Math.cos(theta)
} ;
dot.x = newPos.x ;
dot.y = newPos.y ;
// translate back to center
dot.x += origin.x ;
dot.y += origin.y ;
dot.render();
};
If you want to know, how I rotate the points please reference to this site and this one.
overlaytiler.Rotater.prototype.onMouseMove_ = function(e) {
var dots = this.controlDots_;
var center = overlaytiler.Rotater.prototype.getCenter_(dots);
// get the location of the canvas relative to the screen
var rect = new Array() ;
rect[0] = dots[0].canvas_.getBoundingClientRect() ;
rect[1] = dots[1].canvas_.getBoundingClientRect() ;
rect[2] = dots[2].canvas_.getBoundingClientRect() ;
// calculate the relative center of the image
var relCenter = {
x: (rect[0].left + rect[2].left) / 2,
y: (rect[0].top + rect[2].top) / 2
} ;
// calculate a vector from the center to the bottom left of the image
dotCorner = {
x: rect[1].left - (rect[1].left - relCenter.x) * 2 - relCenter.x,
y: rect[1].top - (rect[1].top - relCenter.y) * 2 - relCenter.y
} ;
// calculate a vector from the center to the mouse position
mousePos = {
x: e.clientX - relCenter.x,
y: e.clientY - relCenter.y
} ;
// calculate the angle between the two vector
theta = calculateAngle(dotCorner, mousePos) ;
// is the mouse-vector left of the dot-vector -> refer to the german math board
if(dotCorner.y*mousePos.x - dotCorner.x*mousePos.y > 0) {
theta *= -1 ;
}
// calculate new position of the dots and render them
overlaytiler.Rotater.prototype.rotateDot_(dots[2], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[1], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[0], theta, center);
// Render
this.render();
this.cx = e.clientX;
this.cy = e.clientY;
};
You can see that I wrote some function for vector calculations (just to make the code more readable):
function calculateScalarProduct(v1,v2)
{
return (v1.x * v2.x + v1.y * v2.y) ;
}
function calculateLength(v1)
{
return (Math.sqrt(v1.x*v1.x + v1.y*v1.y)) ;
}
function calculateAngle(v1, v2)
{
return (Math.acos(calculateScalarProduct(v1,v2) / (calculateLength(v1)*calculateLength(v2)))) ;
}
This is my working solution. Comment if you don't understand something, so I can make my answer more comprehensive.
Working example: JSBin
Wow, this was a tough one.