Less detailed drawing in javascript - javascript

I have this canvas that you can draw on, but since I'm going to use a physics library on the whole thing at some point, I'd like the drawing to be a bit less detailed.
I was thinking about something along the lines of reading the mouse position at a certain interval and then just drawing a straight line to the new mouse position. I've somehow tried this with setInterval and setTimeout, but it never does anything close to what I'd like to see. Does anyone have any suggestions how I could do this?
Thank you!

Here is a javascript implementation of Douglas-Peucker path simplifying.
http://mourner.github.io/simplify-js/
(See full code for simplify.js below.)
You might also consider using curves instead of lines to reduce points (path smoothing)
Smoothing paths is an often considered task:
Douglas Alan Schepers (from w3c):
http://schepers.cc/getting-to-the-point
The Spiro library (as used in Inkscape, etc):
http://www.levien.com/spiro/
Ken Fyrstenberg Nilsen (frequent contributor at SO):
http://www.codeproject.com/Tips/562175/Draw-Smooth-Lines-on-HTML5-Canvas
Paper.js has methods to both smooth and also simplify paths:
http://paperjs.org/tutorials/paths/smoothing-simplifying-flattening/
Code for Simplify.js (BSD license at http://mourner.github.io/simplify-js/):
(function (global, undefined) {
// to suit your point format, run search/replace for '.x' and '.y';
// to switch to 3D, uncomment the lines in the next 2 functions
// (configurability would draw significant performance overhead)
function getSquareDistance(p1, p2) { // square distance between 2 points
var dx = p1.x - p2.x,
// dz = p1.z - p2.z,
dy = p1.y - p2.y;
return dx * dx +
// dz * dz +
dy * dy;
}
function getSquareSegmentDistance(p, p1, p2) { // square distance from a point to a segment
var x = p1.x,
y = p1.y,
// z = p1.z,
dx = p2.x - x,
dy = p2.y - y,
// dz = p2.z - z,
t;
if (dx !== 0 || dy !== 0) {
t = ((p.x - x) * dx +
// (p.z - z) * dz +
(p.y - y) * dy) /
(dx * dx +
// dz * dz +
dy * dy);
if (t > 1) {
x = p2.x;
y = p2.y;
// z = p2.z;
} else if (t > 0) {
x += dx * t;
y += dy * t;
// z += dz * t;
}
}
dx = p.x - x;
dy = p.y - y;
// dz = p.z - z;
return dx * dx +
// dz * dz +
dy * dy;
}
// the rest of the code doesn't care for the point format
// basic distance-based simplification
function simplifyRadialDistance(points, sqTolerance) {
var i,
len = points.length,
point,
prevPoint = points[0],
newPoints = [prevPoint];
for (i = 1; i < len; i++) {
point = points[i];
if (getSquareDistance(point, prevPoint) > sqTolerance) {
newPoints.push(point);
prevPoint = point;
}
}
if (prevPoint !== point) {
newPoints.push(point);
}
return newPoints;
}
// simplification using optimized Douglas-Peucker algorithm with recursion elimination
function simplifyDouglasPeucker(points, sqTolerance) {
var len = points.length,
MarkerArray = (typeof Uint8Array !== undefined + '')
? Uint8Array
: Array,
markers = new MarkerArray(len),
first = 0,
last = len - 1,
i,
maxSqDist,
sqDist,
index,
firstStack = [],
lastStack = [],
newPoints = [];
markers[first] = markers[last] = 1;
while (last) {
maxSqDist = 0;
for (i = first + 1; i < last; i++) {
sqDist = getSquareSegmentDistance(points[i], points[first], points[last]);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
firstStack.push(first);
lastStack.push(index);
firstStack.push(index);
lastStack.push(last);
}
first = firstStack.pop();
last = lastStack.pop();
}
for (i = 0; i < len; i++) {
if (markers[i]) {
newPoints.push(points[i]);
}
}
return newPoints;
}
// both algorithms combined for awesome performance
function simplify(points, tolerance, highestQuality) {
var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
points = highestQuality ? points : simplifyRadialDistance(points, sqTolerance);
points = simplifyDouglasPeucker(points, sqTolerance);
return points;
};
// export either as a Node.js module, AMD module or a global browser variable
if (typeof exports === 'object') {
module.exports = simplify;
} else if (typeof define === 'function' && define.amd) {
define(function () {
return simplify;
});
} else {
global.simplify = simplify;
}
}(this));

Related

X-neighbors of a pixel JavaScript

I'm working on image processing using JavaScript and I would like to you know if there was any generic formula to determine the x-neighbors of a pixel.
I know that for a 3*3 square there is 8 neighbors that can be determine using a specific x and y pixel.
(x-1,y-1) , (x-1,y) , (x-1,y+1),
(x,y-1) , (x,y) , (x,y+1),
(x+1,y-1) , (x+1,y) , (x+1,y+1).
The problem is that I'm working with 5*5 squares,7*7 squares and 9*9 squares and I was wondering if there was any way to have all the x-neighbors of a pixel from those three squares without having to write the location manually in my program.
Thanks
var size = 5;
var d = Math.floor(size / 2);
for (var dx = -d; dx <= d; dx++) {
for (var dy = -d; dy <= d; dy++) {
if (dx || dy) {
// Do something with (x + dx, y + dy)
}
}
}
If you're doing this is a lot (i.e. for every pixel in an image), it might be worth first creating a flat array of value first:
var size = 5;
var d = Math.floor(size / 2);
var dPixels = [];
for (var dx = -d; dx <= d; dx++) {
for (var dy = -d; dy <= d; dy++) {
if (dx || dy) {
dPixels.push(dx, dy);
}
}
}
// Then for each pixel (x, y)
for (var i = 0; i < dPixels.length; i += 2){
// Do something with x + dPixels[i], y + dPixels[i + 1]
}

Spring physics equilibrium always moving to left

I have the following mesh which is generated by random points and creating triangles using Delaunay triangulation. Then I apply spring force per triangle on each of its vertices. But for some reason the equilibrium is always shifted to the left.
Here is a video of the behaviour:
https://youtu.be/gb5aj05zkIc
Why this is happening?
Here is the code for the physics:
for ( let i=0; i < mesh.geometry.faces.length; i++) {
let face = mesh.geometry.faces[i];
let a = mesh.geometry.vertices[face.a];
let b = mesh.geometry.vertices[face.b];
let c = mesh.geometry.vertices[face.c];
let p1 = Vertcies[face.a];
let p2 = Vertcies[face.b];
let p3 = Vertcies[face.c];
update_force_points(p1, p2, a, b);
update_force_points(p1, p3, a, c);
update_force_points(p2, p3, b, c);
}
function update_force_points(p1, p2, p1p, p2p) {
// get all the verticies
var dx = (p1.x - p2.x);
var dy = (p1.y - p2.y);
var len = Math.sqrt(dx*dx + dy*dy);
let fx = (ks * (len - r) * (dx/len)) + ((kd * p2.vx - p1.vx));
let fy = (ks * (len - r) * (dy/len)) + ((kd * p2.vy - p1.vy));
if ( ! p1.fixed ) {
p1.fx = (ks * (len - r) * (dx/len)) + ((kd * p2.vx - p1.vx));
p1.fy = (ks * (len - r) * (dy/len)) + ((kd * p2.vy - p1.vy));
}
if ( ! p2.fixed ) {
p2.fx = -1 * p1.fx;
p2.fy = -1 * p1.fy;
}
p1.vx += p1.fx / mass;
p1.vy += p1.fy / mass;
p2.vx += p2.fx / mass;
p2.vy += p2.fy / mass;
p1.x += p1.vx;
p1.y += p1.vy;
p2.x += p2.vx;
p2.y += p2.vy;
p1p.x = p1.x;
p1p.y = p1.y;
p2p.x = p2.x;
p2p.y = p2.y;
p2p.z = 0.0;
p1p.z = 0.0;
}
At the moment you're doing velocity calculations and assigning new positions at the same time, so the balance will change depending on the order that you cycle through points in. I would guess that points at the bottom left are either at the beginning of the vertex list, or at the end.
try doing all the p#.vx calculations linearly, then do a second pass where you just do p#.x += p#.vx
that way you calculate all necessary velocities based on a snapshot of where points were the previous frame, then you update their positions after all points have new velocities.
So do:
for(var i = 0; i < #; i++){
updateforces(bla,bla,bla) //don't assign position in here, just add forces to the velocity
}
for(var i =0; i < #; i++){
updateposition(bla,bla,bla)
}

How to make the car move towards the monster

I am new to game development and I have build a car game where the automatically moves and when it hits a monster.Now I want to make the car move towards the monster.So I looked into the path finding algorithms and for now I thought to implement A-Star path finding algorithm in my game.So the function for finding path is like below:
function findPath(world, pathStart, pathEnd)
{
// shortcuts for speed
var abs = Math.abs;
var max = Math.max;
var pow = Math.pow;
var sqrt = Math.sqrt;
// the world data are integers:
// anything higher than this number is considered blocked
// this is handy is you use numbered sprites, more than one
// of which is walkable road, grass, mud, etc
var maxWalkableTileNum = 0;
// keep track of the world dimensions
// Note that this A-star implementation expects the world array to be square:
// it must have equal height and width. If your game world is rectangular,
// just fill the array with dummy values to pad the empty space.
var worldWidth = world[0].length;
var worldHeight = world.length;
var worldSize = worldWidth * worldHeight;
// which heuristic should we use?
// default: no diagonals (Manhattan)
var distanceFunction = ManhattanDistance;
var findNeighbours = function(){}; // empty
/*
// alternate heuristics, depending on your game:
// diagonals allowed but no sqeezing through cracks:
var distanceFunction = DiagonalDistance;
var findNeighbours = DiagonalNeighbours;
// diagonals and squeezing through cracks allowed:
var distanceFunction = DiagonalDistance;
var findNeighbours = DiagonalNeighboursFree;
// euclidean but no squeezing through cracks:
var distanceFunction = EuclideanDistance;
var findNeighbours = DiagonalNeighbours;
// euclidean and squeezing through cracks allowed:
var distanceFunction = EuclideanDistance;
var findNeighbours = DiagonalNeighboursFree;
*/
// distanceFunction functions
// these return how far away a point is to another
function ManhattanDistance(Point, Goal)
{ // linear movement - no diagonals - just cardinal directions (NSEW)
return abs(Point.x - Goal.x) + abs(Point.y - Goal.y);
}
function DiagonalDistance(Point, Goal)
{ // diagonal movement - assumes diag dist is 1, same as cardinals
return max(abs(Point.x - Goal.x), abs(Point.y - Goal.y));
}
function EuclideanDistance(Point, Goal)
{ // diagonals are considered a little farther than cardinal directions
// diagonal movement using Euclide (AC = sqrt(AB^2 + BC^2))
// where AB = x2 - x1 and BC = y2 - y1 and AC will be [x3, y3]
return sqrt(pow(Point.x - Goal.x, 2) + pow(Point.y - Goal.y, 2));
}
// Neighbours functions, used by findNeighbours function
// to locate adjacent available cells that aren't blocked
// Returns every available North, South, East or West
// cell that is empty. No diagonals,
// unless distanceFunction function is not Manhattan
function Neighbours(x, y)
{
var N = y - 1,
S = y + 1,
E = x + 1,
W = x - 1,
myN = N > -1 && canWalkHere(x, N),
myS = S < worldHeight && canWalkHere(x, S),
myE = E < worldWidth && canWalkHere(E, y),
myW = W > -1 && canWalkHere(W, y),
result = [];
if(myN)
result.push({x:x, y:N});
if(myE)
result.push({x:E, y:y});
if(myS)
result.push({x:x, y:S});
if(myW)
result.push({x:W, y:y});
findNeighbours(myN, myS, myE, myW, N, S, E, W, result);
return result;
}
// returns every available North East, South East,
// South West or North West cell - no squeezing through
// "cracks" between two diagonals
function DiagonalNeighbours(myN, myS, myE, myW, N, S, E, W, result)
{
if(myN)
{
if(myE && canWalkHere(E, N))
result.push({x:E, y:N});
if(myW && canWalkHere(W, N))
result.push({x:W, y:N});
}
if(myS)
{
if(myE && canWalkHere(E, S))
result.push({x:E, y:S});
if(myW && canWalkHere(W, S))
result.push({x:W, y:S});
}
}
// returns every available North East, South East,
// South West or North West cell including the times that
// you would be squeezing through a "crack"
function DiagonalNeighboursFree(myN, myS, myE, myW, N, S, E, W, result)
{
myN = N > -1;
myS = S < worldHeight;
myE = E < worldWidth;
myW = W > -1;
if(myE)
{
if(myN && canWalkHere(E, N))
result.push({x:E, y:N});
if(myS && canWalkHere(E, S))
result.push({x:E, y:S});
}
if(myW)
{
if(myN && canWalkHere(W, N))
result.push({x:W, y:N});
if(myS && canWalkHere(W, S))
result.push({x:W, y:S});
}
}
// returns boolean value (world cell is available and open)
function canWalkHere(x, y)
{
return ((world[x] != null) &&
(world[x][y] != null) &&
(world[x][y] <= maxWalkableTileNum));
};
// Node function, returns a new object with Node properties
// Used in the calculatePath function to store route costs, etc.
function Node(Parent, Point)
{
var newNode = {
// pointer to another Node object
Parent:Parent,
// array index of this Node in the world linear array
value:Point.x + (Point.y * worldWidth),
// the location coordinates of this Node
x:Point.x,
y:Point.y,
// the heuristic estimated cost
// of an entire path using this node
f:0,
// the distanceFunction cost to get
// from the starting point to this node
g:0
};
return newNode;
}
// Path function, executes AStar algorithm operations
function calculatePath()
{
// create Nodes from the Start and End x,y coordinates
var mypathStart = Node(null, {x:pathStart[0], y:pathStart[1]});
var mypathEnd = Node(null, {x:pathEnd[0], y:pathEnd[1]});
// create an array that will contain all world cells
var AStar = new Array(worldSize);
// list of currently open Nodes
var Open = [mypathStart];
// list of closed Nodes
var Closed = [];
// list of the final output array
var result = [];
// reference to a Node (that is nearby)
var myNeighbours;
// reference to a Node (that we are considering now)
var myNode;
// reference to a Node (that starts a path in question)
var myPath;
// temp integer variables used in the calculations
var length, max, min, i, j;
// iterate through the open list until none are left
while(length = Open.length)
{
max = worldSize;
min = -1;
for(i = 0; i < length; i++)
{
if(Open[i].f < max)
{
max = Open[i].f;
min = i;
}
}
// grab the next node and remove it from Open array
myNode = Open.splice(min, 1)[0];
// is it the destination node?
if(myNode.value === mypathEnd.value)
{
myPath = Closed[Closed.push(myNode) - 1];
do
{
result.push([myPath.x, myPath.y]);
}
while (myPath = myPath.Parent);
// clear the working arrays
AStar = Closed = Open = [];
// we want to return start to finish
result.reverse();
}
else // not the destination
{
// find which nearby nodes are walkable
myNeighbours = Neighbours(myNode.x, myNode.y);
// test each one that hasn't been tried already
for(i = 0, j = myNeighbours.length; i < j; i++)
{
myPath = Node(myNode, myNeighbours[i]);
if (!AStar[myPath.value])
{
// estimated cost of this particular route so far
myPath.g = myNode.g + distanceFunction(myNeighbours[i], myNode);
// estimated cost of entire guessed route to the destination
myPath.f = myPath.g + distanceFunction(myNeighbours[i], mypathEnd);
// remember this new path for testing above
Open.push(myPath);
// mark this node in the world graph as visited
AStar[myPath.value] = true;
}
}
// remember this route as having no more untested options
Closed.push(myNode);
}
} // keep iterating until the Open list is empty
return result;
}
// actually calculate the a-star path!
// this returns an array of coordinates
// that is empty if no path is possible
return calculatePath();
} // end of findPath() function
and then call the function by
currentPath = findPath(world,pathStart,pathEnd);
But not working.My working pen
Any help is appreciated.
Here is a simple path finding script to start from.
Once you have a path calculated, it should be trivial to move the car along it.
This script has two stages:
World generation
Where the map is scanned for hindrances and monsters
Path generation
Where a monster is found and a path is being calculated.
//HTML elements
var canvas = document.body.appendChild(document.createElement("canvas"));
canvas.height = 500;
canvas.width = canvas.height;
var ctx = canvas.getContext("2d");
//Logic elements
var tileSize = 16;
var monster = {
x: Math.floor(Math.random() * Math.ceil(canvas.width / tileSize) / 2) * 2,
y: Math.floor(Math.random() * Math.ceil(canvas.height / tileSize) / 2) * 2
};
var player = {
x: 9,
y: 9
};
var aStar = {
path: [],
opened: [],
closed: [],
done: false
};
//Simple distance formular
function distance(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
//Tested Tiles
ctx.fillStyle = "cyan";
for (var pi = 0; pi < aStar.closed.length; pi++) {
var p = aStar.closed[pi];
ctx.fillRect(p.x * tileSize, p.y * tileSize, tileSize, tileSize);
}
//Path
ctx.fillStyle = "blue";
for (var pi = 0; pi < aStar.path.length; pi++) {
var p = aStar.path[pi];
ctx.fillRect(p.x * tileSize, p.y * tileSize, tileSize, tileSize);
}
//Monster
ctx.fillStyle = "red";
ctx.fillRect(monster.x * tileSize, monster.y * tileSize, tileSize, tileSize);
//Player
ctx.fillStyle = "green";
ctx.fillRect(player.x * tileSize, player.y * tileSize, tileSize, tileSize);
//Tiles
for (var x = 0; x < Math.ceil(canvas.width / tileSize); x++) {
for (var y = 0; y < Math.ceil(canvas.height / tileSize); y++) {
ctx.strokeRect(x * tileSize, y * tileSize, tileSize, tileSize);
}
}
}
function main() {
//If no steps, open "player"
if (aStar.opened.length == 0) {
aStar.opened.push({
x: player.x,
y: player.y,
step: 0
});
}
//Check for monster
if ((aStar.opened.some(function(c) {
return c.x === monster.x && c.y === monster.y;
})) == true) {
//if monster found
if (aStar.path.length < 1) {
//If no steps in path, add monster as first
aStar.path.push(aStar.opened.find(function(c) {
return c.x === monster.x && c.y === monster.y;
}));
} else if ((aStar.path.length > 0 ? aStar.path[aStar.path.length - 1].step == 0 : false) === false) {
//If last step of path isn't player, compute a step to path
var lastTile = aStar.path[aStar.path.length - 1];
var bestTile = {
x: lastTile.x,
y: lastTile.y,
step: lastTile.step
};
//Loop through tiles adjacent to the last path tile and pick the "best"
for (var x = lastTile.x - 1; x < lastTile.x + 2; x++) {
for (var y = lastTile.y - 1; y < lastTile.y + 2; y++) {
var suspect = aStar.closed.find(function(c) {
return c.x === x && c.y === y;
});
if (suspect !== void 0) {
if (suspect.step + distance(suspect, player) < bestTile.step + distance(bestTile, player)) {
bestTile = suspect;
}
}
}
}
//Add best tile to path
aStar.path.push(bestTile);
}
} else {
//If monster isn't found, continue world mapping
//"newOpen" will hold the next "opened" list
var newOpen = [];
//For each opened, check neighbours
for (var oi = 0; oi < aStar.opened.length; oi++) {
var o = aStar.opened[oi];
for (var x = o.x - 1; x < o.x + 2; x++) {
for (var y = o.y - 1; y < o.y + 2; y++) {
if (x === o.x && y === o.y ||
aStar.closed.some(function(c) {
return c.x === x && c.y === y;
}) ||
aStar.opened.some(function(c) {
return c.x === x && c.y === y;
}) ||
newOpen.some(function(c) {
return c.x === x && c.y === y;
})) {
continue;
}
//If neighbours isn't in any list, add it to the newOpen list
newOpen.push({
x: x,
y: y,
step: o.step + 1
});
}
}
}
//Close the previously opened list
aStar.closed = aStar.closed.concat(aStar.opened);
//Add new opened list
aStar.opened = newOpen;
}
//Draw progress
draw();
requestAnimationFrame(main);
}
//Start process
requestAnimationFrame(main);
EDIT 1 - No pathfinding
I am not even sure you need pathfinding for this.
In the example below the cars are simply pushed towards a target relative to their angle to it:
var __extends = (this && this.__extends) || (function() {
var extendStatics = Object.setPrototypeOf ||
({
__proto__: []
}
instanceof Array && function(d, b) {
d.__proto__ = b;
}) ||
function(d, b) {
for (var p in b)
if (b.hasOwnProperty(p)) d[p] = b[p];
};
return function(d, b) {
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Game;
(function(Game) {
var GameImage = (function() {
function GameImage(name, src) {
this.name = name;
this.src = src;
this.node = document.createElement("img");
GameImage._pending++;
this.node.onload = GameImage._loading;
this.node.src = this.src;
GameImage.all.push(this);
}
GameImage.loaded = function() {
return this._loaded === this._pending;
};
GameImage._loading = function() {
this._loaded++;
};
GameImage.getImage = function(id) {
return this.all.find(function(img) {
return img.name === id;
});
};
return GameImage;
}());
GameImage.all = [];
GameImage._loaded = 0;
GameImage._pending = 0;
new GameImage("background", "http://res.cloudinary.com/dfhppjli0/image/upload/c_scale,w_2048/v1492045665/road_dwsmux.png");
new GameImage("hero", "http://res.cloudinary.com/dfhppjli0/image/upload/c_scale,w_32/v1491958999/car_p1k2hw.png");
new GameImage("monster", "http://res.cloudinary.com/dfhppjli0/image/upload/v1491958478/monster_rsm0po.png");
new GameImage("hero_other", "http://res.cloudinary.com/dfhppjli0/image/upload/v1492579967/car_03_ilt08o.png");
function distance(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function degreeToRadian(degrees) {
return degrees * (Math.PI / 180);
}
function radianToDegree(radians) {
return radians * (180 / Math.PI);
}
function angleBetweenTwoPoints(p1, p2) {
return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
}
var Actor = (function() {
function Actor() {
this.angle = 0;
}
Actor.prototype.main = function() {};
Actor.prototype.render = function(ctx) {
if (this.angle != 0) {
var rads = degreeToRadian(this.angle - 90);
ctx.translate(this.position.x + 0.5 * this.image.node.naturalWidth, this.position.y + 0.5 * this.image.node.naturalHeight);
ctx.rotate(rads);
ctx.drawImage(this.image.node, 0, 0);
ctx.rotate(-rads);
ctx.translate(-(this.position.x + 0.5 * this.image.node.naturalWidth), -(this.position.y + 0.5 * this.image.node.naturalHeight));
} else {
ctx.drawImage(this.image.node, this.position.x, this.position.y);
}
};
return Actor;
}());
var Monster = (function(_super) {
__extends(Monster, _super);
function Monster(position) {
var _this = _super.call(this) || this;
_this.position = position;
_this.image = GameImage.getImage("monster");
Monster.all.push(_this);
return _this;
}
return Monster;
}(Actor));
Monster.all = [];
var Car = (function(_super) {
__extends(Car, _super);
function Car(position, target) {
if (target === void 0) {
target = null;
}
var _this = _super.call(this) || this;
_this.position = position;
_this.target = target;
_this.hitCount = 0;
_this.image = GameImage.getImage("hero");
_this.speed = 10;
Car.all.push(_this);
return _this;
}
Car.prototype.main = function() {
var angle = angleBetweenTwoPoints(this.target.position, this.position);
var cos = Math.cos(degreeToRadian(angle)) * -1;
var sin = Math.sin(degreeToRadian(angle));
this.angle = angle;
this.position.x += cos * this.speed;
this.position.y -= sin * this.speed;
if (distance(this.position, this.target.position) < 10) {
this.target.position.x = Math.random() * mainCanvas.width;
this.target.position.y = Math.random() * mainCanvas.height;
this.hitCount++;
console.log("Hit!");
}
};
return Car;
}(Actor));
Car.all = [];
var background = GameImage.getImage("background");
var mainCanvas = document.body.appendChild(document.createElement("canvas"));
mainCanvas.width = background.node.naturalWidth;
mainCanvas.height = background.node.naturalHeight;
var ctx = mainCanvas.getContext("2d");
var monster1 = new Monster({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
});
var monster2 = new Monster({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
});
new Car({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
}, monster1);
new Car({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
}, monster2);
function main() {
ctx.drawImage(background.node, 0, 0);
for (var ci = 0; ci < Car.all.length; ci++) {
var c = Car.all[ci];
c.main();
c.render(ctx);
}
for (var mi = 0; mi < Monster.all.length; mi++) {
var m = Monster.all[mi];
m.main();
m.render(ctx);
}
requestAnimationFrame(main);
}
requestAnimationFrame(main);
})(Game || (Game = {}));
As long as there are not obstacles, this works fine.

Smooth canvas line and on curser start point

I am attempting to create a canvas web application and having two main problems. I would love to make the pen tool draw more smoothly. Secondly, each time I clear the sketch and begin to draw again the line begins in a different point to the curser/mouse.
Here is javascript for my drawing tool:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var radius = 10;
var dragging = false;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
context.lineWidth = radius*2;
var putPoint = function(e){
if(dragging){
context.lineTo(e.clientX, e.clientY);
context.stroke();
context.beginPath();
context.arc(e.clientX, e.clientY, radius, 0, Math.PI*2);
context.fill();
context.beginPath();
context.moveTo(e.clientX, e.clientY);
}}
var engage = function(){
dragging = true;
}
var disengage = function(){
dragging = false;
}
canvas.addEventListener('mousedown', engage);
canvas.addEventListener('mousemove', putPoint);
canvas.addEventListener('mouseup', disengage);
and this is how I am clearing the sketch:
// JavaScript Document
// bind event handler to clear button
document.getElementById('clear').addEventListener('click', function() {
context.clearRect(0, 0, canvas.width, canvas.height);
}, false);
A live preview can be seen at: http://www.sarahemily.net/canvas/
THANKS FOR YOUR HELP!
First the problem of the line starting in the wrong spot. You are forgetting to finish the path you create. You have beginPath, and moveTo but you leave it hanging. You need to call stroke once when the mouse button is up.
Smoothing.
Line smoothing is a very complicated thing to do with many professional drawing apps tackling the problem with a variety of solutions. There does not seem to be one agreed upon method. The big problem is.. How do you smooth a line but not destroy the desired line? and How do you do it quickly????
Here I present a two stage process.
Reduce the line complexity
Step one, reduce the line complexity. Sampling the mouse gives way to many points. So I need to reduce the number of points, but not lose any details.
I use the Ramer–Douglas–Peucker algorithm. It's quick and does a good job of reducing the complexity (number of points) of a line. Below you can find my implementation of the algorithm. It's not the best as it could do with some optimisation. You could most likely find it in some other language and port it to javascript.
It uses a recursive function to reduce complexity based on length and angle between line segments. At its core is the dot product of two line segments, it is a quick way of determining the angle between the two segments. See the supplied link above for more details.
// Line simplification based on
// the Ramer–Douglas–Peucker algorithm
// referance https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
// points: are and array of arrays consisting of [[x,y],[x,y],...,[x,y]]
// length: is in pixels and is the square of the actual distance.
// returns array of points of the same form as the input argument points.
var simplifyLineRDP = function(points, length) {
var simplify = function(start, end) { // recursive simplifies points from start to end
var maxDist, index, i, xx , yy, dx, dy, ddx, ddy, p1, p2, p, t, dist, dist1;
p1 = points[start];
p2 = points[end];
xx = p1[0];
yy = p1[1];
ddx = p2[0] - xx;
ddy = p2[1] - yy;
dist1 = (ddx * ddx + ddy * ddy);
maxDist = length;
for (var i = start + 1; i < end; i++) {
p = points[i];
if (ddx !== 0 || ddy !== 0) {
// dot product
t = ((p[0] - xx) * ddx + (p[1] - yy) * ddy) / dist1;
if (t > 1) {
dx = p[0] - p2[0];
dy = p[1] - p2[1];
} else
if (t > 0) {
dx = p[0] - (xx + ddx * t);
dy = p[1] - (yy + ddy * t);
} else {
dx = p[0] - xx;
dy = p[1] - yy;
}
}else{
dx = p[0] - xx;
dy = p[1] - yy;
}
dist = dx * dx + dy * dy
if (dist > maxDist) {
index = i;
maxDist = dist;
}
}
if (maxDist > length) { // continue simplification while maxDist > length
if (index - start > 1){
simplify(start, index);
}
newLine.push(points[index]);
if (end - index > 1){
simplify(index, end);
}
}
}
var end = points.length - 1;
var newLine = [points[0]];
simplify(0, end);
newLine.push(points[end]);
return newLine;
}
Smoothing using bezier curves
Next the smoothing. As the line has been simplified it if reasonably quick to then compare the angles between the many lines and create a bezier if the angle is below a required threshold.
Below is a example of how I do it. Though this will not fit the original line it is just concerned with smoothing. It is again a bit of a hack on my part and not based on any tried and tested algorithm. I have another one that does a bezier fit but that is too slow for the example.
Basicly it steps through the line segments and calculates the angle between two segments, if the angle is below the threshold it then adds bezier control points along the tangent of the two line segments, making either 2nd order or 3rd order beziers depending on whether two consecutive points are smoothed. This is a stripped down version of a much more complicated algorithm so excuse the mess.
// This is my own smoothing method The blindman`s smoother
// It creates a set of bezier control points either 2nd order or third order
// bezier curves.
// points: list of points [[x,y],[x,y],...,[x,y]]
// cornerThres: when to smooth corners and represents the angle between to lines.
// When the angle is smaller than the cornerThres then smooth.
// match: if true then the control points will be balanced.
// Function will make a copy of the points
// returns [[x,y],[x,y,bx,by],[x,y,b1x,b1y,b2x,b2y],.....] with x and y line points
// bx,by control points for 2nd order bezier and b1x,b1y,b2x,b2y the control
// points for 3rd order bezier. These are mixed as needed. Test the length of
// each point array to work out which bezier if any to use.
var smoothLine = function(points,cornerThres,match){ // adds bezier control points at points if lines have angle less than thres
var p1, p2, p3, dist1, dist2, x, y, endP, len, angle, i, newPoints, aLen, closed, bal, cont1, nx1, nx2, ny1, ny2, np;
function dot(x, y, xx, yy) { // get do product
// dist1,dist2,nx1,nx2,ny1,ny2 are the length and normals and used outside function
// normalise both vectors
dist1 = Math.sqrt(x * x + y * y); // get length
if (dist1 > 0) { // normalise
nx1 = x / dist1 ;
ny1 = y / dist1 ;
}else {
nx1 = 1; // need to have something so this will do as good as anything
ny1 = 0;
}
dist2 = Math.sqrt(xx * xx + yy * yy);
if (dist2 > 0) {
nx2 = xx / dist2;
ny2 = yy / dist2;
}else {
nx2 = 1;
ny2 = 0;
}
return Math.acos(nx1 * nx2 + ny1 * ny2 ); // dot product
}
newPoints = []; // array for new points
aLen = points.length;
if(aLen <= 2){ // nothing to if line too short
for(i = 0; i < aLen; i ++){ // ensure that the points are copied
newPoints.push([points[i][0],points[i][1]]);
}
return newPoints;
}
p1 = points[0];
endP =points[aLen-1];
i = 0; // start from second poitn if line not closed
closed = false;
len = Math.hypot(p1[0]- endP[0], p1[1]-endP[1]);
if(len < Math.SQRT2){ // end points are the same. Join them in coordinate space
endP = p1;
i = 0; // start from first point if line closed
p1 = points[aLen-2];
closed = true;
}
newPoints.push([points[i][0],points[i][1]])
for(; i < aLen-1; i++){
p2 = points[i];
p3 = points[i + 1];
angle = Math.abs(dot(p2[0] - p1[0], p2[1] - p1[1], p3[0] - p2[0], p3[1] - p2[1]));
if(dist1 !== 0){ // dist1 and dist2 come from dot function
if( angle < cornerThres*3.14){ // bend it if angle between lines is small
if(match){
dist1 = Math.min(dist1,dist2);
dist2 = dist1;
}
// use the two normalized vectors along the lines to create the tangent vector
x = (nx1 + nx2) / 2;
y = (ny1 + ny2) / 2;
len = Math.sqrt(x * x + y * y); // normalise the tangent
if(len === 0){
newPoints.push([p2[0],p2[1]]);
}else{
x /= len;
y /= len;
if(newPoints.length > 0){
var np = newPoints[newPoints.length-1];
np.push(p2[0]-x*dist1*0.25);
np.push(p2[1]-y*dist1*0.25);
}
newPoints.push([ // create the new point with the new bezier control points.
p2[0],
p2[1],
p2[0]+x*dist2*0.25,
p2[1]+y*dist2*0.25
]);
}
}else{
newPoints.push([p2[0],p2[1]]);
}
}
p1 = p2;
}
if(closed){ // if closed then copy first point to last.
p1 = [];
for(i = 0; i < newPoints[0].length; i++){
p1.push(newPoints[0][i]);
}
newPoints.push(p1);
}else{
newPoints.push([points[points.length-1][0],points[points.length-1][1]]);
}
return newPoints;
}
As I did not put that much thought into ease of use you will have to use the following function to render the resulting line.
var drawSmoothedLine = function(line){
var i,p;
ctx.beginPath()
ctx.moveTo(line[0][0],line[0][1])
for(i = 0; i < line.length-1; i++){
p = line[i];
p1 = line[i+1]
if(p.length === 2){ // linear
ctx.lineTo(p[0],p[1])
}else
if(p.length === 4){ // bezier 2nd order
ctx.quadraticCurveTo(p[2],p[3],p1[0],p1[1]);
}else{ // bezier 3rd order
ctx.bezierCurveTo(p[2],p[3],p[4],p[5],p1[0],p1[1]);
}
}
if(p.length === 2){
ctx.lineTo(p1[0],p1[1])
}
ctx.stroke();
}
So to use these to smooth a line. Simply capture the mouse points as you draw. When the done, then send the points to both functions in turn. Erase the drawn line and replace it with the new line. The is a bit of a lag between pen up and the smoothed result, but there is plenty of room for improvement in both functions.
To put it all together I have added a snippet below. The two bars at the top left control the smoothing and detail. The bottom bar controls the first function described above and the top controls the smoothing (bezier) the more red you see the smoother the lines and greater the detail reduction.
Middle mouse button clears or just restart.
Sorry, this was more work than I expected so the comments are a little sparse. I will improve the comments as time permits..
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
// mouse stuff
var mouse = {
x:0,
y:0,
buttonLastRaw:0, // user modified value
buttonRaw:0,
buttons:[1,2,4,6,5,3], // masks for setting and clearing button raw bits;
};
function mouseMove(event){
mouse.x = event.offsetX; mouse.y = event.offsetY;
if(mouse.x === undefined){ mouse.x = event.clientX; mouse.y = event.clientY;}
if(event.type === "mousedown"){ mouse.buttonRaw |= mouse.buttons[event.which-1];
}else if(event.type === "mouseup"){mouse.buttonRaw &= mouse.buttons[event.which+2];
}else if(event.type === "mouseout"){ mouse.buttonRaw = 0; mouse.over = false;
}else if(event.type === "mouseover"){ mouse.over = true; }
event.preventDefault();
}
canvas.addEventListener('mousemove',mouseMove);
canvas.addEventListener('mousedown',mouseMove);
canvas.addEventListener('mouseup' ,mouseMove);
canvas.addEventListener('mouseout' ,mouseMove);
canvas.addEventListener('mouseover' ,mouseMove);
canvas.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);
// Line simplification based on
// the Ramer–Douglas–Peucker algorithm
// referance https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
// points are and array of arrays consisting of [[x,y],[x,y],...,[x,y]]
// length is in pixels and is the square of the actual distance.
// returns array of points of the same form as the input argument points.
var simplifyLineRDP = function(points, length) {
var simplify = function(start, end) { // recursize simplifies points from start to end
var maxDist, index, i, xx , yy, dx, dy, ddx, ddy, p1, p2, p, t, dist, dist1;
p1 = points[start];
p2 = points[end];
xx = p1[0];
yy = p1[1];
ddx = p2[0] - xx;
ddy = p2[1] - yy;
dist1 = (ddx * ddx + ddy * ddy);
maxDist = length;
for (var i = start + 1; i < end; i++) {
p = points[i];
if (ddx !== 0 || ddy !== 0) {
t = ((p[0] - xx) * ddx + (p[1] - yy) * ddy) / dist1;
if (t > 1) {
dx = p[0] - p2[0];
dy = p[1] - p2[1];
} else
if (t > 0) {
dx = p[0] - (xx + ddx * t);
dy = p[1] - (yy + ddy * t);
} else {
dx = p[0] - xx;
dy = p[1] - yy;
}
}else{
dx = p[0] - xx;
dy = p[1] - yy;
}
dist = dx * dx + dy * dy
if (dist > maxDist) {
index = i;
maxDist = dist;
}
}
if (maxDist > length) { // continue simplification while maxDist > length
if (index - start > 1){
simplify(start, index);
}
newLine.push(points[index]);
if (end - index > 1){
simplify(index, end);
}
}
}
var end = points.length - 1;
var newLine = [points[0]];
simplify(0, end);
newLine.push(points[end]);
return newLine;
}
// This is my own smoothing method
// It creates a set of bezier control points either 2nd order or third order
// bezier curves.
// points: list of points
// cornerThres: when to smooth corners and represents the angle between to lines.
// When the angle is smaller than the cornerThres then smooth.
// match: if true then the control points will be balanced.
// Function will make a copy of the points
var smoothLine = function(points,cornerThres,match){ // adds bezier control points at points if lines have angle less than thres
var p1, p2, p3, dist1, dist2, x, y, endP, len, angle, i, newPoints, aLen, closed, bal, cont1, nx1, nx2, ny1, ny2, np;
function dot(x, y, xx, yy) { // get do product
// dist1,dist2,nx1,nx2,ny1,ny2 are the length and normals and used outside function
// normalise both vectors
dist1 = Math.sqrt(x * x + y * y); // get length
if (dist1 > 0) { // normalise
nx1 = x / dist1 ;
ny1 = y / dist1 ;
}else {
nx1 = 1; // need to have something so this will do as good as anything
ny1 = 0;
}
dist2 = Math.sqrt(xx * xx + yy * yy);
if (dist2 > 0) {
nx2 = xx / dist2;
ny2 = yy / dist2;
}else {
nx2 = 1;
ny2 = 0;
}
return Math.acos(nx1 * nx2 + ny1 * ny2 ); // dot product
}
newPoints = []; // array for new points
aLen = points.length;
if(aLen <= 2){ // nothing to if line too short
for(i = 0; i < aLen; i ++){ // ensure that the points are copied
newPoints.push([points[i][0],points[i][1]]);
}
return newPoints;
}
p1 = points[0];
endP =points[aLen-1];
i = 0; // start from second poitn if line not closed
closed = false;
len = Math.hypot(p1[0]- endP[0], p1[1]-endP[1]);
if(len < Math.SQRT2){ // end points are the same. Join them in coordinate space
endP = p1;
i = 0; // start from first point if line closed
p1 = points[aLen-2];
closed = true;
}
newPoints.push([points[i][0],points[i][1]])
for(; i < aLen-1; i++){
p2 = points[i];
p3 = points[i + 1];
angle = Math.abs(dot(p2[0] - p1[0], p2[1] - p1[1], p3[0] - p2[0], p3[1] - p2[1]));
if(dist1 !== 0){ // dist1 and dist2 come from dot function
if( angle < cornerThres*3.14){ // bend it if angle between lines is small
if(match){
dist1 = Math.min(dist1,dist2);
dist2 = dist1;
}
// use the two normalized vectors along the lines to create the tangent vector
x = (nx1 + nx2) / 2;
y = (ny1 + ny2) / 2;
len = Math.sqrt(x * x + y * y); // normalise the tangent
if(len === 0){
newPoints.push([p2[0],p2[1]]);
}else{
x /= len;
y /= len;
if(newPoints.length > 0){
var np = newPoints[newPoints.length-1];
np.push(p2[0]-x*dist1*0.25);
np.push(p2[1]-y*dist1*0.25);
}
newPoints.push([ // create the new point with the new bezier control points.
p2[0],
p2[1],
p2[0]+x*dist2*0.25,
p2[1]+y*dist2*0.25
]);
}
}else{
newPoints.push([p2[0],p2[1]]);
}
}
p1 = p2;
}
if(closed){ // if closed then copy first point to last.
p1 = [];
for(i = 0; i < newPoints[0].length; i++){
p1.push(newPoints[0][i]);
}
newPoints.push(p1);
}else{
newPoints.push([points[points.length-1][0],points[points.length-1][1]]);
}
return newPoints;
}
// creates a drawable image
var createImage = function(w,h){
var image = document.createElement("canvas");
image.width = w;
image.height =h;
image.ctx = image.getContext("2d");
return image;
}
// draws the smoothed line with bezier control points.
var drawSmoothedLine = function(line){
var i,p;
ctx.beginPath()
ctx.moveTo(line[0][0],line[0][1])
for(i = 0; i < line.length-1; i++){
p = line[i];
p1 = line[i+1]
if(p.length === 2){ // linear
ctx.lineTo(p[0],p[1])
}else
if(p.length === 4){ // bezier 2nd order
ctx.quadraticCurveTo(p[2],p[3],p1[0],p1[1]);
}else{ // bezier 3rd order
ctx.bezierCurveTo(p[2],p[3],p[4],p[5],p1[0],p1[1]);
}
}
if(p.length === 2){
ctx.lineTo(p1[0],p1[1])
}
ctx.stroke();
}
// smoothing settings
var lineSmooth = {};
lineSmooth.lengthMin = 8; // square of the pixel length
lineSmooth.angle = 0.8; // angle threshold
lineSmooth.match = false; // not working.
// back buffer to save the canvas allowing the new line to be erased
var backBuffer = createImage(canvas.width,canvas.height);
var currentLine = [];
mouse.lastButtonRaw = 0; // add mouse last incase not there
ctx.lineWidth = 3;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.strokeStyle = "black";
ctx.clearRect(0,0,canvas.width,canvas.height);
var drawing = false; // if drawing
var input = false; // if menu input
var smoothIt = false; // flag to allow feedback that smoothing is happening as it takes some time.
function draw(){
// if not drawing test for menu interaction and draw the menus
if(!drawing){
if(mouse.x < 203 && mouse.y < 24){
if(mouse.y < 13){
if(mouse.buttonRaw === 1){
ctx.clearRect(3,3,200,10);
lineSmooth.angle = (mouse.x-3)/200;
input = true;
}
}else
if(mouse.buttonRaw === 1){
ctx.clearRect(3,14,200,10);
lineSmooth.lengthMin = (mouse.x-3)/10;
input = true;
}
canvas.style.cursor = "pointer";
}else{
canvas.style.cursor = "crosshair";
}
if(mouse.buttonRaw === 0 && input){
input = false;
mouse.lastButtonRaw = 0;
}
ctx.lineWidth = 1;
ctx.fillStyle = "red";
ctx.fillRect(3,3,lineSmooth.angle*200,10);
ctx.fillRect(3,14,lineSmooth.lengthMin*10,10);
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillStyle = "#5F2"
ctx.strokeRect(3,3,200,10);
ctx.fillText("Smooth",5,2)
ctx.strokeRect(3,14,200,10);
ctx.fillText("Detail",5,13);
}else{
canvas.style.cursor = "crosshair";
}
if(!input){
ctx.lineWidth = 3;
if(mouse.buttonRaw === 1 && mouse.lastButtonRaw === 0){
currentLine = [];
drawing = true;
backBuffer.ctx.clearRect(0,0,canvas.width,canvas.height);
backBuffer.ctx.drawImage(canvas,0,0);
currentLine.push([mouse.x,mouse.y])
}else
if(mouse.buttonRaw === 1){
var lp = currentLine[currentLine.length-1]; // get last point
// dont record point if no movement
if(mouse.x !== lp[0] || mouse.y !== lp[1] ){
currentLine.push([mouse.x,mouse.y]);
ctx.beginPath();
ctx.moveTo(lp[0],lp[1])
ctx.lineTo(mouse.x,mouse.y);
ctx.stroke();
}
}else
if(mouse.buttonRaw === 0 && mouse.lastButtonRaw === 1){
ctx.textAlign = "center"
ctx.fillStyle = "red"
ctx.fillText("Smoothing...",canvas.width/2,canvas.height/5);
smoothIt = true;
}else
if(smoothIt){
smoothIt = false;
var newLine = smoothLine(
simplifyLineRDP(
currentLine,
lineSmooth.lengthMin
),
lineSmooth.angle,
lineSmooth.match
);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(backBuffer,0,0);
drawSmoothedLine(newLine);
drawing = false;
}
}
// middle button clear
if(mouse.buttonRaw === 2){
ctx.clearRect(0,0,canvas.width,canvas.height);
}
mouse.lastButtonRaw = mouse.buttonRaw;
requestAnimationFrame(draw);
}
draw();
.canC { width:1000px; height:500px;}
<canvas class="canC" id="canV" width=1000 height=500></canvas>
I would love to make the pen tool draw more smoothly
Use can use quadratic curves instead of lines:
ctx.quadraticCurveTo(cpx, cpy, x, y);
Example: http://www.w3schools.com/tags/canvas_quadraticcurveto.asp

Using Google NaCl to read pixels off a canvas - is it worth it?

I am using this library to track colored objects: http://trackingjs.com/examples/color_camera.html.
The library seems to be iterating over an array of pixels, that is feed off the webcam and track the colors.
The issue is that this algorithm is painstakingly slow. I am trying
to use this detection as part of a simple 2D game I am building on my
canvas and it slows it down to a crawl.
The game I am building will be run on only 1 PC, so I have full
control over which browser I can use.
It's obvious that this performance impact is caused by the loops that
are searching for the colors on the canvas.
I have figured that maybe I can use Google's NaCl (Native Client) to iterate over the 'pixel arrays' instead of Javascript. From what I know, a compiled language such as C++ is much faster than an interpreted language such as Javascript.
So the scenario is that I take the webcam feed, post it as a message to NaCl and then get back the detection coordinates.
The question is this:
Would I benefit from such a scenario?
PS: The reason I am asking instead of directly testing is because I never touched compiled languages. I come strictly from a WebDev background and I need to get some suggestions if this thing is worth trying or not, before I get my hands dirty with a brand new paradigm
The current source code:
In any case, this is the source code where the color tracking happens:
tracking.ColorTracker.prototype.calculateDimensions_ = function(cloud, total) {
var maxx = -1;
var maxy = -1;
var minx = Infinity;
var miny = Infinity;
for (var c = 0; c < total; c += 2) {
var x = cloud[c];
var y = cloud[c + 1];
if (x < minx) {
minx = x;
}
if (x > maxx) {
maxx = x;
}
if (y < miny) {
miny = y;
}
if (y > maxy) {
maxy = y;
}
}
return {
width: maxx - minx,
height: maxy - miny,
x: minx,
y: miny
};
};
/**
* Gets the colors being tracked by the `ColorTracker` instance.
* #return {Array.<string>}
*/
tracking.ColorTracker.prototype.getColors = function() {
return this.colors;
};
/**
* Gets the minimum dimension to classify a rectangle.
* #return {number}
*/
tracking.ColorTracker.prototype.getMinDimension = function() {
return this.minDimension;
};
/**
* Gets the maximum dimension to classify a rectangle.
* #return {number}
*/
tracking.ColorTracker.prototype.getMaxDimension = function() {
return this.maxDimension;
};
/**
* Gets the minimum group size to be classified as a rectangle.
* #return {number}
*/
tracking.ColorTracker.prototype.getMinGroupSize = function() {
return this.minGroupSize;
};
/**
* Gets the eight offset values of the neighbours surrounding a pixel.
* #param {number} width The image width.
* #return {array} Array with the eight offset values of the neighbours
* surrounding a pixel.
* #private
*/
tracking.ColorTracker.prototype.getNeighboursForWidth_ = function(width) {
if (tracking.ColorTracker.neighbours_[width]) {
return tracking.ColorTracker.neighbours_[width];
}
var neighbours = new Int32Array(8);
neighbours[0] = -width * 4;
neighbours[1] = -width * 4 + 4;
neighbours[2] = 4;
neighbours[3] = width * 4 + 4;
neighbours[4] = width * 4;
neighbours[5] = width * 4 - 4;
neighbours[6] = -4;
neighbours[7] = -width * 4 - 4;
tracking.ColorTracker.neighbours_[width] = neighbours;
return neighbours;
};
/**
* Unites groups whose bounding box intersect with each other.
* #param {Array.<Object>} rects
* #private
*/
tracking.ColorTracker.prototype.mergeRectangles_ = function(rects) {
var intersects;
var results = [];
var minDimension = this.getMinDimension();
var maxDimension = this.getMaxDimension();
for (var r = 0; r < rects.length; r++) {
var r1 = rects[r];
intersects = true;
for (var s = r + 1; s < rects.length; s++) {
var r2 = rects[s];
if (tracking.Math.intersectRect(r1.x, r1.y, r1.x + r1.width, r1.y + r1.height, r2.x, r2.y, r2.x + r2.width, r2.y + r2.height)) {
intersects = false;
var x1 = Math.min(r1.x, r2.x);
var y1 = Math.min(r1.y, r2.y);
var x2 = Math.max(r1.x + r1.width, r2.x + r2.width);
var y2 = Math.max(r1.y + r1.height, r2.y + r2.height);
r2.height = y2 - y1;
r2.width = x2 - x1;
r2.x = x1;
r2.y = y1;
break;
}
}
if (intersects) {
if (r1.width >= minDimension && r1.height >= minDimension) {
if (r1.width <= maxDimension && r1.height <= maxDimension) {
results.push(r1);
}
}
}
}
return results;
};

Categories