I am trying to figure out a nice way to make a sine wave flow naturally along a javascript path. I made something like this:
Which captures some of the intent but it's very forced and unnatural, especially around the change direction. I also would love to accommodate for higher slope, but not sure if that ives a more natural effect or not.
Any thoughts on how I might be able to accomplish this?
The intent was:
1) Take a set of points
2) Break into equal segments
3) Adjust the actual line's position by the difference of the sin coords and the actual line coords.
This gives a pretty weak display though, and I'd like to create something that was more natural and flowing as if to capture the flow of a sine wave travelling along a path.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = window.innerWidth;
var ch = c.height = window.innerHeight;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var w = cw;
var h = ch * 0.3;
var amplitude = h;
var frequency = 0.01;
var phi = 0;
var frames = 0;
var stopped = true;
ctx.lineWidth = .4;
var offset = 100;
var points = interpolateLineRange( [ [0, 0], [ 95, 58], [84, 158], [350, 300], [540, 190] ], 20);
points = interpolateLineRange(points, 100);
ctx.moveTo(0, 0);
var distance_traveled = 0;
var current_slope = 0;
for (var ii in points) {
if (ii == 0) {
continue;
}
distance_traveled += dist(points[ii - 1], points[ii]);
current_slope = slope(points[ii - 1], points[ii]);
var newY = Math.sin(distance_traveled * .07) * 45 + points[ii][1];
var diff = newY - points[ii][1];
if (points[ii][1] > points[ii - 1][1]) {
ctx.lineTo(points[ii][0] - diff, newY);
} else {
ctx.lineTo(points[ii][0] + diff, newY);
}
}
ctx.stroke();
ctx.moveTo(0, 0);
for (var ii in points) {
ctx.lineTo(points[ii][0], points[ii][1]);
}
ctx.strokeStyle = 'red';
ctx.stroke();
The problem isn't really "drawing sine waves along a path": that part is actually trivial. Take your path section, express it in terms of a distance or time variable, and then draw the sines (or anything else) as an offset function:
for t=0; t<distance; t+=fraction of distance:
point = path.get(t)
normal = path.normal(t)
strength = sin(t)
if t=0:
ctx.moveTo(point + strength * normal)
else:
ctx.lineTo(point + strength * normal)
Easy enough, let's implement that: http://jsbin.com/nefemazovo/edit?js,output
Sure, it's a bit of code, but it's hardly complicated: just a class that models a polygonal path that tracks its length as we add points to it, and a draw function that draws the polygon, as well as some offset function, by sampling the polygon at regular intervals and computing the normal at each point.
The real question is: how are you going to deal with overlaps in your offset data? For instance, from the example above:
There's a pretty obvious area here where we're going to have to do ... something:
So what do we do? Turns out: no one knows, that's really up to you. For instance, you could draw "uneven" sines so that you always end up with a node at the end points of your polygonal sections. Might work, but you might also still have overlap if there's a small enough angle between consecutive segments. Plus your sines would be uneven, so would that look good? Ehh... up to you. Or, you could dampen the offset strength to zero at the polygon transition, and then ramp it back up to 100%, but will that look good? No idea, that is your call. You could also use interpolation so that the sine waves "blend" at the transition. Will that look good? Again, no idea, still up to you. You could even replace the offending section of polygon with something like a quadratic or cubic curve, so you always have smooth transitions along which sine offsets will "just work", but will that look good? ...you get the idea =)
The part of this question we can answer isn't super interesting, and the part that's interesting we unfortunately cannot answer for you...
We can give advice, though: I don't know what your polygon represents, but "curves" almost always work better as spines (almost, because curves can have discontinuities as well, which is the very thing you want to avoid), so if you can construct curves instead, probably worth it. However, that won't solve the problem of weird overlaps when your angles are too small:
You're still left with problems that can only be solved with "executive decisions" more than textbook "in this situation, do this: ..." solutions.
Related
I'm currently working on a JavaScript project which involves 3D point rotation. Using simple trigonometry, I have sketched my own 3D point rotation algorithm, but I have to deal with a huge amount of data (+300 000 points) and my function slows down the runtime substantially (the FPS rate drops from 60 to 12).
I'm looking for another 3D point rotation ALGORITHM which...
rotates points around origin by X, Y and Z axes' angles (PITCH, YAW and ROLL)
has a quite good efficiency (don't worry about this too much, it will always be faster than mine)
is written in JavaScript, C-like code or pseudo-code
Any help will be greatly appreciated :)
Context: 3D point cloud renderer (I want every point to be rotated)
A rotated vector can be described as a product of a rotation matrix with that vector. The German Wikipedia page on pitch, roll and yaw describes the rotation matrix for given Euler rotation angles.
With that information, the rotation of all points with the same angles can be written as JavaScript function, where the points array is global:
function rotate(pitch, roll, yaw) {
var cosa = Math.cos(yaw);
var sina = Math.sin(yaw);
var cosb = Math.cos(pitch);
var sinb = Math.sin(pitch);
var cosc = Math.cos(roll);
var sinc = Math.sin(roll);
var Axx = cosa*cosb;
var Axy = cosa*sinb*sinc - sina*cosc;
var Axz = cosa*sinb*cosc + sina*sinc;
var Ayx = sina*cosb;
var Ayy = sina*sinb*sinc + cosa*cosc;
var Ayz = sina*sinb*cosc - cosa*sinc;
var Azx = -sinb;
var Azy = cosb*sinc;
var Azz = cosb*cosc;
for (var i = 0; i < points.length; i++) {
var px = points[i].x;
var py = points[i].y;
var pz = points[i].z;
points[i].x = Axx*px + Axy*py + Axz*pz;
points[i].y = Ayx*px + Ayy*py + Ayz*pz;
points[i].z = Azx*px + Azy*py + Azz*pz;
}
}
Most of that is setting up the rotation matrix as described in the article. The last three lines inside the loop are the matrix multiplication. You have made a point of not wanting to get into matrices, but that's hardly intimidating, is it? Sooner or later you will encounter more matrices and you should be prepared to deal with them. The stuff you need – multiplication, mainly – is simple. The more complicated stuff like inverting matrices is not needed for your requirements.
Anyway, that performs reasonably fast for 300,000 points. I was able to rotate a point cloud of that size and render it on a 1000px × 1000px canvas in about 10ms.
From wikipedia:
If you multiply your points by each of these matrices they will be rotated by the amount you want.
For example, if I want to rotate point [1, 0, 0] by 90° around the z axis (in the xy plane), sin(90) = 1 and cos(90) = 0 so you get this:
| 0 -1 0 | |1| |0|
| 1 0 0 | * |0| = |1|
| 0 0 1 | |0| |0|
I have short code, that draws circles (orbits) dots (satellites) on these orbits. Satellites are moving around orbits. In fact code isn't mine, but I was asked to solve the problem.
According to profiler in chrome and firefox, function drawSatellite eats 50%-100% cpu and I would like to know why.
Canvas is same big as your window (1920x1080). There are around 160 orbits (increasing with time page is online).
This is drawSatellite:
OrbitBackground.prototype.drawSatellite = function(ctx, satellite) {
ctx.fillStyle = satellite.satellite.fill;
ctx.beginPath();
if (++satellite.satellite.angularPosition == 360)
satellite.satellite.angularPosition = 0;
// 1 FPS = 60 calls => 180 / 6 (6-times faster # 60 FPS) = 30
var radians = satellite.satellite.angularPosition * Math.PI / 30 / satellite.rps;
if (satellite.backward)
radians = -radians;
ctx.arc(
satellite.satellite.x + satellite.orbit.radius * Math.cos(radians),
satellite.satellite.y + satellite.orbit.radius * Math.sin(radians),
satellite.satellite.radius,
0,
Math.PI*2,
true
);
ctx.closePath();
ctx.fill();
};
Function that calls it:
OrbitBackground.prototype.drawFrame = function() {
if (this.running)
requestAnimationFrame(this.drawFrame.bind(this));
this.dynamicStageCtx.clearRect(0, 0, this.pageWidth, this.pageHeight);
for (var i=0; i < this.orbits.length; i++) {
this.drawSatellite(this.dynamicStageCtx, this.orbits[i]);
}
};
You're doing this:
Loop:
set fill style
begin path
make path
end path
fill
You would be much faster doing this:
set fill style (just once, before loop)
begin path (just one path, with loop-number of subpaths)
Loop:
moveTo (start of subpath)
make path
close path
fill (just once, after loop)
But that requires the fill style to be the same on every satellite. If there are only a few colors, you could try to bunch them by their colors.
Note also that calcluating cosine and sine are slow (all trig functions and square root calls are slow), and if you can avoid their use you will be better off.
The size (pixel count) of your canvas also matters. Consider making the canvas half-size or quarter-size (960x540 or 480x270) and scaling it up with CSS.
Might be a problem:
I don't see a ctx.beginPath before your ctx.arc command.
Without ctx.beginPath all your previous arcs are being redrawn along with your current arc.
A small optimization
Assign Math.PI*2 to a variable since it's used very often
var PI2=Math.PI*2;
How to eliminate the slowest part of your code ( Math.cos and Math.sin ).
Since your nodes are going in repeating orbits, you can precalculate all the untranslated [x,y] for a complete orbit.
var statellite.orbitTrig=[];
for(var i=0;i<360;i++){
var radians=PI2/360*i;
var x=satellite.orbit.radius * Math.cos(radians)
var y=satellite.orbit.radius * Math.sin(radians)
satellite.orbitTrig.push({x:x,y:y});
}
Then you can refer to the precalculated values in your animation loop.
var trig=satellite.orbitTrig[satellite.satellite.angularPosition];
var x=satellite.satellite.x + trig.x;
var y=satellite.satellite.y + trig.y;
ctx.beginPath();
ctx.arc(x,y,satellite.satellite.radius,0,PI2);
ctx.closePath();
ctx.fill();
I need to calculate the angle between 3 points. For this, I do the following:
Grab the 3 points (previous, current and next, it's within a loop)
Calculate the distance between the points with Pythagoras
Calculate the angle using Math.acos
This seems to work fine for shapes without angels of over 180 degrees, however if a shape has such an corner it calculates the short-side. Here's an illustration to show what I mean (the red values are wrong):
This is the code that does the calculations:
// Pythagoras for calculating distance between two points (2D)
pointDistance = function (p1x, p1y, p2x, p2y) {
return Math.sqrt((p1x - p2x)*(p1x - p2x) + (p1y - p2y)*(p1y - p2y));
};
// Get the distance between the previous, current and next points
// vprev, vcur and vnext are objects that look like this:
// { x:float, y:float, z:float }
lcn = pointDistance(vcur.x, vcur.z, vnext.x, vnext.z);
lnp = pointDistance(vnext.x, vnext.z, vprev.x, vprev.z);
lpc = pointDistance(vprev.x, vprev.z, vcur.x, vcur.z);
// Calculate and print the angle
Math.acos((lcn*lcn + lpc*lpc - lnp*lnp)/(2*lcn*lpc))*180/Math.PI
Is there something wrong in the code, did I forget to do something, or should it be done a completely different way?
HI there your math and calculations are perfect. Your running into the same problem most people do on calculators, which is orientation. What I would do is find out if the point lies to the left or right of the vector made by the first two points using this code, which I found from
Determine which side of a line a point lies
isLeft = function(ax,ay,bx,by,cx,cy){
return ((bx - ax)*(cy - ay) - (by - ay)*(cx - ax)) > 0;
}
Where ax and ay make up your first point bx by your second and cx cy your third.
if it is to the left just add 180 to your angle
I've got a working but not necessarily brief example of how this can work:
var point1x = 0, point1y = 0,
point2x = 10, point2y = 10,
point3x = 20, point3y = 10,
point4x = 10, point4y = 20;
var slope1 = Math.atan2(point2y-point1y,point2x-point1x)*180/Math.PI;
var slope2 = Math.atan2(point3y-point2y,point3x-point2x)*180/Math.PI;
var slope3 = Math.atan2(point4y-point3y,point4x-point3x)*180/Math.PI;
alert(slope1);
alert(slope2);
alert(slope3);
var Angle1 = slope1-slope2;
var Angle2 = slope2-slope3;
alert(180-Angle1);
alert(180-Angle2);
(see http://jsfiddle.net/ZUESt/1/)
To explain the multiple steps the slopeN variables are the slopes of the individual line segments. AngleN is the amount turned at each junction (ie point N+1). A positive angle is a right turn and a negative angle a left turn.
You can then subtract this angle from 180 to get the actual interior angle that you want.
It should be noted that this code can of course be compressed and that five lines are merely outputting variables to see what is going on. I'll let you worry about optimizing it for your own use with this being a proof of concept.
You need to check boundary conditions (apparently, if points are colinear) and apply the proper calculation to find the angle.
Also, a triangle can't have any (interior) angle greater than 180 degress. Sum of angle of triangle is 180 degrees.
I'm trying to use the following effect on a HTML5 game: http://somethinghitme.com/projects/metaballs/
But since its a game (as opposed to graphical demo) I have tighter FPS requirements, I need time to calculate the physics and the some other things and my biggest bottleneck is the code for the metaballs.
The following code is what I got after stripping the original code for performance, its not as pretty but it's enough for my purposes:
ParticleSpawner.prototype.metabilize = function(ctx) {
var imageData = this._tempCtx.getImageData(0,0,900,675),
pix = imageData.data;
this._tempCtx.putImageData(imageData,0,0);
for (var i = 0, n = pix.length; i <n; i += 4) {
if(pix[i+3]<210){
pix[i+3] = 0;
}
}
//ctx.clearRect(0,0,900,675);
//ctx.drawImage(this._tempCanvas,0,0);
ctx.putImageData(imageData, 0, 0);
}
I had another loop on my code and I managed to increase its performance by using the technique described on the following link http://www.fatagnus.com/unrolling-your-loop-for-better-performance-in-javascript/ but using the same on this actually decreases the performance (maybe I did it wrong?)
I also researched web workers to see if I could split the load (since the code runs for each pixel individually) but the example I found on this link http://blogs.msdn.com/b/eternalcoding/archive/2012/09/20/using-web-workers-to-improve-performance-of-image-manipulation.aspx actually runs slower when using web workers.
What else can I do? Is there a way to remove the branching from the loop? Another way to unroll it? Or is this the best I can do?
Edit:
This is some of the surrounding code:
ParticleSpawner.prototype.drawParticles = function(ctx) {
this._tempCtx.clearRect(0,0,900,675);
var iterations = Math.floor(this._particles.getNumChildren() / 8);
var leftover = this._particles.getNumChildren() % 8;
var i = 0;
if(leftover > 0) {
do {
this.process(i++);
} while(--leftover > 0);
}
do {
this.process(i++);
this.process(i++);
this.process(i++);
this.process(i++);
this.process(i++);
this.process(i++);
this.process(i++);
this.process(i++);
} while(--iterations > 0);
this.metabilize(ctx);
}
and the process method:
ParticleSpawner.prototype.process = function(i) {
if(!this._particles.getChildAt(i)) return;
var bx = this._particles.getChildAt(i).x;
var by = this._particles.getChildAt(i).y;
if(bx > 910 || bx < -10 || by > 685) {
this._particles.getChildAt(i).destroy();
return;
}
//this._tempCtx.drawImage(this._level._queue.getResult("particleGradient"),bx-20,by-20);
var grad = this._tempCtx.createRadialGradient(bx,by,1,bx,by,20);
this._tempCtx.beginPath();
var color = this._particles.getChildAt(i).color;
var c = "rgba("+color.r+","+color.g+","+color.b+",";
grad.addColorStop(0, c+'1.0)');
grad.addColorStop(0.6, c+'0.5)');
grad.addColorStop(1, c+'0)');
this._tempCtx.fillStyle = grad;
this._tempCtx.arc(bx, by, 20, 0, Math.PI*2);
this._tempCtx.fill();
};
As can be seen, I tried using images instead of gradient shapes, but the performance was worse, I also tried to use ctx.drawImage instead of putImageData, but it loses the alpha and is not faster. I can't think of an alternative to achieve the desired effect. The current code runs perfectly on Google Chrome, but Safari and Firefox are really slow. Is there anything else I can try? Should I just give up on those browsers?
Updated
Some techniques that can be applied
Here are some optimization techniques that can be applied to make this work more fluent in FF and Safari as well.
That being said: Chrome's canvas implementation is very good and much faster (at the moment) than the bone provided by Firefox and Safari. The new Opera uses the same engine as Chrome and is (about?) equally as fast as Chrome's.
For this to work fine cross-browser some compromises needs to be made and as always quality will suffer.
The techniques I try to demonstrate are:
Cache a single gradient that is used as meta ball basis
Cache everything if possible
Render in half resolution
Use drawImage() to update main canvas
Disable image smoothing
Use integer coordinates and sizes
Use requestAnimationFrame()
Use while loops as often as you can
Bottlenecks
There is a high cost in generating a gradient for each metaball. So when we cache this once and for all we will just by doing that notice a huge improvement in performance.
The other point is getImageData and putImageData and the fact that we need to use a high-level language to iterate over a low-level byte array. Fortunately the array is typed array so that helps a little but we won't be able to get much more out of it unless we sacrifice more quality.
When you need to squeeze everything you can the so-called micro-optimizations becomes vital (these has an undeserved bad reputation IMO).
From the impression of your post: You seem to be very close to have this working but from the provided code I cannot see what went wrong so-to-speak.
In any case - Here is an actual implementation of this (based on the code you refer to):
Fiddle demo
Pre-calculate variables in the initial steps - everything we can pre-calculate helps us later as we can use the value directly:
var ...,
// multiplicator for resolution (see comment below)
factor = 2,
width = 500,
height = 500,
// some dimension pre-calculations
widthF = width / factor,
heightF = height / factor,
// for the pixel alpha
threshold = 210,
thresholdQ = threshold * 0.25,
// for gradient (more for simply setting the resolution)
grad,
dia = 500 / factor,
radius = dia * 0.5,
...
We use a factor here to reduce the actual size and to scale the final render to on-screen canvas. For each 2 factor you save 4x pixels exponentially. I preset this to 2 in the demo and this works great with Chrome and good with Firefox. You might even be able to run factor of 1 (1:1 ratio) in both browsers on a better spec'ed machine than mine (Atom CPU).
Init the sizes of the various canvases:
// set sizes on canvases
canvas.width = width;
canvas.height = height;
// off-screen canvas
tmpCanvas.width = widthF;
tmpCanvas.height = heightF;
// gradient canvas
gCanvas.width = gCanvas.height = dia
Then generate a single instance of a gradient that will be cached for the other balls later. Worth to notice: I initially used only this to draw all the balls but later decided to cache each ball as an image (canvas) instead of drawing and scaling.
This has a memory penalty but increases the performance. If memory is of importance you can skip the caching of rendered balls in the loop that generates them and just drawImage the gradient canvas instead when you need to draw the balls.
Generate gradient:
var grad = gCtx.createRadialGradient(radius, radius, 1, radius, radius, radius);
grad.addColorStop(0, 'rgba(0,0,255,1)');
grad.addColorStop(1, 'rgba(0,0,255,0)');
gCtx.fillStyle = grad;
gCtx.arc(radius, radius, radius, 0, Math.PI * 2);
gCtx.fill();
Then in the loop that generates the various metaballs.
Cache the calculated and rendered metaball:
for (var i = 0; i < 50; i++) {
// all values are rounded to integer values
var x = Math.random() * width | 0,
y = Math.random() * height | 0,
vx = Math.round((Math.random() * 8) - 4),
vy = Math.round((Math.random() * 8) - 4),
size = Math.round((Math.floor(Math.random() * 200) + 200) / factor),
// cache this variant as canvas
c = document.createElement('canvas'),
cc = c.getContext('2d');
// scale and draw the metaball
c.width = c.height = size;
cc.drawImage(gCanvas, 0, 0, size, size);
points.push({
x: x,
y: y,
vx: vx,
vy: vy,
size: size,
maxX: widthF + size,
maxY: heightF + size,
ball: c // here we add the cached ball
});
}
Then we turn off interpolating for images that are being scaled - this gains even more speed.
Note that you can also use CSS in some browsers to do the same as here.
Disable image smoothing:
// disable image smoothing for sake of speed
ctx.webkitImageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.oImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false; // future...
Now the non-critical parts are done. The rest of the code utilizes these tweaks to perform better.
The main loop now looks like this:
function animate() {
var len = points.length,
point;
// clear the frame of off-sceen canvas
tmpCtx.clearRect(0, 0, width, height);
while(len--) {
point = points[len];
point.x += point.vx;
point.y += point.vy;
// the checks are now exclusive so only one of them is processed
if (point.x > point.maxX) {
point.x = -point.size;
} else if (point.x < -point.size) {
point.x = point.maxX;
}
if (point.y > point.maxY) {
point.y = -point.size;
} else if (point.y < -point.size) {
point.y = point.maxY;
}
// draw cached ball onto off-screen canvas
tmpCtx.drawImage(point.ball, point.x, point.y, point.size, point.size);
}
// trigger levels
metabalize();
// low-level loop
requestAnimationFrame(animate);
}
Using requestAnimationFrame squeezes a little more of the browser as it is intended to be more low-level and more efficient than just using a setTimeout.
The original code checked for both edges - this is not necessary as a ball can only cross one edge at the time (per axis).
The metabolize function is modified like this:
function metabalize(){
// cache what can be cached
var imageData = tmpCtx.getImageData(0 , 0, widthF, heightF),
pix = imageData.data,
i = pix.length - 1,
p;
// using a while loop here instead of for is beneficial
while(i > 0) {
p = pix[i];
if(p < threshold) {
pix[i] = p * 0.1667; // multiply is faster than div
if(p > thresholdQ){
pix[i] = 0;
}
}
i -= 4;
}
// put back data, clear frame and update scaled
tmpCtx.putImageData(imageData, 0, 0);
ctx.clearRect(0, 0, width, height);
ctx.drawImage(tmpCanvas, 0, 0, width, height);
}
Some micro-optimizations that actually helps in this context.
We cache the pixel value for alpha channel as we use it more than two times. Instead of diving on 6 we multiply with 0.1667 as multiplication is a tad faster.
We have already cached tresholdQ value (25% of threshold). Putting the cached value inside the function would give a little more speed.
Unfortunately as this method is based on the alpha channel we need to clear also the main canvas. This has a (relatively) huge penalty in this context. The optimal would be to be able to use solid colors which you could "blit" directly but I didn't look Into that aspect here.
You could also had put the point data in an array instead of as objects. However, since there are so few this will probably not be worth it in this case.
In conclusion
I have probably missed one or two (or more) places which can be optimized further but you get the idea.
And as you can see the modified code runs several times faster than the original code mainly due to the compromise we make here with quality and some optimizations particularly with the gradient.
There is scope of improvement in programming, in drawing particle section.
instead of using
if(leftover > 0) {
do {
this.process(i++);
} while(--leftover > 0);
}
you can just use this
while(leftover > 0) {
this.process(i++);
leftover --;
}
This will reduce one step of condition checking of if and also the (--)operator that decrements one value and checks. this will reduce the complexity
with all do while you have (--) that can be removed, with simple statement this will reduce the Cyclomatic Complexity of this particular code and make this code faster.
ultimately this will give the performance improvement with the faster processing of your code and less use of CPU and resources. although Ken's answer is also working one, I have created one more fiddle that is similar to your sample site with more speed.
fiddle
If any problem please leave a comment, and update fiddle with game code for performance check.
This loop is already pretty simple, uses stable types that JIT likes, so I don't think you can get significant improvement.
I've eliminated +3 and unrolled it a bit (assuming width*height is divisible by 4). I've added |0 "cast" to integer that makes it sliiightly faster in V8.
Overall it gave 10% improvement:
var i = (3 - 4)|0;
var n = (pix.length - 16)|0;
while(i < n) {
if (pix[i+=4] < 210){
pix[i] = 0;
}
if (pix[i+=4] < 210){
pix[i] = 0;
}
if (pix[i+=4] < 210){
pix[i] = 0;
}
if (pix[i+=4] < 210){
pix[i] = 0;
}
}
If you need it to be massively faster, then maybe use lower-resolution canvas for the effect?
BACKGROUND:
The app allows users to upload a photo of themselves and then place a pair of glasses over their face to see what it looks like. For the most part, it is working fine. After the user selects the location of the 2 pupils, I auto zoom the image based on the ratio between the distance of the pupils and then already known distance between the center points of the glasses. All is working fine there, but now I need to automatically place the glasses image over the eyes.
I am using KinectJS, but the problem is not with regards to that library or javascript.. it is more of an algorithm requirement
WHAT I HAVE TO WORK WITH:
Distance between pupils (eyes)
Distance between pupils (glasses)
Glasses width
Glasses height
Zoom ratio
SOME CODE:
//.. code before here just zooms the image, etc..
//problem is here (this is wrong, but I need to know what is the right way to calculate this)
var newLeftEyeX = self.leftEyePosition.x * ratio;
var newLeftEyeY = self.leftEyePosition.y * ratio;
//create a blue dot for testing (remove later)
var newEyePosition = new Kinetic.Circle({
radius: 3,
fill: "blue",
stroke: "blue",
strokeWidth: 0,
x: newLeftEyeX,
y: newLeftEyeY
});
self.pointsLayer.add(newEyePosition);
var glassesWidth = glassesImage.getWidth();
var glassesHeight = glassesImage.getHeight();
// this code below works perfect, as I can see the glasses center over the blue dot created above
newGlassesPosition.x = newLeftEyeX - (glassesWidth / 4);
newGlassesPosition.y = newLeftEyeY - (glassesHeight / 2);
NEEDED
A math genius to give me the algorithm to determine where the new left eye position should be AFTER the image has been resized
UPDATE
After researching this for the past 6 hours or so, I think I need to do some sort of "translate transform", but the examples I see only allow setting this by x and y amounts.. whereas I will only know the scale of the underlying image. Here's the example I found (which cannot help me):
http://tutorials.jenkov.com/html5-canvas/transformation.html
and here is something which looks interesting, but it is for Silverlight:
Get element position after transform
Is there perhaps some way to do the same in Html5 and/or KinectJS? Or perhaps I am going down the wrong road here... any ideas people?
UPDATE 2
I tried this:
// if zoomFactor > 1, then picture got bigger, so...
if (zoomFactor > 1) {
// if x = 10 (for example) and if zoomFactor = 2, that means new x should be 5
// current x / zoomFactor => 10 / 2 = 5
newLeftEyeX = self.leftEyePosition.x / zoomFactor;
// same for y
newLeftEyeY = self.leftEyePosition.y / zoomFactor;
}
else {
// else picture got smaller, so...
// if x = 10 (for example) and if zoomFactor = 0.5, that means new x should be 20
// current x * (1 / zoomFactor) => 10 * (1 / 0.5) = 10 * 2 = 20
newLeftEyeX = self.leftEyePosition.x * (1 / zoomFactor);
// same for y
newLeftEyeY = self.leftEyePosition.y * (1 / zoomFactor);
}
that didn't work, so then I tried an implementation of Rody Oldenhuis' suggestion (thanks Rody):
var xFromCenter = self.leftEyePosition.x - self.xCenter;
var yFromCenter = self.leftEyePosition.y - self.yCenter;
var angle = Math.atan2(yFromCenter, xFromCenter);
var length = Math.hypotenuse(xFromCenter, yFromCenter);
var xNew = zoomFactor * length * Math.cos(angle);
var yNew = zoomFactor * length * Math.sin(angle);
newLeftEyeX = xNew + self.xCenter;
newLeftEyeY = yNew + self.yCenter;
However, that is still not working as expected. So, I am not sure what the issue is currently. If anyone has worked with KinectJS before and has an idea of what the issue may be, please let me know.
UPDATE 3
I checked Rody's calculations on paper and they seem fine, so there is obviously something else here messing things up.. I got the coordinates of the left pupil at zoom factors 1 and 2. With those coordinates, maybe someone can figure out what the issue is:
Zoom Factor 1: x = 239, y = 209
Zoom Factor 2: x = 201, y = 133
OK, since it's an algorithmic question, I'm going to keep this generic and only write pseudo code.
I f I understand you correctly, What you want is the following:
Transform all coordinates such that the origin of your coordinate system is at the zoom center (usually, central pixel)
Compute the angle a line drawn from this new origin to a point of interest makes with the positive x-axis. Compute also the length of this line.
The new x and y coordinates after zooming are defined by elongating this line, such that the new line is the zoom factor times the length of the original line.
Transform the newly found x and y coordinates back to a coordinate system that makes sense to the computer (e.g., top left pixel = 0,0)
Repeat for all points of interest.
In pseudo-code (with formulas):
x_center = image_width/2
y_center = image_height/2
x_from_zoom_center = x_from_topleft - x_center
y_from_zoom_center = y_from_topleft - y_center
angle = atan2(y_from_zoom_center, x_from_zoom_center)
length = hypot(x_from_zoom_center, y_from_zoom_center)
x_new = zoom_factor * length * cos(angle)
y_new = zoom_factor * length * sin(angle)
x_new_topleft = x_new + x_center
y_new_topleft = y_new + y_center
Note that this assumes the number of pixels used for length and width stays the same after zooming. Note also that some rounding should take place (keep everything double precision, and only round to int after all calculations)
In the code above, atan2 is the four-quadrant arctangent, available in most programming languages, and hypot is simply sqrt(x*x + y*y), but then computed more carefully (e.g., to avoid overflow etc.), also available in most programing languages.
Is this indeed what you were after?