Related
I'm trying to optimize the layer order of paths in Illustrator so that when sent to a laser cutter, the end of one path is close to the start of the next path reducing the travel time of the laser between each cut.
I've come up with the following code, which works, but could be further optimized considering length of lines, or through an annealing process. I'm posting it here in case anyone else is Googling 'Laser cutting optimization' and doesn't want to write their own code. Also if anyone can suggest improvements to the below code, I'd love to hear them.
// For this script to work, all paths to be optimised need to be on layer 0.
// Create a new empty layer in position 1 in the layer heirarchy.
// Run the script, all paths will move from layer 0 to layer 1 in an optimized order.
// Further optimisation possible with 'Annealing', but this will be a good first run optimization.
// Load into Visual Studio Code, follow steps on this website
// https://medium.com/#jtnimoy/illustrator-scripting-in-visual-studio-code-cdcf4b97365d
// to get setup, then run code when linked to Illustrator.
function test() {
if (!app.documents.length) {
alert("You must have a document open.");
return;
}
var docRef = app.activeDocument;
function endToStartDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[0].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
function Optimize(items) {
var lastPath, closest, minDist, delIndex, curItem, tempItems = [];
var topLayer = app.activeDocument.layers[0];
var newLayer = app.activeDocument.layers[1];
for (var x = 1, len = items.length; x < len; x++) {
tempItems.push(items[x]);
}
lastPath = items[0];
lastPath.move(newLayer, ElementPlacement.PLACEATBEGINNING);
while (tempItems.length) {
closest = tempItems[0];
minDist = endToStartDistance(lastPath, closest);
delIndex = 0;
for (var y = 1, len = tempItems.length; y < len; y++) {
curItem = tempItems[y];
if (endToStartDistance(lastPath, curItem) < minDist) {
closest = curItem;
minDist = endToStartDistance(lastPath, closest);
delIndex = y;
}
}
$.writeln(minDist);
//closest.zOrder(ZOrderMethod.BRINGTOFRONT);
closest.move(newLayer, ElementPlacement.PLACEATBEGINNING);
lastPath = closest;
tempItems.splice(delIndex, 1);
}
}
var allPaths = [];
for (var i = 0; i < documents[0].pathItems.length; i++) {
allPaths.push(documents[0].pathItems[i]);
//$.writeln(documents[0].pathItems[i].pathPoints[0].anchor[0])
}
Optimize(allPaths);
}
test();
Version 2 of the above code, some changes include the ability to reverse paths if this results in a reduced distance for the cutting head to move between paths, and added comments to make the code easier to read.
// Create a new empty layer in position 1 in the layer heirarchy.
// Run the script, all paths will move from their current layer to layer 1 in an optimized order.
// Further optimisation possible with 'Annealing', but this will be a good first run optimization.
// Load into Visual Studio Code, follow steps on this website
// https://medium.com/#jtnimoy/illustrator-scripting-in-visual-studio-code-cdcf4b97365d
// to get setup, then run code when linked to Illustrator.aa
function main() {
if (!app.documents.length) {
alert("You must have a document open.");
return;
}
var docRef = app.activeDocument;
// The below function gets the distance between the end of the endPath vector object
// and the start of the startPath vector object.
function endToStartDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[0].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
// The below function gets the distance between the end of the endPath vector object
// and the end of the startPath vector object.
function endToEndDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[startPath.pathPoints.length - 1].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
// The below function iterates over the supplied list of tempItems (path objects) and checks the distance between
// the end of path objects and the start/end of all other path objects, ordering the objects in the layer heirarchy
// so that there is the shortest distance between the end of one path and the start of the next.
// The function can reverse the direciton of a path if this results in a smaller distance to the next object.
function Optimize(tempItems) {
var lastPath, closest, minDist, delIndex, curItem;
var newLayer = app.activeDocument.layers[1]; // There needs to be an empty layer in position 2 in the layer heirarchy
// This is where the path objects are moved as they are sorted.
lastPath = tempItems[0]; // Arbitrarily take the first item in the list of supplied items
tempItems.splice(0, 1); // Remove the first item from the list of items to be iterated over
lastPath.move(newLayer, ElementPlacement.PLACEATBEGINNING); // Move the first item to the first position in the new layer
while (tempItems.length) { // Loop over all supplied items while the length of this array is not 0.
// Items are removed from the list once sorted.
closest = tempItems[0]; // Start by checking the distance to the first item in the list
minDist = Math.min(endToStartDistance(lastPath, closest), endToEndDistance(lastPath, closest));
// Find the smallest of the distances between the end of the previous path item
// and the start / end of this next item.
delIndex = 0; // The delIndex is the index to be removed from the tempItems list after iterating through
// the entire list.
for (var y = 1, len = tempItems.length; y < len; y++) {
// Iterate over all items in the list, starting at item 1 (item 0 already being used above)
curItem = tempItems[y];
if (endToStartDistance(lastPath, curItem) < minDist || endToEndDistance(lastPath, curItem) < minDist) {
// If either the end / start distance to the current item is smaller than the previously
// measured minDistance, then the current path item becomes the new smallest entry
closest = curItem;
minDist = Math.min(endToStartDistance(lastPath, closest), endToEndDistance(lastPath, closest));
// The new minDistace is set
delIndex = y; // And the item is marked for removal from the list at the end of the loop.
}
}
if (endToEndDistance(lastPath, closest) < endToStartDistance(lastPath, closest)) {
reversePaths(closest); // If the smallest distance is yielded from the end of the previous path
// To the end of the next path, reverse the next path so that the
// end-to-start distance between paths is minimised.
}
closest.move(newLayer, ElementPlacement.PLACEATBEGINNING); // Move the closest path item to the beginning of the new layer
lastPath = closest; // The moved path item becomes the next item in the chain, and is stored as the previous item
// (lastPath) for when the loop iterates again.
tempItems.splice(delIndex, 1); // Remove the item identified as closest in the previous loop from the list of
// items to iterate over. When there are no items left in the list
// The loop ends.
}
}
function reversePaths(theItems) { // This code taken / adapted from https://gist.github.com/Grsmto/bfe1541957a0bb17972d
if (theItems.typename == "PathItem" && !theItems.locked && !theItems.parent.locked && !theItems.layer.locked) {
pathLen = theItems.pathPoints.length;
for (k = 0; k < pathLen / 2; k++) {
h = pathLen - k - 1;
HintenAnchor = theItems.pathPoints[h].anchor;
HintenLeft = theItems.pathPoints[h].leftDirection;
HintenType = theItems.pathPoints[h].pointType;
HintenRight = theItems.pathPoints[h].rightDirection;
theItems.pathPoints[h].anchor = theItems.pathPoints[k].anchor;
theItems.pathPoints[h].leftDirection = theItems.pathPoints[k].rightDirection;
theItems.pathPoints[h].pointType = theItems.pathPoints[k].pointType;
theItems.pathPoints[h].rightDirection = theItems.pathPoints[k].leftDirection;
theItems.pathPoints[k].anchor = HintenAnchor;
theItems.pathPoints[k].leftDirection = HintenRight;
theItems.pathPoints[k].pointType = HintenType;
theItems.pathPoints[k].rightDirection = HintenLeft;
}
}
}
var allPaths = []; // Grab every line in the document
for (var i = 0; i < documents[0].pathItems.length; i++) {
allPaths.push(documents[0].pathItems[i]);
// This could be better changed to the selected objects, or to filter only objects below a certain
// stroke weight so that raster paths are not affected, but cut paths are.
}
Optimize(allPaths); // Feed all paths in the document into the optimize function.
}
main(); // Call the main function, executing the above code.
I've just implemented a topological sort algorithm on my isometric game using this guide: https://mazebert.com/2013/04/18/isometric-depth-sorting/
The issue
Here's a little example (this is just a drawing to illustrate my problem because as we say, a picture is worth a thousand words), what I'm expecting is in left and the result of the topological sorting algorithm is in right
So in the right image, the problem is that the box is drawn BEFORE the character and I'm expecting it to be drawn AFTER like in the left image.
Code of the topological sorting algorithm (Typescript)
private TopologicalSort2() {
// https://mazebert.com/2013/04/18/isometric-depth-sorting/
for(var i = 0; i < this.Stage.children.length; i++) {
var a = this.Stage.children[i];
var behindIndex = 0;
for(var j = 0; j < this.Stage.children.length; j++) {
if(i == j) {
continue;
}
var b = this.Stage.children[j];
if(!a.isoSpritesBehind) {
a.isoSpritesBehind = [];
}
if(!b.isoSpritesBehind) {
b.isoSpritesBehind = [];
}
if(b.posX < a.posX + a.sizeX && b.posY < a.posY + a.sizeY && b.posZ < a.posZ + a.sizeZ) {
a.isoSpritesBehind[behindIndex++] = b;
}
}
a.isoVisitedFlag = 0;
}
var _sortDepth = 0;
for(var i = 0; i < this.Stage.children.length; ++i) {
visitNode(this.Stage.children[i]);
}
function visitNode(n: PIXI.DisplayObject) {
if(n.isoVisitedFlag == 0) {
n.isoVisitedFlag = 1;
if(!n.isoSpritesBehind) {
return;
}
for(var i = 0; i < n.isoSpritesBehind.length; i++) {
if(n.isoSpritesBehind[i] == null) {
break;
} else {
visitNode(n.isoSpritesBehind[i]);
n.isoSpritesBehind[i] = null;
}
}
n.isoDepth = _sortDepth++;
}
}
this.Stage.children.sort((a, b) => {
if(a.isoDepth - b.isoDepth != 0) {
return a.isoDepth - b.isoDepth;
}
return 0;
});
}
Informations
Player:
posX: [the x coordinate of the player]
posY: [the y coordinate of the player]
posZ: 0
sizeX: 1
sizeY: 1
sizeZ: 1
Box:
posX: [the x coordinate of the box]
posY: [the y coordinate of the box]
posZ: 0
sizeX: 3
sizeY: 1
sizeZ: 1
X and Y axis
Do you have any idea of the source of this problem? and maybe how to solve it?
The way to determine whether one object is before the other requires a bit more linear algebra.
First of all, I would suggest to translate the coordinates from the "world" coordinates to the "view" 2D coordinates, i.e. to the rows and columns of the display.
Note also that the original Z coordinate does not influence the sort order (imagine that an object would be lifted up along the Z axis: we can find a sort order where this move would not have any impact). So the above-mentioned translation could assume all points are at Z=0.
Let's take this set-up, but depicted from "above", so when looking along the Z axis down to the game floor:
In the picture there are 7 objects, numbered from 0 to 6. The line of view in the game would be from the bottom-left of this picture. The coordinate system in which I would suggest to translate some points is depicted with the red row/col axis.
The white diagonals in each object link the two points that would be translated and used in the algorithm. The assumption is that when one object is in front of another, their diagonal lines will not intersect. If they would, it would mean that objects are overlapping each other in the game world, which would mean they are like gasses, not solids :) I will assume this is not the case.
One object A could be in front of another object B when in the new coordinate system, the left-most column coordinate of B falls between the two column coordinates of A (or vice versa). There might not really be such an overlap when their Z coordinates differ enough, but we can ignore that, because when there is no overlap we can do no harm in specifying a certain order anyway.
Now, when the coordinates indicate an overlap, the coordinates of diagonals (of A and B) must be compared with some linear algebra formula, which will determine which one is in front of the other.
Here is your adapted function that does that:
topologicalSort() {
// Exit if sorting is a non-operation
if (this.Stage.children.length < 2) return;
// Add two translated coordinates, where each of the resulting
// coordinates has a row (top to bottom) and column
// (left to right) part. They represent a position in the final
// rendered view (the screen).
// The two pairs of coordinates are translations of the
// points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
// Z is ignored (0), since it does not influence the order.
for (let obj of this.Stage.children) {
obj.leftCol = obj.posY - obj.posX - obj.sizeX;
obj.rightCol = obj.posY - obj.posX + obj.sizeY;
obj.leftRow = obj.posY + obj.posX + obj.sizeX;
obj.rightRow = obj.posY + obj.posX + obj.sizeY;
obj.isoSpritesBehind = [];
}
for(let i = 0; i < this.Stage.children.length; i++) {
let a = this.Stage.children[i];
// Only loop over the next objects
for(let j = i + 1; j < this.Stage.children.length; j++) {
let b = this.Stage.children[j];
// Get the two objects in order of left column:
let c = b.leftCol < a.leftCol ? b : a;
let d = b.leftCol < a.leftCol ? a : b;
// See if they overlap in the view (ignoring Z):
if (d.leftCol < c.rightCol) {
// Determine which is behind: some linear algebra
if (d.leftRow <
(d.leftCol - c.leftCol)/(c.rightCol - c.leftCol)
* (c.rightRow - c.leftRow) + c.leftRow) {
// c is in front of d
c.isoSpritesBehind.push(d);
} else { // d is in front of c
d.isoSpritesBehind.push(c);
}
} // in the else-case it does not matter which one comes first
}
}
// This replaces your visitNode function and call:
this.Stage.children.forEach(function getDepth(obj) {
// If depth was already assigned, this node was already visited
if (!obj.isoDepth) {
// Get depths recursively, and retain the maximum of those.
// Add one more to get the depth for the current object
obj.isoDepth = obj.isoSpritesBehind.length
? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
: 1; // Depth when there is nothing behind it
}
return obj.isoDepth; // Return it for easier recursion
});
// Sort like you did, but in shorter syntax
this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
}
I add a snippet where I completed the class with a minimum of code, enough to make it run and output the final order in terms of object index numbers (as they were originally inserted):
class Game {
constructor() {
this.Stage = { children: [] };
}
addObject(posX, posY, posZ, sizeX, sizeY, sizeZ) {
this.Stage.children.push({posX, posY, posZ, sizeX, sizeY, sizeZ,
id: this.Stage.children.length}); // add a unique id
}
topologicalSort() {
// Exit if sorting is a non-operation
if (this.Stage.children.length < 2) return;
// Add two translated coordinates, where each of the resulting
// coordinates has a row (top to bottom) and column
// (left to right) part. They represent a position in the final
// rendered view (the screen).
// The two pairs of coordinates are translations of the
// points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
// Z is ignored (0), since it does not influence the order.
for (let obj of this.Stage.children) {
obj.leftCol = obj.posY - obj.posX - obj.sizeX;
obj.rightCol = obj.posY - obj.posX + obj.sizeY;
obj.leftRow = obj.posY + obj.posX + obj.sizeX;
obj.rightRow = obj.posY + obj.posX + obj.sizeY;
obj.isoSpritesBehind = [];
}
for(let i = 0; i < this.Stage.children.length; i++) {
let a = this.Stage.children[i];
// Only loop over the next objects
for(let j = i + 1; j < this.Stage.children.length; j++) {
let b = this.Stage.children[j];
// Get the two objects in order of left column:
let c = b.leftCol < a.leftCol ? b : a;
let d = b.leftCol < a.leftCol ? a : b;
// See if they overlap in the view (ignoring Z):
if (d.leftCol < c.rightCol) {
// Determine which is behind: some linear algebra
if (d.leftRow <
(d.leftCol - c.leftCol)/(c.rightCol - c.leftCol)
* (c.rightRow - c.leftRow) + c.leftRow) {
// c is in front of d
c.isoSpritesBehind.push(d);
} else { // d is in front of c
d.isoSpritesBehind.push(c);
}
} // in the else-case it does not matter which one comes first
}
}
// This replaces your visitNode function and call:
this.Stage.children.forEach(function getDepth(obj) {
// If depth was already assigned, this node was already visited
if (!obj.isoDepth) {
// Get depths recursively, and retain the maximum of those.
// Add one more to get the depth for the current object
obj.isoDepth = obj.isoSpritesBehind.length
? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
: 1; // Depth when there is nothing behind it
}
return obj.isoDepth; // Return it for easier recursion
});
// Sort like you did, but in shorter syntax
this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
}
toString() { // Just print the ids of the children
return JSON.stringify(this.Stage.children.map( x => x.id ));
}
}
const game = new Game();
game.addObject( 2, 2, 0, 1, 1, 1 );
game.addObject( 1, 3, 0, 3, 1, 1 );
game.addObject( 6, 1, 0, 1, 3, 1 );
game.addObject( 9, 3, 0, 1, 1, 1 );
game.addObject( 5, 3, 0, 1, 3, 1 );
game.addObject( 7, 2, 0, 1, 1, 1 );
game.addObject( 8, 2, 0, 3, 1, 1 );
game.topologicalSort();
console.log(game + '');
The objects in the snippet are the same as in the picture with the same numbers. The output order is [0,1,4,2,5,6,3] which is the valid sequence for drawing the objects.
I'm writing an A* pathing script for a game set on a 7x7 grid of tiles with the player always in the middle (tile 24). Zeros are added as a visual and it's actually one array, not a 7x7 2D array.
[00,01,02,03,04,05,06]
[07,08,09,10,11,12,13]
[14,15,16,17,18,19,20]
[21,22,23,24,25,26,27]
[28,29,30,31,32,33,34]
[35,36,37,38,39,40,41]
[42,43,44,45,46,47,48]
The game is server-driven so the player uses relative coordinates. What that means is, if the player moves, tile[0] changes. The short version of that is the player will always move from tile 24, which is the center tile. The grid is hard coded in, but if I post it publicly I'll change the code a little; no problem.
The function should take a destination and find a good path from tile 24 to that square but what it actually does it return "undefined".
If I input 24 I want the game to output an array like this
[18,12,6]
Here's the code:
z = 0;
function pathTo(goal){
var createPath = function (goal){
var createNode = function(i){
this.id = i;
this.g = Infinity;
this.f = Infinity;
this.parent = null;
this.open = null;
};
this.nodes = Array(49);
for(i=0;i<this.nodes.length;i++){
this.nodes[i] = new createNode(i);
}
this.start = this.nodes[24];
this.start.g = 0;
this.currentNodeId = 24;
this.goal = this.nodes[goal];
this.bestPath = null;
};//end createPath
var getBestNeighbor = function(nodeId){
z++
if(z>50){throw z}debugger;
console.log(nodeId);
var getG = function(parentG){
//here you can check the map for water, sand, and ruins penalties
/*
default = 1
path = .9
water = 3
*/
return (parentG + 1);
};
var closeNode = function (node){
node.open = false;
};//end closeNode
var getF = function(startId,endId,g){
if(g>9){
throw g;
}
var startX = startId % 7;
var startY = (startId - startX) / 7;
var endX = endId % 7;
var endY = (endId - endX) / 7;
var h = Math.sqrt( Math.pow((startX - endX) , 2 ) + Math.pow(( startY - endY ), 2 ) );
console.log("Start.id:"+startId+"H:"+h+" Start.id.g:"+g);
return (h + g);
};//end getF
var tracePath = function(tracedNode){
path.bestPath = [];
while(tracedNode != path.start){
path.bestPath.unshift(tracedNode.id);
tracedNode = tracedNode.parent;
}
return path.bestPath;
};//end tracePath
var getNeighborNodeId = function(x,y,currentId){return currentId + (y*7) + x;};//end getNeighborNodeId
if(path.bestPath === null){
var neighborNode = {};
var bestNode = {f: Infinity};
if(nodeId == path.goal.id){//may need to pass path
return tracePath(path.nodes[nodeId]);
}else{
for(x=-1;x<=1;x++){
for(y=-1;y<=1;y++){
var nnId = getNeighborNodeId(x,y,nodeId);
if(nnId==24){debugger}
if( ( (x!=0) && (y!=0) ) ||( (nnId>=0) && (nnId<=48))){
var neighborNode = path.nodes[nnId];
if(neighborNode.open === null){ neighborNode.open = true; }
if(neighborNode.open === true ){//don't check closed neighbors
if(typeof neighborNode === "object"){
neighborNode.parent = path.nodes[nodeId]
debugger;
neighborNode.g = getG(neighborNode.parent.g);
neighborNode.f = getF(neighborNode.id , path.goal.id , neighborNode.g);
if( neighborNode.f < bestNode.f){
bestNode = neighborNode;
}//endif
}//endif
}//endif Note: if the node isn't null or true, it's false.
}
}//endfor
}//endfor - Note: Here we should have the best neighbor
if(bestNode.f == Infinity){
closeNode(path.nodes[nodeId]);//need escape for no possible path
return;
}else{
//bestNode.parent = path.nodes[nodeId];
path.currentNodeId = bestNode.id;
getBestNeighbor(bestNode.id);
}//endelse
}//endelse
}//endif
};//end getBestNeighbor
var path = new createPath(goal);
while(path.bestPath === null){
getBestNeighbor(path.currentNodeId);
}//end while
return path.bestPath;
}//end pathTo
console.log(pathTo(41)); //testing with 6
and a JSFiddle link: https://jsfiddle.net/jb4xtf3h/
It's my first time not just slapping globals everywhere, so it may have a scope issue I'm not familiar with.
Most likely my issue is in the getNeighborId function; I don't think I have anything declaring a good node's parent.
The problem is that it goes NW three times instead of NE three times. That probably means I have a mistake in the getBestNeighbor function where I'm reading a -1 as a 1.
Also I don't think I'm escaping the recursive function correctly.
For some reason, when I put in 41 it gets really confused. This either has to do with how I set G and H which are classically used in A* to record distance traveled on this path and the estimated distance remaining. Specifically the G number is wrong because it's taking bad steps for some reason.
Here is the working code. I didn't implement walls or anything but I do show where you would do that. All you need to do is close all the nodes that are walls before you begin pathing and you can assign movement penalties if you want the AI to "know" to avoid water or sand.
I actually can't pin down a single problem but a major one was the way the statement:
if( ( (x!=0) && (y!=0) ) ||( (nnId>=0) && (nnId<=48))){
was changed to:
if( ( !(x==0 && y==0) && ( nnId>=0 && nnId<=48))){
The intent of this line was to prevent searching the tile you are standing on x,y = (0,0) and also to make sure that the neighbor you want to look at is on the grid (7x7 grid has 49 squares numbered 0-48)
What I was trying to say is "IF X & Y ARE BOTH NOT ZERO" apparently that actually makes it the same as an or statement so if either square was 0 it skipped it and tiles that needed that space were having problems since there were several directions that weren't working.
I hope that helps somebody if they need a nice simple pathing script I tried really hard to make the code readable and I'm not the strongest coder in the world but a working A* script in 100 lines that I think is fairly easy to follow. If you are reading this and you're not familiar with A* pathing what you might need to know is
H is your heuristic value it's an estimation of the remaining distance form a tile. In this code it's under the path object path.nodes[array#].h
G is the distance you've moved so far to get to that square path.nodes[array#].g.
F just adds h+g for the total value. This pseudocode on Wikipedia helped me write it.
var z = 0;
function pathTo(goal){
var createPath = function (goal){
var createNode = function(i){
this.id = i;
this.g = Infinity;
this.f = Infinity;
this.parent = null;
this.open = null;
};
this.nodes = Array(49);
for(i=0;i<this.nodes.length;i++){
this.nodes[i] = new createNode(i);
}
this.start = this.nodes[24];
this.start.g = 0;
this.currentNodeId = 24;
this.goal = this.nodes[goal];
this.bestPath = null;
};//end createPath
var path = new createPath(goal);
var getBestNeighbor = function(nodeId){
var getG = function(parentG){
//here you can check the map for water, sand, and ruins penalties
/*
default = 1
path = .9
water = 3
*/
return (parentG + 1);
};
var closeNode = function (node){
node.open = false;
};//end closeNode
var getF = function(startId,endId,g){
var startX = startId % 7;
var startY = (startId - startX) / 7;
var endX = endId % 7;
var endY = (endId - endX) / 7;
var h = Math.sqrt( Math.pow((startX - endX) , 2 ) + Math.pow(( startY - endY ), 2 ) );
return (h + g);
};//end getF
var tracePath = function(tracedNode){
path.bestPath = [];
while(tracedNode != path.start){
path.bestPath.unshift(tracedNode.id);
tracedNode = tracedNode.parent;
}
return path.bestPath;
};//end tracePath
var getNeighborNodeId = function(x,y,currentId){return currentId + (y*7) + x;};//end getNeighborNodeId
debugger;
z++
if(z>50){throw z}
if(path.bestPath === null){
var neighborNode = {};
var bestNode = {f: Infinity};
if(nodeId == path.goal.id){//may need to pass path
return tracePath(path.nodes[nodeId]);
}else{
for(y=-1;y<=1;y++){
for(x=-1;x<=1;x++){
var nnId = getNeighborNodeId(x,y,nodeId);
if( ( !(x==0 && y==0) && ( nnId>=0 && nnId<=48))){
var neighborNode = path.nodes[nnId];
if(path.nodes[nodeId].parent!=neighborNode){
if(neighborNode.open === null){ neighborNode.open = true; }
if(neighborNode.open === true ){//don't check closed neighbors
if(typeof neighborNode === "object"){
neighborNode.parent = path.nodes[nodeId]
neighborNode.g = getG(neighborNode.parent.g);
neighborNode.f = getF(neighborNode.id , path.goal.id , neighborNode.g);
if( neighborNode.f < bestNode.f){
bestNode = neighborNode;
}//endif
}//endif
}
}//endif Note: if the node isn't null or true, it's false.
}
}//endfor
}//endfor - Note: Here we should have the best neighbor
if(bestNode.f >= 50){
closeNode(path.nodes[nodeId]);//need escape for no possible path
return;
}else{
path.currentNodeId = bestNode.id;
getBestNeighbor(bestNode.id);
}//endelse
}//endelse
}//endif
};//end getBestNeighbor
while(path.bestPath === null){
getBestNeighbor(path.currentNodeId);
}//end while
return path.bestPath;
}//end pathTo
myPath = pathTo(41); //testing with 6
console.log("path2:"+myPath);
I am following exactly this tutorial on
Youtube :
https://www.youtube.com/watch?v=ohlrknEwobs&list=PLE10sFVGtI1ejzPFmX5CebHrTMhaCx2oI&index=31
The concept is to make an avoid-er game in which 3 objects fall vertically, one of which will increase point score, and the other two which will end the game.
I have created 4 objects within an array which are as follows:
var:objects Array = [new BlueBird(),new RedBird(),new Goomba(),new Ham()]
// objects[0] = Is a Blue Bird which jumps to scene 3 ("Death Scene")
// objects[1] = Is a Red Bird which jumps to scene 3 ("Death Scene")
// objects[2] = Is a Goomba which jumps to scene 3 ("Death Scene")
// objects[3] = Is an piece of Ham which adds Points
Here is the statement that I am having trouble with. I can't get the object[3], the piece of Ham, to test for else if statement. I coded in && objectsIndex == 3, but it did not work:
if((Pig.hit).hitTestObject(objects[objectsIndex]))
{
// Reset objects to y positions
objects[objectsIndex].x = randomRange(0,3) * 50
objects[objectsIndex].y = -50;
stage.removeEventListener(KeyboardEvent.KEY_DOWN,pressedButton);
stage.removeEventListener(Event.ENTER_FRAME,CollisionSensor);
stage.frameRate = 24;
gotoAndStop(1,"GG WP");
//trace ("you died!");
}
else if((Pig.hit).hitTestObject(objects[objectsIndex]) && objectsIndex == 3)
{
score = score + 1; //increase score by 1 point
text1.text = String(score); // Score is now a string value
// Since text1 is a text box, it must be denotated as a string
// Reset the objects to y positions
objects[objectsIndex].x = randomRange(0,3) * 50;
objects[objectsIndex].y = -50
}
Here is the entire Projects Code for Reference:
stop();
import flash.events.Event;
stage.focus = this;
stage.addEventListener(KeyboardEvent.KEY_DOWN, pressedButton);
stage.addEventListener(Event.ENTER_FRAME, CollisionSensor);
var score:Number = 0; //Player Score
var enemy_velocity:Number = 5;
var objects:Array = [new RedBird(),new Goomba,new BlueBird(),new Ham()];
// objects[0] is a RedBird
// objects[1] is a Goomba
// objects[2] is a BlueBird
// objects[3] is an piece of Ham
var objectsIndex:Number = randomRange(0,3); //Randomizer
//Picks a random array value
for (var i:int=0; i<objects.length; i++)
{
objects[i].x = randomRange(0,3) * 50;
objects[i].y = -50;
//Brings objects to stage
stage.addChild(objects[i]);
}
//Generate a ranodom number from minNum to maxNum including the endpoints
function randomRange(minNum:Number, maxNum:Number):Number
{
return (Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum);
}
function CollisionSensor(e:Event)
{
objects[objectsIndex].y = objects[objectsIndex].y + enemy_velocity;
// check if enemy objects have gone past the bottom of the game screen
if (objects[objectsIndex].y > stage.stageHeight)
{
objects[objectsIndex].y = -50; //Resets objects in array and parameters
// To enter frame in a repeated loop
//Randomizes a random x position for Objects
var num:Number = randomRange(0,3);
//This creates 4 random x positions - 0,50,100,150 To randomly place
//The array objects in
objects[objectsIndex].x = num * 50;
// now pick a random object
objectsIndex = randomRange(0,3);
// objects[0] is a RedBird
// objects[1] is a Goomba
// objects[2] is a BlueBird
// objects[3] is an piece of Ham
if(stage.frameRate <= 60)
{
stage.frameRate = stage.frameRate + 5;
}
else
{
stage.frameRate = 60;
if (enemy_velocity <= 25)
{
enemy_velocity += 1;
}
else
{
enemy_velocity = 25;
}
}
}
//check contanct with enemy objects
if((Pig.hit).hitTestObject(objects[objectsIndex]))
{
// Reset objects to y positions
objects[objectsIndex].x = randomRange(0,3) * 50
objects[objectsIndex].y = -50;
stage.removeEventListener(KeyboardEvent.KEY_DOWN,pressedButton);
stage.removeEventListener(Event.ENTER_FRAME,CollisionSensor);
stage.frameRate = 24;
gotoAndStop(1,"GG WP");
//trace ("you died!");
}
else if((Pig.hit).hitTestObject(objects[objectsIndex]) && objectsIndex == 3)
{
score = score + 1;
//increase score by 1 point
`text1.text = String(score);` // Score is now a string value
// Since text1 is a text box, it must be denotated as a string
// Reset the objects to y positions
objects[objectsIndex].x = randomRange(0,3) * 50;
objects[objectsIndex].y = -50
}
}
function pressedButton(event:KeyboardEvent):void
{
if (event.keyCode == Keyboard.LEFT && Pig.x >= 0)
{
Pig.x = Pig.x - 50;
}
else if (event.keyCode == Keyboard.RIGHT && Pig.x <= 150)
{
Pig.x = Pig.x + 50;
}
}
Thank you, and as always, have a nice day :)
You might have other issues with your code but this does look right
if((Pig.hit).hitTestObject(objects[objectsIndex]))
it should be..
if((Pig).hitTestObject(objects[0]))
where objects[0] would be the index in the array to access for testing against. But that might even have issues so get specific names of what you added on stage try this in your for loop that adds objects to stage
//Brings objects to stage
stage.addChild(objects[i]);
trace ("added object name : " + objects[i] );
Whatever name you got there is what you test against if( (Pig).hitTestObject(namehere) )
I'm trying to detect if two given div's are too close or collide/overlap .
I have the below codepen which tries to generate 20 random div's and only append them to body if their position isn't too close to other existing div.
That's the idea but it doesn't work as expected where i get div's that get through with close/overlapping positions to existing divs. (run it multiple times if first time is perfect and you should come across it).
http://codepen.io/anon/pen/fHLzj
Can anyone see the mistake and way to make it work?
This is somewhat hard to explain and get..but here goes:
check every div against every div by running for loop.
x,y,h,w
x is top-left corner's distance from left.
y is top-left corner's distance from top.
h is div's height.
w is div's width.
Point to consider... you don't really need to check every div..consider this
there are 10 divs...
First you will check 1st against 9.
Second one against 8.
.............
Eight one against 2.
Ninth one against 1.
And don't the last one.
Also it's a good idea to assign values and check for collisions in data, before assigning them to dom. Dom should be just for rendering final result.
I'll assume you want to keep none of the two colliding divs.
Preview
http://jsfiddle.net/techsin/m4fSf/6/
as expected code is huge
var
div={},
number=10,
size=20,
m = ele('main');
mw= parseFloat(getComputedStyle(m).getPropertyValue("width"))-size,
mh= parseFloat(getComputedStyle(m).getPropertyValue("height"))-size,
f=true,
nn;
var i
for (i = 0; i < number; i++) {
div[i] = {};
var t = true, newX, newY, nn;
if (i!=0){
while (t) {
newX = rand(mw);
newY = rand(mh);
for (nn = 0; nn < i; nn++) {
if (!(((newX > div[nn].x + size+5) || (newY > div[nn].y + size+5)) ||
((newX + size+5 < div[nn].x) || (newY + size+5 < div[nn].y)))) {
break;
}
if (nn == i-1) t = false;
}}} else {
newX = rand(mw);
newY = rand(mh);
}
console.log(newX);
div[i].x = newX;
div[i].y = newY;
}
for (i = 0; i < number; i++) {
render(div[i]);
}
console.log(div);
function render(x){
var d=document.createElement('div');
d.style.position='absolute';
d.style.left=(x.x+'px');
d.style.top=(x.y+'px');
m.appendChild(d);
}
function rand(x) { return Math.random()*x;}
function ele(x){return document.getElementById(x);}
this code is from my collision site...ill try and put it in the code above, but this what's needed to avoid collisions and close gaps.
if (xpost+30>xx.left && xx.left>xpost && xx.top+30>ypost && xx.top<ypost+30) { xspeed = -speed; }
if (xpost<xx.left+30 && xx.left<xpost && xx.top+30>ypost && xx.top<ypost+30) { xspeed = speed; }
if (ypost+30>xx.top && xx.top>ypost && xx.left+30>xpost && xx.left<xpost+30) { yspeed = -speed; }
if (ypost<xx.top+30 && xx.top<ypost && xx.left+30>xpost && xx.left<xpost+30) { yspeed = speed; }
How about using one of these libraries to detect the collisions for you?
http://sourceforge.net/projects/jquerycollision/
http://gamequeryjs.com/
I changed the collision logic. It detects if an object is close to another object by comparing the distance between the objects. I wrapped the logic in a do-while loop as well, so that it will keep attempting to find a position to place the square and you'll have exactly 20 squares.
This works:
var positions = []; //stroe positions of appended divs
var divsize = 20;
var topGap = 40; // gap from top
var leftGap = 80; //gap from left
function generateRandomPositionedDiv(){
for(var c = 0; c < 20; c++){
var color = '#'+ Math.round(0xffffff * Math.random()).toString(16);
$newdiv = $('<div/>').css({
'width':divsize+'px',
'height':divsize+'px',
'background-color': color
});
var posLeft;
var posTop;
var checkObj;
var collide = false;
posLeft = Math.floor((Math.random() * ($(document).width() - divsize)));//.toFixed();
posTop = Math.floor((Math.random() * ($(document).height() - divsize)));//.toFixed();
checkObj = {x: posLeft, y: posTop};
collide = checkForCollisions(checkObj);
if(!collide) {
positions.push({x: posLeft, y: posTop});
$newdiv.css({
'position':'absolute',
'left':posLeft+'px',
'top':posTop+'px'
});
$('body').append($newdiv);
}
}
}
/*function getPositions(box) {
var $box = $(box);
var pos = $box.position();
var width = $box.width();
var height = $box.height();
return [ [ pos.left, pos.left + width + leftGap ], [ pos.top, pos.top + height + topGap ] ];
}*/
function comparePositions(obj1, obj2) {
if(Math.abs(obj1.x - obj2.x) <= (divsize + leftGap) && Math.abs(obj1.y - obj2.y) <= (divsize + topGap)) {
return true;
} else {
return false;
}
}
function checkForCollisions(posObj){
for(var i = 0; i < positions.length; i++){
var match = comparePositions(positions[i], posObj);
if (match) {
//return true if two positions are close or overlapping
return match;
}
}
}
generateRandomPositionedDiv();