Get the nearly equal point (a kind of IndexOfPoint method) - javascript

With three.js I need to get the mesh point wich is nearly equal (within a 'fuzz') to a calculated point. This calculated point is not a THREE.Vector2/3, it's a generic object with x y properties.
The function/method is used by a recursive function and a 'real time' process, that's why it has to be optimized.
The simplest expression I've found is the following. Let's say that it's a 2D point, that I provide an array (geometry.vertices), and there is no need to compute the distance :
function IndexOfPoint ( pt, lst ) {
var i = -1;
var test = lst.some( function ( p ) {
i++;
return ( Math.abs( p.x - pt.x ) < 1E-12 ) && ( Math.abs( p.y - pt.y ) < 1E-12 );
});
return test? i: -1;
}
It works, but I have made this quickly and would like to know if there is a best and fastest method ... (please no additional library for that, only plain js)
Thanks in advance

Looks good for me. You could probably skip the increment on the index var i, since the current index is already provided to the callback as the 2nd argument as in Array.prototype.some( function(element, index, array) { ... } ):
function IndexOfPoint ( pt, lst ) {
var i = -1;
lst.some( function ( p, idx ) {
if ( ( Math.abs( p.x - pt.x ) < 1E-12 ) && ( Math.abs( p.y - pt.y ) < 1E-12 ) ) {
i = idx;
return true;
}
else {
return false;
}
});
return i;
}

I am not familiar with three.js, but if the list is sorted and if the grid is uniform, you might be able to obtain the index of the closest vector immediately and simply test that index. For example:
function IndexOfPoint (pt, list) {
// assuming the list of points is sorted going from left to right, top to bottom
var index = Math.floor((pt.x - offsetX) * scaleX) + gridWidth * Math.floor((pt.y - offsetY) * scaleY);
var candidatePoint = list[index];
if (candidatePoint && (Math.abs(pt.x - candidatePoint.x) < 1E-12) && (Math.abs(pt.y - candidatePoint.y) < 1E-12) {
return index;
} else {
return -1;
}
}

Related

Check if given three line segments represented by coordinates of 4 element array can form a triangle

I need to check if given three line segments form a triangle. A line segment can be expressed as an array of 4 integers giving the end-points coordinates in the form [ x1, y1, x2, y2 ].
So I need to write a function that is given as input three line segments K, L and M and will return 1 if they form a triangle, 0 otherwise.
If the input parameters are outside the range of the algorithm supports I need to return -1.
Examples :
function trigTest(K, L, M)
var K=[2,3,6,9], L=[8,1,6,9], M=[8,1,2,3], X=[1,7,6,9]
trigTest(K, L, M) // -> 1
trigTest(L, K, M) // -> 1
trigTest(M, K, L) // -> 1
trigTest(L, L, M) // -> 0
trigTest(X, L, M) // -> 0
I actually have a solution but it's pretty cumbersome and I don't think it is the right way. First I calculate the distance of every line segment and then I use triangle inequalities to check if they can actually form a triangles base on their lengths.
function distance(line){
var x1 = line[0],
y1 = line[1],
x2 = line[2],
y2 = line[3];
return Math.sqrt(Math.pow((x2-x1),2) + Math.pow(y2-y1),2)
}
function trigTest(K,L,M){
var distanceK = distance(K), distanceL = distance(L), distanceM = distance(M);
if((distanceK + distanceL) > distanceM && (distanceK + distanceM) > distanceL && distanceL + distanceM > distanceK){
// algorithm here
}else{
return 0;
}
}
Update
Thanks to #antoniskamamis and #trincot I have made a similar solution if someone wants to stick with arrays instead of working with strings. Big shout out to them.
function trigTest(K, L, M) {
var points = [];
var k = dots(K), l = dots(L), m = dots(M);
if(ifDotsOnSameLineAreEqual(k) || ifDotsOnSameLineAreEqual(l) || ifDotsOnSameLineAreEqual(m)){
return false;
}else{
return points.concat(k,l,m).every(function(point, index, array){
return array.filter(function(i){ return ifTwoDotsAreEqual(i,point)}).length == 2;
})
}
}
function dots(line) {
var x1 = line[0],
y1 = line[1],
x2 = line[2],
y2 = line[3];
return [[x1,y1],[x2, y2]];
}
function ifTwoDotsAreEqual(x,y){
return x[0] == y[0] && x[1] == y[1];
}
function ifDotsOnSameLineAreEqual(line){
return ifTwoDotsAreEqual(line[0],line[1]);
}
you could use this approach
function trigTest(a,b,c){
var parts = [];
Array.prototype.slice.call(arguments).forEach(function(item){
parts.push(item.slice(0,2).join("|"));
parts.push(item.slice(2).join("|"));
})
return parts.every(function(item, index, array){
return array.filter( function(x){ return x == item}).length == 2;
})
}
What it does is:
runs through the list of arguments
Array.prototype.slice.call(arguments).forEach
seperates the arrays into points first two, last two as strings parts.push(item.slice(0,2).join(""));parts.push(item.slice(2).join(""));
given the array of points it checks that each point is present two times parts.every(function(item, index, array){ return array.filter( function(x){ return x == item}).length == 2; })
Using a 'one liner'
function trigTest(a,b,c){
var slice = Array.prototype.slice;
return slice.call(arguments).reduce(function(previous, current){
previous.push(current.slice(0,2).join("|"));
previous.push(current.slice(2).join("|"));
return previous;
}, [])
.every(function(item, index, array){
return array.filter( function(x){ return x == item; }).length == 2;
})
}
Checking for zero length lines
if we know that the inputs are not validated to be lines before we have to add a check if any of the given lines has start and end points the same (is a 0 length line or a point)
in this case our code will have to be like this
function trigTest(a,b,c){
var slice = Array.prototype.slice;
if(slice.call(arguments).some(isPoint)){
return false;
};
return slice.call(arguments).reduce(function(previous, current){
previous.push(current.slice(0,2).join("|"));
previous.push(current.slice(2).join("|"));
return previous;
}, [])
.every(function(item, index, array){
return array.filter( function(x){ return x == item; }).length == 2;
})
}
function isPoint(value){
return value[0] == value[2] && value[1] == value[3];
}
Based on your examples, the key criteria is that you have exactly two copies of three x,y coordinates, so rather than deal with this from a geometric or trigonometric standpoint, you may have an easier time dealing with this based on basic set-theory: to have a triangle formed from three points A, B, C, your line segments must follow the pattern [Ax, Ay, Bx, By], [Bx, By, Cx, Cy], [Cx, Cy, Ax, Ay].
These segments are not required to be in that order, such as [Bx, By, Ax, Ay] is also valid for the first term.
To check for a valid triangle, count repeated coordinates first to verify two repeats of three unique coordinates (this will also eliminate repeated line segments), then verify that each line segment is non-zero in length (not [Ax, Ay, Ax, Ay]). Those two checks will handle the first two requirements.
I don't know the boundary limits, so I cannot advise on how to test whether it is outside the bounds of the algorithm, but I suspect that will require checking the actual coordinate range, which is integer arithmetic.
This approach should be usable in any javascript engine, although your specific choice of javascript engine will determine the best way to implement it.
var getRandom = () => 1+ Math.floor( Math.random() * 3 ) ;
// get random line
var getLine = () =>
{
do
var l = {
'a' : {
'x' : getRandom(),
'y' : getRandom()
},
'b' : {
'x' : getRandom(),
'y' : getRandom()
}
};
// repeat until startPoint differ from endPoint
while ( l.a.x == l.b.x & l.a.y == l.b.y )
return l;
};
var match = (K, L, M) => {
// Tirangle consist of three points
// three lines -> six points
var p1 = K.a.x + "," + K.a.y,
p2 = K.b.x + "," + K.b.y,
p3 = L.a.x + "," + L.a.y,
p4 = L.b.x + "," + L.b.y,
p5 = M.a.x + "," + M.a.y,
p6 = M.b.x + "," + M.b.y;
// count frequency
var freq = {};
freq[p1] = freq[p1] + 1 || 1;
freq[p2] = freq[p2] + 1 || 1;
freq[p3] = freq[p3] + 1 || 1;
freq[p4] = freq[p4] + 1 || 1;
freq[p5] = freq[p5] + 1 || 1;
freq[p6] = freq[p6] + 1 || 1;
// result Array
var result = Array();
for ( point in freq ){
// if the point is common for two lines add to result array
freq[point] == 2 ? result.push( point ) : false;
}
return result;
}
var test = () => {
// Three random lines
var K = getLine(), L = getLine(), M = getLine();
// Test if three lines has three common points
if ( match(K, L, M).length == 3 ) {
printSvg(K,L,M);
return 1
} else {
return 0
}
}
// run when document ready
var app = () => {
// div#box needed to print svg with triangles
const box = document.getElementById('box');
// test random lines, repeat
for (x =0; x <= 1000; x++) {
t = test ();
}
}
// fire app() when document ready
document.onreadystatechange = ()=> document.readyState == "complete" ? app() : false;
// format legend html
var printWsp = (L) => "("+ L.a.x + ","+ L.a.y+") ("+L.b.x+","+L.b.y+")";
// append svg to div#box
var printSvg = (K, L, M) => {
var legend = '<div class="legend">K ' + printWsp(K) +"<br>L " + printWsp(L) +"<br>M "+ printWsp(M) + "</div>";
var svgStr = "<svg height='250' width='250'>";
svgStr += "<line x1="+K.a.x*60 +" y1="+K.a.y*60 +" x2="+K.b.x*60 +" y2="+K.b.y*60 +" style='stroke:rgb(255,0,0);stroke-width:2' />";
svgStr += "<line x1="+L.a.x*60 +" y1="+L.a.y*60 +" x2="+L.b.x*60 +" y2="+L.b.y*60 +" style='stroke:rgb(0,255,0);stroke-width:2' />";
svgStr += "<line x1="+M.a.x*60 +" y1="+M.a.y*60 +" x2="+M.b.x*60 +" y2="+M.b.y*60 +" style='stroke:rgb(255,0,255);stroke-width:2' />";
svgStr += "</svg> ";
box.insertAdjacentHTML('beforeend', legend);
box.insertAdjacentHTML('beforeend', svgStr);
}

d3.js How to simplify a complex path - using a custom algorithm

I've got a very basic example here. http://jsfiddle.net/jEfsh/57/ that creates a complex path - with lots of points. I've read up on an algorithm that may look over the points and create a simpler set of coordinates. Does anyone have any experience with this - examples on how to loop through the path data and pass it through the algorithm - find the shortest set of points to create a more rudimentary version of the shape?
http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
var points = "M241,59L237,60L233,60L228,61L224,61L218,63L213,63L209,65L204,66L199,67L196,68L193,69L189,70L187,72L184,72L182,74L179,75L177,76L175,78L173,79L170,81L168,83L165,85L163,87L161,89L159,92L157,95L157,97L155,102L153,105L152,110L151,113L151,117L151,123L150,137L148,180L148,185L148,189L148,193L148,197L148,202L148,206L149,212L151,218L152,222L154,229L154,232L155,235L157,239L158,241L160,245L162,247L163,249L165,251L167,254L169,256L172,258L175,260L178,261L183,265L188,268L193,270L206,273L213,275L220,275L225,275L232,276L238,277L243,277L249,277L253,277L259,277L266,277L271,277L277,277L281,277L284,277L288,277L293,277L297,276L302,274L305,272L308,271L311,268L313,267L315,264L318,261L322,257L324,254L326,249L329,244L331,241L332,239L334,234L338,230L339,226L341,222L343,218L345,213L347,211L348,207L349,201L351,196L352,192L353,187L353,183L353,180L353,178L354,176L354,173L354,170L354,168L354,167L354,166L354,164L354,162L354,161L354,159L354,158L354,155L354,152L354,149L352,143L349,137L347,133L343,125L340,119 M241,59L340,119";
d3.select("#g-1").append("path").attr("d", points);
//simplify the path
function DouglasPeucker(){
}
/*
//http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
function DouglasPeucker(PointList[], epsilon)
// Find the point with the maximum distance
dmax = 0
index = 0
end = length(PointList)
for i = 2 to ( end - 1) {
d = shortestDistanceToSegment(PointList[i], Line(PointList[1], PointList[end]))
if ( d > dmax ) {
index = i
dmax = d
}
}
// If max distance is greater than epsilon, recursively simplify
if ( dmax > epsilon ) {
// Recursive call
recResults1[] = DouglasPeucker(PointList[1...index], epsilon)
recResults2[] = DouglasPeucker(PointList[index...end], epsilon)
// Build the result list
ResultList[] = {recResults1[1...end-1] recResults2[1...end]}
} else {
ResultList[] = {PointList[1], PointList[end]}
}
// Return the result
return ResultList[]
end
*/
It's not clear what your problem is exactly. Do you have problems to turn the SVG data string into a list of points? You can use this:
function path_from_svg(svg) {
var pts = svg.split(/[ML]/);
var path = [];
console.log(pts.length);
for (var i = 1; i < pts.length; i++) {
path.push(pts[i].split(","));
}
return path;
}
It is a very simple approach: It splits the string on all move (M) and line (L) commands and treats them as lines. It then splits all substrings on the comma. The first "substring" is ignored, because it is the empty string before the first M. If there is a way to do this better in d3 I haven't found it.
The reverse operation is easier:
function svg_to_path(path) {
return "M" + path.join("L");
}
This is equivalent to svg.line.interpolate("linear").
You can then implement the Douglas-Peucker algorithm on this path data recursively:
function path_simplify_r(path, first, last, eps) {
if (first >= last - 1) return [path[first]];
var px = path[first][0];
var py = path[first][1];
var dx = path[last][0] - px;
var dy = path[last][1] - py;
var nn = Math.sqrt(dx*dx + dy*dy);
var nx = -dy / nn;
var ny = dx / nn;
var ii = first;
var max = -1;
for (var i = first + 1; i < last; i++) {
var p = path[i];
var qx = p[0] - px;
var qy = p[1] - py;
var d = Math.abs(qx * nx + qy * ny);
if (d > max) {
max = d;
ii = i;
}
}
if (max < eps) return [path[first]];
var p1 = path_simplify_r(path, first, ii, eps);
var p2 = path_simplify_r(path, ii, last, eps);
return p1.concat(p2);
}
function path_simplify(path, eps) {
var p = path_simplify_r(path, 0, path.length - 1, eps);
return p.concat([path[path.length - 1]]);
}
The distance to the line is not calculated in a separate function but directly with the formula for the distance of a point to a 2d line from the normal {nx, ny} on the line vector {dx, dy} between the first and last point. The normal is normalised, nx*nx + ny*ny == 1.
When creating the paths, only the first point is added, the last point path[last] is implied and must be added in path_simplify, which is a front end to the recursive function path_simplify_r. This approach was chosen so that concatenating the left and right subpaths does not create a duplicate point in the middle. (This could equally well and maybe cleaner be done by joining p1 and p2.slice(1).)
Here's everything put together in a fiddle: http://jsfiddle.net/Cbk9J/3/
Lots of good references in the comments to this question -- alas they are comments and not suggested answers which can be truly voted on.
http://bost.ocks.org/mike/simplify/
shows interactive use of this kind of thing which references Douglas-Peucker but also Visvalingam.

Difficulties in converting an recursive algorithm into an iterative one

I've been trying to implement a recursive backtracking maze generation algorithm in javascript. These were done after reading a great series of posts on the topic here
While the recursive version of the algorithm was a no brainer, the iterative equivalent has got me stumped.
I thought I understood the concept, but my implementation clearly produces incorrect results. I've been trying to pin down a bug that might be causing it, but I am beginning to believe that my problems are being caused by a failure in logic, but of course I am not seeing where.
My understanding of the iterative algorithm is as follows:
A stack is created holding representations of cell states.
Each representation holds the coordinates of that particular cell, and a list of directions to access adjacent cells.
While the stack isn't empty iterate through the directions on the top of the stack, testing adjacent cells.
If a valid cell is found place it at the top of the stack and continue with that cell.
Here is my recursive implementation ( note: keydown to step forward ): http://jsbin.com/urilan/14
And here is my iterative implementation ( once again, keydown to step forward ): http://jsbin.com/eyosij/2
Thanks for the help.
edit: I apologize if my question wasn't clear. I will try to further explain my problem.
When running the iterative solution various unexpected behaviors occur. First and foremost, the algorithm doesn't exhaust all available options before backtracking. Rather, it appears to be selecting cells at a random when there is one valid cell left. Overall however, the movement doesn't appear to be random.
var dirs = [ 'N', 'W', 'E', 'S' ];
var XD = { 'N': 0, 'S':0, 'E':1, 'W':-1 };
var YD = { 'N':-1, 'S':1, 'E':0, 'W': 0 };
function genMaze(){
var dirtemp = dirs.slice().slice(); //copies 'dirs' so its not overwritten or altered
var path = []; // stores path traveled.
var stack = [[0,0, shuffle(dirtemp)]]; //Stack of instances. Each subarray in 'stacks' represents a cell
//and its current state. That is, its coordinates, and which adjacent cells have been
//checked. Each time it checks an adjacent cell a direction value is popped from
//from the list
while ( stack.length > 0 ) {
var current = stack[stack.length-1]; // With each iteration focus is to be placed on the newest cell.
var x = current[0], y = current[1], d = current[2];
var sLen = stack.length; // For testing whether there is a newer cell in the stack than the current.
path.push([x,y]); // Store current coordinates in the path
while ( d.length > 0 ) {
if( stack.length != sLen ){ break;}// If there is a newer cell in stack, break and then continue with that cell
else {
var cd = d.pop();
var nx = x + XD[ cd ];
var ny = y + YD[ cd ];
if ( nx >= 0 && ny >= 0 && nx < w && ny < h && !cells[nx][ny] ){
dtemp = dirs.slice().slice();
cells[nx][ny] = 1;
stack.push( [ nx, ny, shuffle(dtemp) ] ); //add new cell to the stack with new list of directions.
// from here the code should break from the loop and start again with this latest addition being considered.
}
}
}
if (current[2].length === 0){stack.pop(); } //if all available directions have been tested, remove from stack
}
return path;
}
I hope that helps clear up the question for you. If it is still missing any substance please let me know.
Thanks again.
I'm not very good in javascript, but I try to implement your recursive code to iterative. You need to store For index on stack also. So code look like:
function genMaze(cx,cy) {
var dirtemp = dirs; //copies 'dirs' so its not overwritten
var path = []; // stores path traveled.
var stack = [[cx, cy, shuffle(dirtemp), 0]]; // we also need to store `for` indexer
while (stack.length > 0) {
var current = stack[stack.length - 1]; // With each iteration focus is to be placed on the newest cell.
var x = current[0], y = current[1], d = current[2], i = current[3];
if (i > d.length) {
stack.pop();
continue;
}
stack[stack.length - 1][3] = i + 1; // for next iteration
path.push([x, y]); // Store current coordinates in the path
cells[x][y] = 1;
var cd = d[i];
var nx = x + XD[cd];
var ny = y + YD[cd];
if (nx >= 0 && ny >= 0 && nx < w && ny < h && !cells[nx][ny]) {
dtemp = dirs;
stack.push([nx, ny, shuffle(dtemp), 0]);
}
}
return path;
}
Does this little code could also help ?
/**
Examples
var sum = tco(function(x, y) {
return y > 0 ? sum(x + 1, y - 1) :
y < 0 ? sum(x - 1, y + 1) :
x
})
sum(20, 100000) // => 100020
**/
function tco(f) {
var value, active = false, accumulated = []
return function accumulator() {
accumulated.push(arguments)
if (!active) {
active = true
while (accumulated.length) value = f.apply(this, accumulated.shift())
active = false
return value
}
}
}
Credits, explanations ans more infos are on github https://gist.github.com/1697037
Is has the benefit to not modifying your code, so it could be applied in other situations too. Hope that helps :)

Techniques for smoother image animation with JS/CSS

I am using the following code to glide an image across the top layer of a webpage but its a little jittery, giving streaky vertical lines down the image especially when over content with many nested elements. This is the case even when the border is set to zero. Any suggestions for a smoother method for gliding an image with JS/CSS?
border=4;
pps=250; // speed of glide (pixels per second)
skip=2; // e.g. if set to 10 will skip 9 in 10 pixels
refresh=3; // how often looks to see if move needed in milliseconds
elem = document.createElement("img");
elem.id = 'img_id';
elem.style.zIndex="2000";
elem.style.position="fixed";
elem.style.top=0;
elem.style.left=0;
elem.src='http://farm7.static.flickr.com/6095/6301314495_69e6d9eb5c_m.jpg';
elem.style.border=border+'px solid black';
elem.style.cursor='pointer';
document.body.insertBefore(elem,null);
pos_start = -250;
pos_current = pos_start;
pos_finish = 20000;
var timer = new Date().getTime();
move();
function move ()
{
var elapsed = new Date().getTime() - timer;
var pos_new = Math.floor((pos_start+pps*elapsed/1000)/skip)*skip;
if (pos_new != pos_current)
{
if (pos_new>pos_finish)
pos_new=pos_finish;
$("#img_id").css('left', pos_new);
if (pos_new==pos_finish)
return;
pos_current = pos_new;
}
t = setTimeout("move()", refresh);
}
I do not have a solution that I am sure of will prevent the vertical lines from appearing.
I do however have a couple of tips to improve your code so performance increases and you might have a chance that the lines disappear.
Cache the image element outside of your move function:
var image = $("#img_id")[0];
In your code, there is no reason to query the image ID against the DOM every 3 milliseconds. jQuery's selector engine, Sizzle has to a lot of work¹.
Don't use the jQuery CSS function:
image.style.left = pos_new;
Setting a property object is faster than a function call. In the case of the jQuery css function, there are at least two function calls (one to css and one inside css).
Use interval instead of timeout:
setInterval(move, refresh);
I would consider an interval for one-off animations I wanted to be as
smooth as possible
setTimeout or setInterval?
One other option for smoother animation is to use CSS transitions or animations. A great introduction and comparison can be found in CSS Animations and JavaScript by John Resig
Browser support table: http://caniuse.com/#search=transition
A JavaScript library that I find makes CSS animation via JavaScript very easy is morpheus.
¹ Under the hood, this is the code it goes through every 3 milliseconds to find your image:
In a browser that supports querySelectorAll:
Sizzle = function( query, context, extra, seed ) {
context = context || document;
// Only use querySelectorAll on non-XML documents
// (ID selectors don't work in non-HTML documents)
if ( !seed && !Sizzle.isXML(context) ) {
// See if we find a selector to speed up
var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
// Speed-up: Sizzle("TAG")
if ( match[1] ) {
return makeArray( context.getElementsByTagName( query ), extra );
// Speed-up: Sizzle(".CLASS")
} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
return makeArray( context.getElementsByClassName( match[2] ), extra );
}
}
if ( context.nodeType === 9 ) {
// Speed-up: Sizzle("body")
// The body element only exists once, optimize finding it
if ( query === "body" && context.body ) {
return makeArray( [ context.body ], extra );
// Speed-up: Sizzle("#ID")
} else if ( match && match[3] ) {
var elem = context.getElementById( match[3] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Handle the case where IE and Opera return items
// by name instead of ID
if ( elem.id === match[3] ) {
return makeArray( [ elem ], extra );
}
} else {
return makeArray( [], extra );
}
}
try {
return makeArray( context.querySelectorAll(query), extra );
} catch(qsaError) {}
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the root
// and working up from there (Thanks to Andrew Dupont for the technique)
// IE 8 doesn't work on object elements
} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
var oldContext = context,
old = context.getAttribute( "id" ),
nid = old || id,
hasParent = context.parentNode,
relativeHierarchySelector = /^\s*[+~]/.test( query );
if ( !old ) {
context.setAttribute( "id", nid );
} else {
nid = nid.replace( /'/g, "\\$&" );
}
if ( relativeHierarchySelector && hasParent ) {
context = context.parentNode;
}
try {
if ( !relativeHierarchySelector || hasParent ) {
return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
}
} catch(pseudoError) {
} finally {
if ( !old ) {
oldContext.removeAttribute( "id" );
}
}
}
}
return oldSizzle(query, context, extra, seed);
};
And a browser that doesn't:
var Sizzle = function( selector, context, results, seed ) {
results = results || [];
context = context || document;
var origContext = context;
if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
return [];
}
if ( !selector || typeof selector !== "string" ) {
return results;
}
var m, set, checkSet, extra, ret, cur, pop, i,
prune = true,
contextXML = Sizzle.isXML( context ),
parts = [],
soFar = selector;
// Reset the position of the chunker regexp (start from head)
do {
chunker.exec( "" );
m = chunker.exec( soFar );
if ( m ) {
soFar = m[3];
parts.push( m[1] );
if ( m[2] ) {
extra = m[3];
break;
}
}
} while ( m );
if ( parts.length > 1 && origPOS.exec( selector ) ) {
if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
set = posProcess( parts[0] + parts[1], context, seed );
} else {
set = Expr.relative[ parts[0] ] ?
[ context ] :
Sizzle( parts.shift(), context );
while ( parts.length ) {
selector = parts.shift();
if ( Expr.relative[ selector ] ) {
selector += parts.shift();
}
set = posProcess( selector, set, seed );
}
}
} else {
// Take a shortcut and set the context if the root selector is an ID
// (but not if it'll be faster if the inner selector is an ID)
if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
ret = Sizzle.find( parts.shift(), context, contextXML );
context = ret.expr ?
Sizzle.filter( ret.expr, ret.set )[0] :
ret.set[0];
}
if ( context ) {
ret = seed ?
{ expr: parts.pop(), set: makeArray(seed) } :
Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
set = ret.expr ?
Sizzle.filter( ret.expr, ret.set ) :
ret.set;
if ( parts.length > 0 ) {
checkSet = makeArray( set );
} else {
prune = false;
}
while ( parts.length ) {
cur = parts.pop();
pop = cur;
if ( !Expr.relative[ cur ] ) {
cur = "";
} else {
pop = parts.pop();
}
if ( pop == null ) {
pop = context;
}
Expr.relative[ cur ]( checkSet, pop, contextXML );
}
} else {
checkSet = parts = [];
}
}
if ( !checkSet ) {
checkSet = set;
}
if ( !checkSet ) {
Sizzle.error( cur || selector );
}
if ( toString.call(checkSet) === "[object Array]" ) {
if ( !prune ) {
results.push.apply( results, checkSet );
} else if ( context && context.nodeType === 1 ) {
for ( i = 0; checkSet[i] != null; i++ ) {
if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
results.push( set[i] );
}
}
} else {
for ( i = 0; checkSet[i] != null; i++ ) {
if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
results.push( set[i] );
}
}
}
} else {
makeArray( checkSet, results );
}
if ( extra ) {
Sizzle( extra, origContext, results, seed );
Sizzle.uniqueSort( results );
}
return results;
};
There are lots of minor ways to tweak you code to run slightly smoother... Use a feedback loop to optimize the step size and delay, look for even steps that don't round up or down causing small jumps at regular intervals, etc.
But the secret API you're probably looking for (and which is used by many of the libraries you are avoiding) is requestAnimationFrame. It's currently non-standarized, so each browser has a prefixed implementation (webkitRequestAnimationFrame, mozRequestAnimationFrom, etc.)
Instead of re-explaining how it helps reduce/prevent tearing and vsync issues, I'll point you to the article itself:
http://robert.ocallahan.org/2010/08/mozrequestanimationframe_14.html
I took a shot at this with a few ideas in mind. I could never get the animation to be incredibly un-smooth, nor did I ever experience any vertical lines, so I'm not sure if it's even an improvement. Nevertheless, the function below takes a few key ideas into account that make sense to me:
Keep the element away from the DOM with a container <div> for the animation. DOM involvement in repaints makes it much longer than it should be for a basic overlay animation.
Keep as much fat as possible out of the move function. Seeing as this function will be called a large amount, the less script there is to run, the better. This includes that jQuery call to change the element position.
Only refresh as much as absolutely necessary. I set the refresh interval here to 121 Hz, but that's an absolute top-end for a 60Hz monitor. I might suggest 61 or less, depending on what's needed.
Only set a value in to the element style object if it's needed. The function in the question did do this, but again it's a good thing to keep in mind, because in some engines simply accessing the setter in a style object will force a repaint.
What I wanted to try out was using the image as the background of an element, so you could just script changing the CSS background-position property instead of changing the element position. This would mean loss DOM involvement in the repaints triggered by the animation, if possible.
And the function, for your testing, with a fairly unnecessary closure:
var border = 4;
var pps = 250;
var skip = 2;
var refresh = 1000 / 121; // 2 * refresh rate + 1
var image = new Image();
image.src = 'http://farm7.static.flickr.com/6095/6301314495_69e6d9eb5c_m.jpg';
// Move img (Image()) from x1,y1 to x2,y2
var moveImage = function (img, x1, y1, x2, y2) {
x_min = (x1 > x2) ? x2 : x1;
y_min = (y1 > y2) ? y2 : y1;
x_max = (x1 > x2) ? x1 : x2;
y_max = (y1 > y2) ? y1 : y2;
var div = document.createElement('div');
div.id = 'animationDiv';
div.style.zIndex = '2000';
div.style.position = 'fixed';
div.style.top = y_min;
div.style.left = x_min;
div.style.width = x_max + img.width + 'px';
div.style.height = y_max + img.height + 'px';
div.style.background = 'none';
document.body.insertBefore(div, null);
elem = document.createElement('img');
elem.id = 'img_id';
elem.style.position = 'relative';
elem.style.top = 0;
elem.style.left = 0;
elem.src = img.src;
elem.style.border = border + 'px solid black';
elem.style.cursor = 'pointer';
var theta = Math.atan2((y2 - y1), (x2 - x1));
(function () {
div.insertBefore(elem, null);
var stop = function () {
clearInterval(interval);
elem.style.left = x2 - x1;
elem.style.top = y2 - y1;
};
var startTime = +new Date().getTime();
var xpmsa = pps * Math.cos(theta) / (1000 * skip); // per milli adjusted
var ypmsa = pps * Math.sin(theta) / (1000 * skip);
var interval = setInterval(function () {
var t = +new Date().getTime() - startTime;
var x = (Math.floor(t * xpmsa) * skip);
var y = (Math.floor(t * ypmsa) * skip);
if (parseInt(elem.style.left) === x &&
parseInt(elem.style.top) === y) return;
elem.style.left = x + 'px';
elem.style.top = y + 'px';
if (x > x_max || x < x_min || y > y_max || y < y_min) stop();
}, refresh);
console.log(xpmsa, ypmsa, elem, div, interval);
})();
};
For your circumstance, you should consider the followings to make animation smoother:
The interval between animation steps (your refresh value) should be long enough for browser to process (JavaScript code, rendering). As my experience, it should be 10 to 20 milliseconds.
Why you made the image position multiple of skip? Set skip value as small as possible (1) could make animation smoother.
Avoid causing browsers reflow if possible (reflow vs repaint).
Using appropriate easing method instead of linear (as in your code) could make animation look better (human sight, not technical)
Optimize JavaScript code for each animation step. This is not problem in simple animation as yours, but you can improve something such as: use setInterval instead of setTimeout, cache image object for fast access, use native JS code to change image position
Hope these help.
Your question really seems to be about browser rendering engines and their capabilities. As you have noticed, there are limitations as to how quick a browser can render animation. If you hit this limitation, you'll see jitter or other 'unsmooth' behavior. Sometimes rendering faults, like not cleaning up parts of the animation or scrambled parts.
Back in the olden days, any form of decent animation was virtually impossible. In time, things got better, but I still remember using the tiniest possible images to keep my nice folding/unfolding menu performing smoothly. Of course, these days we've got hardware accelerated browser rendering, so you can do multiple animations at once, and don't need to worry a whole lot about animation being slow.
But I've been redoing some animations I've used, because my iPad (1) seems quite slow rendering some of them. Like scrolling a large div got quite choppy. So basically, I started to tune things down:
Using simple animation instead of complex, and: no combined animation (like scroll and fade)
Reduce number of html-elements inside animated object
Make animated object smaller
Preload as much as possible
Create space for the animated object (if possible, in case sliding or moving means moving a whole lot of other elements)
This did work, after some trial and error. What you've got to keep in mind is that the javascript is just changing the css properties of html-elements. The browser repaints what the JS tells him to. So the more it tells him, the heavier it gets, and the rendering falls behind.
Looking at performance, it breaks down into three components: CPU, GPU and screen updates. Every browser engine works differently, so performance can differ as well. An interesting look at how this works, comes from the people on the IE 10 team, which is more thorough than I could be: http://blogs.msdn.com/b/ie/archive/2011/04/26/understanding-differences-in-hardware-acceleration-through-paintball.aspx
Javascript animations are always somewhat jittery, since timers aren't very precise. You can get a little better peformance by using a few tricks:
Enable hardware acceleration: img { -webkit-transform: translateZ(0) };
Use setInterval, it can result in smoother animation too, although the change is usually unnoticeable
Set your refresh rate to 1000/60 (60pfs) - that's the screen limit, and timers never go below 4ms
IE9+ seems to solve this by coupling ticks with the screen refresh rate, which makes for much smoother animation, but I wouldn't count on other browsers doing that anytime soon. The future is in CSS transitions.
In CSS, you could use this:
img {
-webkit-transition:2s all linear;
-moz-transition:2s all linear;
-ms-transition:2s all linear;
transition:2s all linear;
}
But since your animation duration depends on the target position to achieve a constant speed, you can manipulate the values via JS:
var img = document.createElement('img')
document.body.appendChild(img)
var styles = {
zIndex : '2000'
, position : 'absolute'
, top : '0px'
, left : '0px'
, border : '4px solid black'
, cursor : 'pointer'
}
Object.keys(styles).forEach(function(key){
img.style[key] = styles[key]
})
var prefixes = ['webkit', 'Moz', 'O', 'ms', '']
, speed = 250
, endPosition = 2000
, transition = Math.floor(endPosition/speed)+'s all linear'
prefixes.forEach(function(prefix){
img.style[prefix+(prefix ? 'T' : 't')+'ransition'] = transition
})
img.onload = function(){
img.style.left = endPosition+'px' // starts the animation
}
img.src = 'http://farm7.static.flickr.com/6095/6301314495_69e6d9eb5c_m.jpg'
(left out a few cross-browser code paths for brevity - onload, forEach, Object.keys)
Try taking advantage of css transforms and requestanimationframe feature.
See the TweenLite library:
http://www.greensock.com/v12/

javascript grid help

I am working on looping through a few grid items and was hoping someone could help me find a way to figure out how to get to the items that I need and possibly I will better understand how to access the items in the grid. So here is the grid.
[0] [1] [2] [3] [4]
[5] [6] [7] [8] [9]
[10] [11] [12] [13] [14]
[15] [16] [17] [18] [19]
[20] [21] [22] [23] [24]
This is basically a 5x5 grid, however it could be any size but I just used this for the example. There are two ways I would like to loop through this. The first one being in this order:
0,1,2,3,4,9,14,19,24,23,22,21,20,15,10,5,6,7,8,13,18,17,16,11,12
Basically all that is doing is going around the outside starting from the top left. The next way I would want to loop through that is through the same exact values except in reverse order (basically inside out instead of outside in) and actually thinking about this now I could just loop through the first method backwards. If anyone can help me with this it would be great. I really want to learn more on how to loop through items in crazy arrangements like this.
This function
function n(i, w, h)
{
if (i < w)
return i;
if (i < w + h-1)
return w-1 + (i-w+1)*w;
if (i < w + h-1 + w-1)
return w-1 + (h-1)*w - (i - (w + h-1 - 1));
if (i < w + h-1 + w-1 + h-2)
return (h - (i - (w + w-1 + h-1 - 2)))*w;
var r = n(i - (w-1)*2 - (h-1)*2, w-2, h-2);
var x = r % (w-2);
var y = Math.floor(r / (w-2));
return (y+1)*w + (x+1);
}
accepts as input
i: Index of the item you're looking for
w: Width of the grid
h: Height of the grid
and returns the corresponding element of the grid assuming that clock-wise spiral traversal.
The implementation simply checks if we're on the top side (i<w), on the downward right side (i<w+h-1) and so on and for these cases it computes the cell element explicitly.
If we complete one single trip around the spiral then it calls recursively itself to find the element in the inner (w-2)*(h-2) grid and then extracts and adjusts the two coordinates considering the original grid size.
This is much faster for big grids than just iterating and emulating the spiral walk, for example computing n(123121, 1234, 3012) is immediate while the complete grid has 3712808 elements and a walk of 123121 steps would require quite a long time.
You just need a way to represent the traversal pattern.
Given an NxM matrix (e.g. 5x5), the pattern is
GENERAL 5x5
------- -------
N right 5
M-1 down 4
N-1 left 4
M-2 up 3
N-2 right 3
M-3 down 2
N-3 left 2
M-4 up 1
N-4 right 1
This says move 5 to the right, 4 down, 4 left, 3 up, 3 right, 2 down, 2 left, 1 up, 1 right. The step size shifts after each two iterations.
So, you can track the current "step-size" and the current direction while decrementing N, M until you reach the end.
IMPORTANT: make sure to write down the pattern for a non-square matrix to see if the same pattern applies.
Here's the "walking" method. Less efficient, but it works.
var arr = new Array();
for(var n=0; n<25; n++) arr.push(n);
var coords = new Array();
var x = 0;
var y = 0;
for(var i=0; i<arr.length; i++) {
if( x > 4 ) {
x = 0;
y++;
}
coords[i] = {'x': x, 'y': y};
x++;
}
// okay, coords contain the coordinates of each item in arr
// need to move along the perimeter until a collision, then turn.
// start at 0,0 and move east.
var dir = 0; // 0=east, 1=south, 2=west, 3=north.
var curPos = {'x': 0, 'y': 0};
var resultList = new Array();
for(var x=0; x<arr.length; x++) {
// record the current position in results
var resultIndex = indexOfCoords(curPos, coords);
if(resultIndex > -1) {
resultList[x] = arr[resultIndex];
}
else {
resultList[x] = null;
}
// move the cursor to a valid position
var tempCurPos = movePos(curPos, dir);
var outOfBounds = isOutOfBounds(tempCurPos, coords);
var itemAtTempPos = arr[indexOfCoords(tempCurPos, coords)];
var posInList = resultList.indexOf( itemAtTempPos );
if(outOfBounds || posInList > -1) {
dir++;
if(dir > 3) dir=0;
curPos = movePos(curPos, dir);
}
else {
curPos = tempCurPos;
}
}
/* int indexOfCoords
*
* Searches coordList for a match to myCoords. If none is found, returns -1;
*/
function indexOfCoords(myCoords, coordsList) {
for(var i=0; i<coordsList.length; i++) {
if(myCoords.x == coordsList[i].x && myCoords.y == coordsList[i].y) return i;
}
return -1;
}
/* obj movePos
*
* Alters currentPosition by incrementing it 1 in the direction provided.
* Valid directions are 0=east, 1=south, 2=west, 3=north
* Returns the resulting coords as an object with x, y.
*/
function movePos(currentPosition, direction) {
var newPosition = {'x':currentPosition.x, 'y':currentPosition.y};
if(direction == 0) {
newPosition.x++;
}
else if(direction == 1) {
newPosition.y++;
}
else if(direction == 2) {
newPosition.x--;
}
else if(direction == 3) {
newPosition.y--;
}
return newPosition;
}
/* bool isOutOfBounds
*
* Compares the x and y coords of a given position to the min/max coords in coordList.
* Returns true if the provided position is outside the boundaries of coordsList.
*
* NOTE: This is just one, lazy way of doing this. There are others.
*/
function isOutOfBounds(position, coordsList) {
// get min/max
var minx=0, miny=0, maxx=0, maxy=0;
for(var i=0; i<coordsList.length; i++) {
if(coordsList[i].x > maxx) maxx = coordsList[i].x;
else if(coordsList[i].x < minx) minx = coordsList[i].x;
if(coordsList[i].y > maxy) maxy = coordsList[i].y;
else if(coordsList[i].y < miny) miny = coordsList[i].y;
}
if(position.x < minx || position.x > maxx || position.y < miny || position.y > maxy) return true;
else return false;
}
This will move through the grid in a direction until it hits the bounds or an item already in the results array, then turn clockwise. It's pretty rudimentary, but I think it would do the job. You could reverse it pretty simply.
Here's a working example: http://www.imakewidgets.com/test/walkmatrix.html

Categories