Improve function speed - javascript

I'm doing a BattleShip game in javascript with iio engine.
I'm trying to play against a computer so I have to put a random position for the ships (I hope you know the game :) ).
I have 5 ships that have to be placed in a grid (10x10). The problem is that the function is pretty slow, and sometimes the page don't get load at all.
I want to know if there are some emprovement for the speed of these function, I'm a little bit newbie :D
function posShips(size){
// var size -> size of the ship
var isOk = false; // flag var to check if the ship is in a right position
var isOk2 = true; // flag var, become false if the cell is already fill with another ship
var i;
var j;
var side; // horizontal or vertical
while(!isOk){
i = iio.getRandomInt(1,11);
j = iio.getRandomInt(1,11);
side = iio.getRandomInt(0,2);
if((side ? j : i)+size-1 < 11){ // Not out of the array
for (var k = 0; k < size; k++) { // Size of the ship
if(side){
if(gridHit[i][j+k].stat == "empty"){ //If is empty put the ship
gridHit[i][j+k].stat = "ship";
gridHit[i][j+k].setFillStyle("red")
}else{ // If not empty
isOk2 = false; //Position is not good, do all the thing again.
for (var a = 0; a < size; a++) { // Reset cell
gridHit[i][j+a].stat = "empty";
}
k = 10;
}
}else{
if(gridHit[i+k][j].stat == "empty"){ //If is empty put the ship
gridHit[i+k][j].stat = "ship";
gridHit[i+k][j].setFillStyle("red")
}else{ // If not empty
isOk2 = false; //Position is not good, do all the thing again.
for (var a = 0; a < size; a++) { // Reset cell
gridHit[i+a][j].stat = "empty";
}
k = 10;
}
}
};
if(isOk2)
isOk = true;
}
}
}

Don't pick ship positions that will fall outside the grid. Pick the direction first, and then limit the x and y initial positions based on size. e.g. if the size is 3, there's no point going above 7 for the initial value of the varying coordinate.
Don't change the array while you're searching. Do the search first, and only afterwards update the array. This avoids any "cleanup" operation.
Wherever possible, eliminate repeated deep object references. If accessing grid[y][x] repeatedly for differing x, take a reference to grid[y] first, and then use that for the subsequent accesses.
Break out of loops early, there's no point continuing to test a position if a previous one already failed.
Place your big ships first - it's easier to fit small ships into the gaps left between the big ones.
See http://jsfiddle.net/alnitak/Rp9Ke/ for my implementation, with the equivalent of your function being this:
this.place = function(size) {
// faster array access
var g = this.grid;
// initial direction, and vector
var dir = rand(2); // 0 - y, 1 - x
var dx = dir ? 1 : 0;
var dy = dir ? 0 : 1; // or 1 - dx
LOOP: while (true) {
// initial position
var x = dir ? rand(10 - size) : rand(10);
var y = dir ? rand(10) : rand(10 - size);
// test points
var n = size, tx = x, ty = y;
while (n--) {
if (g[ty][tx]) continue LOOP;
tx += dx;
ty += dy;
}
// fill points
n = size;
while (n--) {
g[y][x] = size;
x += dx;
y += dy;
}
break;
}
};

Related

HTML5 Canvas, better pixel control and better speed

I'm trying to create a little simulation with the help of HTML5 and Javascript using a canvas. My problem however is, I can't really think of a way to control the behavior of my pixels, without making every single pixel an object, which leads to an awful slowdown of my simulation.
Heres the code so far:
var pixels = [];
class Pixel{
constructor(color){
this.color=color;
}
}
window.onload=function(){
canv = document.getElementById("canv");
ctx = canv.getContext("2d");
createMap();
setInterval(game,1000/60);
};
function createMap(){
pixels=[];
for(i = 0; i <= 800; i++){
pixels.push(sub_pixels = []);
for(j = 0; j <= 800; j++){
pixels[i].push(new Pixel("green"));
}
}
pixels[400][400].color="red";
}
function game(){
ctx.fillStyle = "white";
ctx.fillRect(0,0,canv.width,canv.height);
for(i = 0; i <= 800; i++){
for(j = 0; j <= 800; j++){
ctx.fillStyle=pixels[i][j].color;
ctx.fillRect(i,j,1,1);
}
}
for(i = 0; i <= 800; i++){
for(j = 0; j <= 800; j++){
if(pixels[i][j].color == "red"){
direction = Math.floor((Math.random() * 4) + 1);
switch(direction){
case 1:
pixels[i][j-1].color= "red";
break;
case 2:
pixels[i+1][j].color= "red";
break;
case 3:
pixels[i][j+1].color= "red";
break;
case 4:
pixels[i-1][j].color= "red";
break;
}
}
}
}
}
function retPos(){
return Math.floor((Math.random() * 800) + 1);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script language="javascript" type="text/javascript" src="game.js"></script>
</head>
<body>
<canvas width="800px" height="800px" id="canv"></canvas>
</body>
</html>
So my two big questions are, what better way of controlling those pixels is there? And how can I speed up the pixel generation?
Hope you can help me.
Optimizing pixel manipulation
There are many options to speed up your code
Pixels as 32bit ints
The following will slug most machines with too much work.
// I removed fixed 800 and replaced with const size
for(i = 0; i <= size; i++){
for(j = 0; j <= size; j++){
ctx.fillStyle=pixels[i][j].color;
ctx.fillRect(i,j,1,1);
}
}
Don't write each pixel via a rect. Use the pixel data you can get from the canvas API via createImageData and associated functions. It uses typed arrays that are a little quicker than arrays and can have multiple view on the same content.
You can write all the pixels to the canvas in a single call. Not blindingly fast but a zillion times faster than what you are doing.
const size = 800;
const imageData = ctx.createImageData(size,size);
// get a 32 bit view
const data32 = new Uint32Array(imageData.data.buffer);
// To set a single pixel
data32[x+y*size] = 0xFF0000FF; // set pixel to red
// to set all pixels
data32.fill(0xFF00FF00); // set all to green
To get a pixel at a pixel coord
const pixel = data32[x + y * imageData.width];
See Accessing pixel data for more on using the image data.
The pixel data is not displayed until you put it onto the canvas
ctx.putImageData(imageData,0,0);
That will give you a major improvement.
Better data organization.
When performance is critical you sacrifice memory and simplicity to get more CPU cycles doing what you want and less doing a lot of nothing.
You have red pixels randomly expanding into the scene, you read every pixel and check (via a slow string comparison) if it is red. When you find one you add a random red pixel besides it.
Checking the green pixels is a waste and can be avoided. Expanding red pixels that are completely surrounded by other reds is also pointless. They do nothing.
The only pixels you are interested in are the red pixels that are next to green pixels.
Thus you can create a buffer that holds the location of all active red pixels, An active red has at least one green. Each frame you check all the active reds, spawning new ones if they can, and killing them if they are surrounded in red.
We don't need to store the x,y coordinate of each red, just the memory address so we can use a flat array.
const reds = new Uint32Array(size * size); // max size way over kill but you may need it some time.
You dont want to have to search for reds in your reds array so you need to keep track of how many active reds there are. You want all the active reds to be at the bottom of the array. You need to check each active red only once per frame. If a red is dead than all above it must move down one array index. But you only want to move each red only once per frame.
Bubble array
I dont know what this type of array is called its like a separation tank, dead stuff slowly moves up and live stuff moves down. Or unused items bubble up used items settle to the bottom.
I will show it as functional because it will be easier to understand. but is better implemented as one brute force function
// data32 is the pixel data
const size = 800; // width and height
const red = 0xFF0000FF; // value of a red pixel
const green = 0xFF00FF00; // value of a green pixel
const reds = new Uint32Array(size * size); // max size way over kill but you var count = 0; // total active reds
var head = 0; // index of current red we are processing
var tail = 0; // after a red has been process it is move to the tail
var arrayOfSpawnS = [] // for each neighbor that is green you want
// to select randomly to spawn to. You dont want
// to spend time processing so this is a lookup
// that has all the possible neighbor combinations
for(let i = 0; i < 16; i ++){
let j = 0;
const combo = [];
i & 1 && (combo[j++] = 1); // right
i & 2 && (combo[j++] = -1); // left
i & 4 && (combo[j++] = -size); // top
i & 5 && (combo[j++] = size); // bottom
arrayOfSpawnS.push(combo);
}
function addARed(x,y){ // add a new red
const pixelIndex = x + y * size;
if(data32[pixelIndex] === green) { // check if the red can go there
reds[count++] = pixelIndex; // add the red with the pixel index
data32[pixelIndex] = red; // and set the pixel
}
}
function safeAddRed(pixelIndex) { // you know that some reds are safe at the new pos so a little bit faster
reds[count++] = pixelIndex; // add the red with the pixel index
data32[pixelIndex] = red; // and set the pixel
}
// a frame in the life of a red. Returns false if red is dead
function processARed(indexOfRed) {
// get the pixel index
var pixelIndex = reds[indexOfRed];
// check reds neighbors right left top and bottom
// we fill a bit value with each bit on if there is a green
var n = data32[pixelIndex + 1] === green ? 1 : 0;
n += data32[pixelIndex - 1] === green ? 2 : 0;
n += data32[pixelIndex - size] === green ? 4 : 0;
n += data32[pixelIndex + size] === green ? 8 : 0;
if(n === 0){ // no room to spawn so die
return false;
}
// has room to spawn so pick a random
var nCount = arrayOfSpawnS[n].length;
// if only one spawn point then rather than spawn we move
// this red to the new pos.
if(nCount === 1){
reds[indexOfRed] += arrayOfSpawnS[n][0]; // move to next pos
}else{ // there are several spawn points
safeAddRed(pixelIndex + arrayOfSpawnS[n][(Math.random() * nCount)|0]);
}
// reds frame is done so return still alive to spawn another frame
return true;
}
Now to process all the reds.
This is the heart of the bubble array. head is used to index each active red. tail is the index of where to move the current head if no deaths have been encountered tail is equal to head. If however a dead item is encountered the head move up one while the tail remains pointing to the dead item. This moves all the active items to the bottom.
When head === count all active items have been checked. The value of tail now contains the new count which is set after the iteration.
If you were using an object rather than a Integer, instead of moving the active item down you swap the head and tail items. This effectively creates a pool of available objects that can be used when adding new items. This type of array management incurs not GC or Allocation overhead and is hence very quick when compared to stacks and object pools.
function doAllReds(){
head = tail = 0; // start at the bottom
while(head < count){
if(processARed(head)){ // is red not dead
reds[tail++] = reds[head++]; // move red down to the tail
}else{ // red is dead so this creates a gap in the array
// Move the head up but dont move the tail,
// The tail is only for alive reds
head++;
}
}
// All reads done. The tail is now the new count
count = tail;
}
The Demo.
The demo will show you the speed improvement. I used the functional version and there could be some other tweaks.
You can also consider webWorkers to get event more speed. Web worker run on a separate javascript context and provides true concurrent processing.
For the ultimate speed use WebGL. All the logic can be done via a fragment shader on the GPU. This type of task is very well suited to parallel processing for which the GPU is designed.
Will be back later to clean up this answer (got a little too long)
I have also added a boundary to the pixel array as the reds were spawning off the pixel array.
const size = canvas.width;
canvas.height = canvas.width;
const ctx = canvas.getContext("2d");
const red = 0xFF0000FF;
const green = 0xFF00FF00;
const reds = new Uint32Array(size * size);
const wall = 0xFF000000;
var count = 0;
var head = 0;
var tail = 0;
var arrayOfSpawnS = []
for(let i = 0; i < 16; i ++){
let j = 0;
const combo = [];
i & 1 && (combo[j++] = 1); // right
i & 2 && (combo[j++] = -1); // left
i & 4 && (combo[j++] = -size); // top
i & 5 && (combo[j++] = size); // bottom
arrayOfSpawnS.push(combo);
}
const imageData = ctx.createImageData(size,size);
const data32 = new Uint32Array(imageData.data.buffer);
function createWall(){//need to keep the reds walled up so they dont run free
for(let j = 0; j < size; j ++){
data32[j] = wall;
data32[j * size] = wall;
data32[j * size + size - 1] = wall;
data32[size * (size - 1) +j] = wall;
}
}
function addARed(x,y){
const pixelIndex = x + y * size;
if (data32[pixelIndex] === green) {
reds[count++] = pixelIndex;
data32[pixelIndex] = red;
}
}
function processARed(indexOfRed) {
var pixelIndex = reds[indexOfRed];
var n = data32[pixelIndex + 1] === green ? 1 : 0;
n += data32[pixelIndex - 1] === green ? 2 : 0;
n += data32[pixelIndex - size] === green ? 4 : 0;
n += data32[pixelIndex + size] === green ? 8 : 0;
if(n === 0) { return false }
var nCount = arrayOfSpawnS[n].length;
if (nCount === 1) { reds[indexOfRed] += arrayOfSpawnS[n][0] }
else {
pixelIndex += arrayOfSpawnS[n][(Math.random() * nCount)|0]
reds[count++] = pixelIndex;
data32[pixelIndex] = red;
}
return true;
}
function doAllReds(){
head = tail = 0;
while(head < count) {
if(processARed(head)) { reds[tail++] = reds[head++] }
else { head++ }
}
count = tail;
}
function start(){
data32.fill(green);
createWall();
var startRedCount = (Math.random() * 5 + 1) | 0;
for(let i = 0; i < startRedCount; i ++) { addARed((Math.random() * size-2+1) | 0, (Math.random() * size-2+1) | 0) }
ctx.putImageData(imageData,0,0);
setTimeout(doItTillAllDead,1000);
countSameCount = 0;
}
var countSameCount;
var lastCount;
function doItTillAllDead(){
doAllReds();
ctx.putImageData(imageData,0,0);
if(count === 0 || countSameCount === 100){ // all dead
setTimeout(start,1000);
}else{
countSameCount += count === lastCount ? 1 : 0;
lastCount = count; //
requestAnimationFrame(doItTillAllDead);
}
}
start();
<canvas width="800" height="800" id="canvas"></canvas>
The main cause of your slow down is your assumption that you need to loop over every pixel for every operation. You do not do this, as that would be 640,000 iterations for every operation you need to do.
You also shouldn't be doing any manipulation logic within the render loop. The only thing that should be there is drawing code. So this should be moved out to preferably a separate thread (Web Workers). If unable to use those a setTimeout/Interval call.
So first a couple of small changes:
Make Pixel class contain the pixel's coordinates along with the color:
class Pixel{
constructor(color,x,y){
this.color=color;
this.x = x;
this.y = y;
}
}
Keep an array of pixels that will end up creating new red pixels. And another one to keep track of what pixels have been updated so we know which ones need drawn.
var pixels = [];
var infectedPixesl = [];
var updatedPixels = [];
Now the easiest part of the code to change is the render loop. Since the only thing that it needs to do is draw the pixels it will be only a couple lines.
function render(){
var numUpdatedPixels = updatedPixels.length;
for(let i=0; i<numUpdatedPixels; i++){
let pixel = updatedPixels[i];
ctx.fillStyle = pixel.color;
ctx.fillRect(pixel.x,pixel.y,1,1);
}
//clear out the updatedPixels as they should no longer be considered updated.
updatedPixels = [];
//better method than setTimeout/Interval for drawing
requestAnimationFrame(render);
}
From there we can move on to the logic. We will loop over the infectedPixels array, and with each pixel we decide a random direction and get that pixel. If this selected pixel is red we do nothing and continue on. Otherwise we change it's color and add it to a temporary array affectedPixels. After which we test to see if all the pixels around the original pixel are all red, if so we can remove it from the infectedPixels as there is no need to check it again. Then add all the pixels from affectedPixels onto the infectedPixels as these are now new pixels that need to be checked. And the last step is to also add affectedPixels onto updatedPixels so that the render loop draws the changes.
function update(){
var affectedPixels = [];
//needed as we shouldn't change an array while looping over it
var stillInfectedPixels = [];
var numInfected = infectedPixels.length;
for(let i=0; i<numInfected; i++){
let pixel = infectedPixels[i];
let x = pixel.x;
let y = pixel.y;
//instead of using a switch statement, use the random number as the index
//into a surroundingPixels array
let surroundingPixels = [
(pixels[x] ? pixels[x][y - 1] : null),
(pixels[x + 1] ? pixels[x + 1][y] : null),
(pixels[x] ? pixels[x][y + 1] : null),
(pixels[x - 1] ? pixels[x - 1][y] : null)
].filter(p => p);
//filter used above to remove nulls, in the cases of edge pixels
var rand = Math.floor((Math.random() * surroundingPixels.length));
let selectedPixel = surroundingPixels[rand];
if(selectedPixel.color == "green"){
selectedPixel.color = "red";
affectedPixels.push(selectedPixel);
}
if(!surroundingPixels.every(p=>p.color=="red")){
stillInfectedPixels.push(pixel);
}
}
infectedPixels = stillInfectedPixel.concat( affectedPixels );
updatedPixels.push(...affectedPixels);
}
Demo
var pixels = [],
infectedPixels = [],
updatedPixels = [],
canv, ctx;
window.onload = function() {
canv = document.getElementById("canv");
ctx = canv.getContext("2d");
createMap();
render();
setInterval(() => {
update();
}, 16);
};
function createMap() {
for (let y = 0; y < 800; y++) {
pixels.push([]);
for (x = 0; x < 800; x++) {
pixels[y].push(new Pixel("green",x,y));
}
}
pixels[400][400].color = "red";
updatedPixels = [].concat(...pixels);
infectedPixels.push(pixels[400][400]);
}
class Pixel {
constructor(color, x, y) {
this.color = color;
this.x = x;
this.y = y;
}
}
function update() {
var affectedPixels = [];
var stillInfectedPixels = [];
var numInfected = infectedPixels.length;
for (let i = 0; i < numInfected; i++) {
let pixel = infectedPixels[i];
let x = pixel.x;
let y = pixel.y;
let surroundingPixels = [
(pixels[x] ? pixels[x][y - 1] : null),
(pixels[x + 1] ? pixels[x + 1][y] : null),
(pixels[x] ? pixels[x][y + 1] : null),
(pixels[x - 1] ? pixels[x - 1][y] : null)
].filter(p => p);
var rand = Math.floor((Math.random() * surroundingPixels.length));
let selectedPixel = surroundingPixels[rand];
if (selectedPixel.color == "green") {
selectedPixel.color = "red";
affectedPixels.push(selectedPixel);
}
if (!surroundingPixels.every(p => p.color == "red")) {
stillInfectedPixels.push(pixel);
}
}
infectedPixels = stillInfectedPixels.concat(affectedPixels);
updatedPixels.push(...affectedPixels);
}
function render() {
var numUpdatedPixels = updatedPixels.length;
for (let i = 0; i < numUpdatedPixels; i++) {
let pixel = updatedPixels[i];
ctx.fillStyle = pixel.color;
ctx.fillRect(pixel.x, pixel.y, 1, 1);
}
updatedPixels = [];
requestAnimationFrame(render);
}
<canvas id="canv" width="800" height="800"></canvas>

Using arrays for tetris pieces and grid. Why won't this piece show up completely?

Here's the code, below is a link to the jsfiddle.
The first row of the block isn't drawn for some reason, all these 2 dimension for loops are hard for me to wrap my head around, and can't find the reason why the block is only being drawn from the second row and not complete.
The add_block function is supposed to read array data from any block and put it on the grid where my x and y coordinates are.
If there's anyone who knows how to rotate the block, that would be cool too, i know in order to turn something +90 degrees i need to transpose and then revese each row, but it hasn't really worked in earlier tries.
I know i'm not good at explaining but i'll be sure to answer any of your questions.
Thanks in advance! I really want to have a complete picture of how arrays and double for loops interact with eachother, in my head.
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 500;
var grid_columns = 10;
var grid_rows = 15;
var grid_cell_size = 10;
var grid = [];
function create_empty_grid(){
for(var i=0;i<grid_columns;i++){
grid[i] = [];
for(var j=0;j<grid_rows;j++){
grid[i][j] = 0;
}
}
// END DOUBLE FOR LOOPS
}
function clear_grid(){
for(var i=0;i<grid_columns;i++){
for(var j=0;j<grid_rows;j++){
grid[i][j] = 0;
}
}
// END DOUBLE FOR LOOPS
}
var x = 0;
var y = 0;
var w = 2;
var h = 3;
var block = [];
block[0] = [
[1,0,0,0],
[1,1,0,0],
[0,1,0,0],
[0,0,0,0]
];
function add_block(num){
var b = block[num];
for(var i=0;i<w;i++){
for(var j=0;j<h;j++){
if(i >= x && j >= y && i <= w && j <= h){
grid[i][j] = b[i][j];
}
}
}
// END DOUBLE FOR LOOPS
}
function draw(){
for(var i=0;i<grid_columns;i++){
for(var j=0;j<grid_rows;j++){
ctx.fillStyle = "black";
if(grid[i][j] === 1){
ctx.fillStyle = "red";
}else if(grid[i][j] === 0){
ctx.fillStyle = "green";
}
ctx.fillRect(i*grid_cell_size,j*grid_cell_size,grid_cell_size-1,grid_cell_size-1);
}
}
// END DOUBLE FOR LOOP
}
function update(){
}
function tick(){
clearRect(0,0,canvas.width,canvas.height);
clear_grid();
draw();
update();
}
create_empty_grid();
add_block(0);
draw();
View in jsfiddle
It seems like you're using i and j to represent "block" coordinates, and
x and y to represent "grid" coordinates. So I think this condition is wrong:
if(i >= x && j >= y && i <= w && j <= h){
grid[i][j] = b[i][j];
}
I think all you really need here is to replace the if statement with something like:
grid[i+x][j+y] = b[i][j];
But as #Arnauld pointed out, there is also a bit of confusion about whether i,j denotes "row, column" or "column, row", and it looks like your usage is opposite from the way the arrays are initialized. In other words, you're using:
grid[row][column] and b[row][column]
but the way you laid out the arrays, it needs to be
grid[column][row] and b[column][row]
So you'll need to make a few adjustments here and there to make the code do what you want.
The "b" arrays were upside-down for some reason so i changed grid[j+x][i+y] = b[j][i] to grid[j+x][i+y] = b[i][j] this displays the full block, but i still can't visualise the arrays or be able to prevent these problems in the future.
Arrays are so confusing to me.
I'll figure it out eventually i guess, but a day has to go by.

Draw "Squiggly" line along a curve in javascript

This is a bit complicated to describe, so please bear with me.
I'm using the HTML5 canvas to extend a diagramming tool (Diagramo). It implements multiple types of line, straight, jagged (right angle) and curved (cubic or quadratic). These lines can be solid, dotted or dashed.
The new feature I am implementing is a "squiggly" line, where instead of following a constant path, the line zigzags back and forth across the desired target path in smooth arcs.
Below is an example of this that is correct. This works in most cases, however, in certain edge cases it does not.
The implementation is to take the curve, use the quadratic or cubic functions to estimate equidistance points along the line, and draw squiggles along these straight lines by placing control points on either side of the straight line (alternating) and drawing multiple cubic curves.
The issues occur when the line is relatively short, and doubles back on itself close to the origin. An example is below, this happens on longer lines too - the critical point is that there is a very small sharp curve immediately after the origin. In this situation the algorithm picks the first point after the sharp curve, in some cases immediately next to the origin, and considers that the first segment.
Each squiggle has a minimum/maximum pixel length of 8px/14px (which I can change, but much below that and it becomes too sharp, and above becomes too wavy) the code tries to find the right sized squiggle for the line segment to fit with the minimum empty space, which is then filled by a straight line.
I'm hoping there is a solution to this that can account for sharply curved lines, if I know all points along a line can I choose control points that alternate either side of the line, perpendicular too it?
Would one option be to consider a point i and the points i-1 and i+1 and use that to determine the orientation of the line, and thus pick control points?
Code follows below
//fragment is either Cubic or Quadratic curve.
paint(fragment){
var length = fragment.getLength();
var points = Util.equidistancePoints(fragment, length < 100 ? (length < 50 ? 3: 5): 11);
points.splice(0, 1); //remove origin as that is the initial point of the delegate.
//points.splice(0, 1);
delegate.paint(context, points);
}
/**
*
* #param {QuadCurve} or {CubicCurbe} curve
* #param {Number} m the number of points
* #return [Point] a set of equidistance points along the polyline of points
* #author Zack
* #href http://math.stackexchange.com/questions/321293/find-coordinates-of-equidistant-points-in-bezier-curve
*/
equidistancePoints: function(curve, m){
var points = curve.getPoints(0.001);
// Get fractional arclengths along polyline
var n = points.length;
var s = 1.0/(n-1);
var dd = [];
var cc = [];
var QQ = [];
function findIndex(dd, d){
var i = 0;
for (var j = 0 ; j < dd.length ; j++){
if (d > dd[j]) {
i = j;
}
else{
return i;
}
}
return i;
};
dd.push(0);
cc.push(0);
for (var i = 0; i < n; i++){
if(i >0) {
cc.push(Util.distance(points[i], points[i - 1]));
}
}
for (var i = 1 ; i < n ; i++) {
dd.push(dd[i-1] + cc[i]);
}
for (var i = 1 ; i < n ; i++) {
dd[i] = dd[i]/dd[n-1];
}
var step = 1.0/(m-1);
for (var r = 0 ; r < m ; r++){
var d = parseFloat(r)*step;
var i = findIndex(dd, d);
var u = (d - dd[i]) / (dd[i+1] - dd[i]);
var t = (i + u)*s;
QQ[r] = curve.getPoint(t);
}
return QQ;
}
SquigglyLineDelegate.prototype = {
constructor: SquigglyLineDelegate,
paint: function(context, points){
var squiggles = 0;
var STEP = 0.1;
var useStart = false;
var bestSquiggles = -1;
var bestA = 0;
var distance = Util.distance(points[0], this.start);
for(var a = SquigglyLineDelegate.MIN_SQUIGGLE_LENGTH; a < SquigglyLineDelegate.MAX_SQUIGGLE_LENGTH; a += STEP){
squiggles = distance / a;
var diff = Math.abs(Math.floor(squiggles) - squiggles);
if(diff < bestSquiggles || bestSquiggles == -1){
bestA = a;
bestSquiggles = diff;
}
}
squiggles = distance / bestA;
for(var i = 0; i < points.length; i++){
context.beginPath();
var point = points[i];
for(var s = 0; s < squiggles-1; s++){
var start = Util.point_on_segment(this.start, point, s * bestA);
var end = Util.point_on_segment(this.start, point, (s + 1) * bestA);
var mid = Util.point_on_segment(this.start, point, (s + 0.5) * bestA);
end.style.lineWidth = 1;
var line1 = new Line(Util.point_on_segment(mid, end, -this.squiggleWidth), Util.point_on_segment(mid, end, this.squiggleWidth));
var mid1 = Util.getMiddle(line1.startPoint, line1.endPoint);
line1.transform(Matrix.translationMatrix(-mid1.x, -mid1.y));
line1.transform(Matrix.rotationMatrix(radians = 90 * (Math.PI/180)));
line1.transform(Matrix.translationMatrix(mid1.x, mid1.y));
var control1 = useStart ? line1.startPoint : line1.endPoint;
var curve = new QuadCurve(start, control1, end);
curve.style = null;
curve.paint(context);
useStart = !useStart;
}
this.start = point;
context.lineTo(point.x, point.y);
context.stroke();
}
}
}

scriptProcessorNode oscillator frequency

I am working on a web audio stochastic oscillator and am having trouble with the scriptProcessorNode. My algorithm uses a random walk to determine dynamic breakpoints in the waveform and then interpolates between them.
As the breakpoints move on the x axis I thought the frequency of the oscillating waveform would change, but there is just a filtering effect, and the frequency seems to just be determined by the scriptProcessorNode buffer size, which must be a power of 2 between 256 and 16384.
How do you change the frequency of a scriptProcessorNode oscillator?
Here is my synthesis code:
scriptNode.onaudioprocess = function(audioProcessingEvent) {
walk(); //use random walk to generate new x/y position for each breakpoint
var outputBuffer = audioProcessingEvent.outputBuffer;
var lastPoint = 0;
var index = 0;
// linearly interpolate between the new breakpoint positions
for(var i = 0; i < breakpoint.length-1; i++) {
var y = breakpoint[lastPoint].y;
for(var channel = 0; channel <= 0;channel++) {
var outputData = outputBuffer.getChannelData(channel);
if(i != 0){
if(y >= breakpoint[i].y) {
while(y >= breakpoint[i].y) {
y = (breakpoint[i].m*index)+breakpoint[i].b;// y = m(x)+b
outputData[index] = y;
index++;
}
} else if(y <= breakpoint[i].y) {
while(y <= breakpoint[i].y) {
y = (breakpoint[i].m*index)+breakpoint[i].b;
outputData[index] = y;
index++;
}
}
}
}
lastPoint = i;
}
}
And here is a link to a working example: http://andrewbernste.in/bernie/gendy011.html
This is all based on Iannis Xenakis' GENDY stochastic synthesis program.
Thanks!
I solved the problem by using an index variable outside of my scriptNode.onaudioprocess function to write the waveform to the scriptNode buffer. That way the frequency at which the waveform is written to the buffer is not tied to the size of the buffer.
Here is the final code:
var index = 0;
var freq = 0.8;
scriptNode.onaudioprocess = function(audioProcessingEvent){
var outputBuffer = audioProcessingEvent.outputBuffer;
var outputData = outputBuffer.getChannelData(0);
for(var j = 0; j < outputData.length;j++){
// linearly interpolate between the new breakpoint positions
// get the interp point by comparing index to the x distance
var lerp = (index - breakpoint[point].x) / (breakpoint[point+1].x - breakpoint[point].x)
y = nx.interp(lerp,breakpoint[point].y,breakpoint[point+1].y);
if(point < breakpoint.length && index >= breakpoint[point+1].x) {
point++;
}
outputData[j] = y;
index+=freq;
if(index >= breakpoint[breakpoint.length-1].x){
index = 0;
point = 0;
walk();
}
}
}

Algorithm to find space for an object within a 2d area

I'm building a website which uses jQuery to allow users to add widgets to a page, drag them around and resize them (the page is fixed width and infinite height.) The issue that I'm having is that when adding a new widget to the page I have to find a free space for it (the widgets cannot overlap and I'd like to favour spaces at the top of the page.)
I've been looking at various packing algorithms and none of them seem to be suitable. The reason why is that they are designed for packing all of the objects in to the container, this means that all of the previous rectangles are laid out in a uniform way. They often line up an edge of the rectangle so that they form rows/columns, this simplifies working out what will fit where in the next row/column. When the user can move/resize widgets at will these algorithms don't work well.
I thought that I had a partial solution but after writing some pseudo code in here I’ve realized that it won’t work. A brute force based approach would work, but I'd prefer something more efficient if possible. Can anyone suggest a suitable algorithm? Is it a packing algorithm that I'm looking for or would something else work better?
Thanks
Ok, I've worked out a solution. I didn't like the idea of a brute force based approach because I thought it would be inefficient, what I realized though is if you can look at which existing widgets are in the way of placing the widget then you can skip large portions of the grid.
Here is an example: (the widget being placed is 20x20 and page width is 100px in this example.)
This diagram is 0.1 scale and got messed up so I've had to add an extra column
*123456789A*
1+---+ +--+1
2| | | |2
3| | +--+3
4| | 4
5+---+ 5
*123456789A*
We attempt to place a widget at 0x0 but it doesn't fit because there is a 50x50 widget at that coordinate.
So we then advance the current x coordinate being scanned to 51 and check again.
We then find a 40x30 widget at 0x61.
So we then advance the x coordinate to 90 but this doesn't leave enough room for the widget being placed so we increment the y coordinate and reset x back to 0.
We know from the previous attempts that the widgets on the previous line are at least 30px high so we increase the y coordinate to 31.
We encounter the same 50x50 widget at 0x31.
So we increase x to 51 and find that we can place a widget at 51x31
Here is the javascript:
function findSpace(width, height) {
var $ul = $('.snap-layout>ul');
var widthOfContainer = $ul.width();
var heightOfContainer = $ul.height();
var $lis = $ul.children('.setup-widget'); // The li is on the page and we dont want it to collide with itself
for (var y = 0; y < heightOfContainer - height + 1; y++) {
var heightOfShortestInRow = 1;
for (var x = 0; x < widthOfContainer - width + 1; x++) {
console.log(x + '/' + y);
var pos = { 'left': x, 'top': y };
var $collider = $(isOverlapping($lis, pos, width, height));
if ($collider.length == 0) {
// Found a space
return pos;
}
var colliderPos = $collider.position();
// We have collided with something, there is no point testing the points within this widget so lets skip them
var newX = colliderPos.left + $collider.width() - 1; // -1 to account for the ++ in the for loop
x = newX > x ? newX : x; // Make sure that we are not some how going backwards and looping forever
var colliderBottom = colliderPos.top + $collider.height();
if (heightOfShortestInRow == 1 || colliderBottom - y < heightOfShortestInRow) {
heightOfShortestInRow = colliderBottom - y; // This isn't actually the height its just the distance from y to the bottom of the widget, y is normally at the top of the widget tho
}
}
y += heightOfShortestInRow - 1;
}
//TODO: Add the widget to the bottom
}
Here is the longer and more less elegant version that also adjusts the height of the container (I've just hacked it together for now but will clean it up later and edit)
function findSpace(width, height,
yStart, avoidIds // These are used if the function calls itself - see bellow
) {
var $ul = $('.snap-layout>ul');
var widthOfContainer = $ul.width();
var heightOfContainer = $ul.height();
var $lis = $ul.children('.setup-widget'); // The li is on the page and we dont want it to collide with itself
var bottomOfShortestInRow;
var idOfShortestInRow;
for (var y = yStart ? yStart : 0; y <= heightOfContainer - height + 1; y++) {
var heightOfShortestInRow = 1;
for (var x = 0; x <= widthOfContainer - width + 1; x++) {
console.log(x + '/' + y);
var pos = { 'left': x, 'top': y };
var $collider = $(isOverlapping($lis, pos, width, height));
if ($collider.length == 0) {
// Found a space
return pos;
}
var colliderPos = $collider.position();
// We have collided with something, there is no point testing the points within this widget so lets skip them
var newX = colliderPos.left + $collider.width() - 1; // -1 to account for the ++ in the for loop
x = newX > x ? newX : x; // Make sure that we are not some how going backwards and looping forever
colliderBottom = colliderPos.top + $collider.height();
if (heightOfShortestInRow == 1 || colliderBottom - y < heightOfShortestInRow) {
heightOfShortestInRow = colliderBottom - y; // This isn't actually the height its just the distance from y to the bottom of the widget, y is normally at the top of the widget tho
var widgetId = $collider.attr('data-widget-id');
if (!avoidIds || !$.inArray(widgetId, avoidIds)) { // If this is true then we are calling ourselves and we used this as the shortest widget before and it didnt work
bottomOfShortestInRow = colliderBottom;
idOfShortestInRow = widgetId;
}
}
}
y += heightOfShortestInRow - 1;
}
if (!yStart) {
// No space was found so create some
var idsToAvoid = [];
for (var attempts = 0; attempts < widthOfContainer; attempts++) { // As a worse case scenario we have lots of 1px wide colliders
idsToAvoid.push(idOfShortestInRow);
heightOfContainer = $ul.height();
var maxAvailableRoom = heightOfContainer - bottomOfShortestInRow;
var extraHeightRequired = height - maxAvailableRoom;
if (extraHeightRequired < 0) { extraHeightRequired = 0;}
$ul.height(heightOfContainer + extraHeightRequired);
var result = findSpace(width, height, bottomOfShortestInRow, idsToAvoid);
if (result.top) {
// Found a space
return result;
}
// Got a different collider so lets try that next time
bottomOfShortestInRow = result.bottom;
idOfShortestInRow = result.id;
if (!bottomOfShortestInRow) {
// If this is undefined then its broken (because the widgets are bigger then their contianer which is hardcoded atm and resets on f5)
break;
}
}
debugger;
// Something has gone wrong so we just stick it on the bottom left
$ul.height($ul.height() + height);
return { 'left': 0, 'top': $ul.height() - height };
} else {
// The function is calling itself and we shouldnt recurse any further, just return the data required to continue searching
return { 'bottom': bottomOfShortestInRow, 'id': idOfShortestInRow };
}
}
function isOverlapping($obsticles, tAxis, width, height) {
var t_x, t_y;
if (typeof (width) == 'undefined') {
// Existing element passed in
var $target = $(tAxis);
tAxis = $target.position();
t_x = [tAxis.left, tAxis.left + $target.outerWidth()];
t_y = [tAxis.top, tAxis.top + $target.outerHeight()];
} else {
// Coordinates and dimensions passed in
t_x = [tAxis.left, tAxis.left + width];
t_y = [tAxis.top, tAxis.top + height];
}
var overlap = false;
$obsticles.each(function () {
var $this = $(this);
var thisPos = $this.position();
var i_x = [thisPos.left, thisPos.left + $this.outerWidth()]
var i_y = [thisPos.top, thisPos.top + $this.outerHeight()];
if (t_x[0] < i_x[1] && t_x[1] > i_x[0] &&
t_y[0] < i_y[1] && t_y[1] > i_y[0]) {
overlap = this;
return false;
}
});
return overlap;
}

Categories