Slicing up a Rectangle - algorithm

I need to get AS3 Rectangle objects from a function receiving other Rectangles as parameters. The result is very similar to the slice tool in Photoshop. It is quite hard to explain, so here is a picture:
(source: free.fr)
The blue squares are the rectangles that are given as parameters and the green ones are the result. Given Rectangles can overlap, as seen on picture 2 or be out of frame.
I don't look for a graphical realisation but for a way to get Rectangle objects as result.
Do you know any lib to do that?

Looked like a fun problem, so I gave it a crack. My idea was to just brute force it by:
Determine which points where the corners of the generated rectangles could be.
Remove all duplicates from this list of points.
Check all rectangles that could theoretically be drawn where the rect would have all 4 corners in the list of point.
Filter out all invalid rectangles (it intersects with one of our original rectangles etc.)
Reduce all valid rectangles to the minimum amount needed (if a valid rectangle contains another valid rectangle the "child" is removed.
It seems to work (although I haven't tested extensively).
Here's a demo. Sorry about the color palette. I was winging it.
Here's the source code (could probably be optimized quite a bit):
package
{
import flash.display.*;
import flash.events.*;
import flash.geom.*;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.utils.getTimer;
public class Main extends Sprite {
private var m_colors : Array = [0xffaaaa, 0x77ff77, 0xaaaaff, 0xffff44, 0xff44ff, 0xaaffff, 0x444444, 0xffaa55, 0xaaff55, 0x55aaff, 0x55ffaa];
private var m_roomRect : Rectangle;
private var m_sourceRects : Vector.<Rectangle> = new Vector.<Rectangle>();
private var m_currentDragRect : Rectangle;
private var m_dragMousePoint : Point = new Point();
private var m_outputTextField : TextField;
public function Main() : void {
m_roomRect = new Rectangle(40, 40, 400, 400);
m_sourceRects.push(new Rectangle(60, 60, 60, 80));
m_sourceRects.push(new Rectangle(130, 220, 70, 80));
m_sourceRects.push(new Rectangle(160, 260, 100, 80));
this.stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent);
this.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent);
this.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseEvent);
var tf : TextField = new TextField();
tf.defaultTextFormat = new TextFormat("_sans", 12);
tf.text = "Click and drag blue rectangles to move them";
tf.autoSize = TextFieldAutoSize.LEFT;
tf.x = (m_roomRect.left + m_roomRect.right) / 2 - tf.width / 2;
tf.y = m_roomRect.top - tf.height;
this.stage.addChild(tf);
m_outputTextField = new TextField();
m_outputTextField.defaultTextFormat = tf.defaultTextFormat;
m_outputTextField.width = m_roomRect.width;
m_outputTextField.x = m_roomRect.x;
m_outputTextField.y = m_roomRect.bottom + 5;
this.stage.addChild(m_outputTextField);
redraw();
}
private function onMouseEvent(event : MouseEvent):void {
switch(event.type) {
case MouseEvent.MOUSE_DOWN:
checkMouseDownOnRect();
break;
case MouseEvent.MOUSE_MOVE:
checkMouseDrag();
break;
case MouseEvent.MOUSE_UP:
m_currentDragRect = null;
break;
}
}
private function checkMouseDownOnRect():void {
m_currentDragRect = null;
m_dragMousePoint = new Point(this.stage.mouseX, this.stage.mouseY);
for each(var sourceRect : Rectangle in m_sourceRects) {
if (sourceRect.containsPoint(m_dragMousePoint)) {
m_currentDragRect = sourceRect;
break;
}
}
}
private function checkMouseDrag():void {
if (m_currentDragRect != null) {
m_currentDragRect.x += this.stage.mouseX - m_dragMousePoint.x;
m_currentDragRect.y += this.stage.mouseY - m_dragMousePoint.y;
m_dragMousePoint.x = this.stage.mouseX;
m_dragMousePoint.y = this.stage.mouseY;
redraw();
}
}
private function redraw():void {
// calculate data
var time : int = getTimer();
var data : CalculationData = calculate();
var calcTime : int = getTimer() - time;
// draw room bounds
this.graphics.clear();
this.graphics.lineStyle(3, 0x0);
this.graphics.drawRect(m_roomRect.x, m_roomRect.y, m_roomRect.width, m_roomRect.height);
// draw generated rectangles
for (var i : int = 0; i < data.outputRects.length; i++) {
var color : int = m_colors[i % m_colors.length];
var rect : Rectangle = data.outputRects[i];
this.graphics.lineStyle(2, color, 0.5);
this.graphics.beginFill(color, 0.5);
this.graphics.drawRect(rect.x, rect.y, rect.width, rect.height);
this.graphics.endFill();
}
// draw horisontal lines (a line that crosses each red point) for debug purposes
for each (var lineY : int in data.lines) {
this.graphics.lineStyle(1, 0, 0.2);
this.graphics.moveTo(m_roomRect.x, lineY);
this.graphics.lineTo(m_roomRect.x + m_roomRect.width, lineY);
this.graphics.endFill();
}
// the original rectangles
for each (var sourceRect : Rectangle in m_sourceRects) {
this.graphics.lineStyle(2, 0x0);
this.graphics.beginFill(0x0000aa, 0.5);
this.graphics.drawRect(sourceRect.x, sourceRect.y, sourceRect.width, sourceRect.height);
this.graphics.endFill();
}
// draw all points that was used to generate the output rectangles for debug purposes
for each (var p : Point in data.points) {
this.graphics.lineStyle(0, 0, 0);
this.graphics.beginFill(0xff0000, 1);
this.graphics.drawCircle(p.x, p.y, 3);
this.graphics.endFill();
}
m_outputTextField.text = "Rect count: " + data.outputRects.length + " (calculation time: " + calcTime + "ms)";
}
private function calculate(): CalculationData {
// list of y coords for horisontal lines,
// which are interesting when determining which rectangles to generate
var lines : Vector.<int> = new Vector.<int>();
// list of all points which are interesting
// when determining where the corners of the generated rect could be
var points : Vector.<Point> = new Vector.<Point>();
// add the 4 corners of the room to interesting points
points.push(new Point(m_roomRect.left, m_roomRect.top));
points.push(new Point(m_roomRect.right, m_roomRect.top));
points.push(new Point(m_roomRect.left, m_roomRect.bottom));
points.push(new Point(m_roomRect.right, m_roomRect.bottom));
for (var i:int = 0; i < m_sourceRects.length; i++) {
var sourceRect : Rectangle = m_sourceRects[i];
// source rect is completely outside of the room, we shoud ignore it
if (!m_roomRect.containsRect(sourceRect) && !m_roomRect.intersects(sourceRect)) {
continue;
}
// push the y coord of the rect's top edge to the list of lines if it's not already been added
if (lines.indexOf(sourceRect.y) == -1) {
lines.push(sourceRect.y);
}
// push the y coord of the rect's bottom edge to the list of lines if it's not already been added
if (lines.indexOf(sourceRect.bottom) == -1) {
lines.push(sourceRect.bottom);
}
// add the 4 corners of the source rect to the list of interesting points
addCornerPoints(points, sourceRect);
// find the intersections between source rectangles and add those points
for (var j:int = 0; j < m_sourceRects.length; j++) {
if (j != i) {
var intersect : Rectangle = m_sourceRects[i].intersection(m_sourceRects[j]);
if (intersect.width != 0 && intersect.height != 0) {
addCornerPoints(points, intersect);
}
}
}
}
for (i = 0; i < lines.length; i++) {
// add the points where the horisontal lines intersect with the room's left and right edges
points.push(new Point(m_roomRect.x, lines[i]));
points.push(new Point(m_roomRect.right, lines[i]));
var lineRect : Rectangle = new Rectangle(m_roomRect.x, m_roomRect.y,
m_roomRect.width, lines[i] - m_roomRect.y);
// add all points where the horisontal lines intersect with the source rectangles
for (a = 0; a < m_sourceRects.length;a++) {
intersect = m_sourceRects[a].intersection(lineRect);
if (intersect.width != 0 && intersect.height != 0) {
addCornerPoints(points, intersect);
}
}
}
// clamp all points that are outside of the room to the room edges
for (i = 0; i < points.length; i++) {
points[i].x = Math.min(Math.max(m_roomRect.left, points[i].x), m_roomRect.right);
points[i].y = Math.min(Math.max(m_roomRect.top, points[i].y), m_roomRect.bottom);
}
removeDuplicatePoints(points);
var outputRects : Vector.<Rectangle> = new Vector.<Rectangle>();
var pointsHash : Object = { };
for (a = 0; a < points.length; a++) {
pointsHash[points[a].x + "_" + points[a].y] = true;
}
for (var a:int = 0; a < points.length; a++) {
for (var b:int = 0; b < points.length; b++) {
if (b != a && points[b].x > points[a].x && points[b].y == points[a].y) {
for (var c:int = 0; c < points.length; c++) {
// generate a rectangle that has its four corners in our points of interest
if (c != b && c != a && points[c].y > points[b].y && points[c].x == points[b].x) {
var r : Rectangle = new Rectangle(points[a].x, points[a].y, points[b].x - points[a].x, points[c].y - points[b].y);
// make sure the rect has the bottom left corner in one of our points
if (pointsHash[r.left+"_"+r.bottom]) {
var containsOrIntersectsWithSource : Boolean = false;
for (i = 0; i < m_sourceRects.length;i++) {
if (r.containsRect(m_sourceRects[i]) || r.intersects(m_sourceRects[i])) {
containsOrIntersectsWithSource = true;
break;
}
}
// we don't add any rectangles that either intersects with a source rect
// or completely contains a source rect
if (!containsOrIntersectsWithSource) {
outputRects.push(r);
}
}
}
}
}
}
}
trace("outputRects before cleanup:", outputRects.length);
combineOutputRects(outputRects)
trace("outputRects after cleanup", outputRects.length);
var data : CalculationData = new CalculationData();
data.outputRects = outputRects;
data.lines = lines;
data.points = points;
return data;
}
private function addCornerPoints(points : Vector.<Point>, rect : Rectangle) : void {
points.push(new Point(rect.left, rect.top));
points.push(new Point(rect.right, rect.top));
points.push(new Point(rect.left, rect.bottom));
points.push(new Point(rect.right, rect.bottom));
}
// removes all rectangle that are already contained in another rectangle
private function combineOutputRects(outputRects : Vector.<Rectangle>):Boolean {
for (var a : int = 0; a < outputRects.length; a++) {
for (var b : int = 0; b < outputRects.length; b++) {
if (b != a) {
if (outputRects[a].containsRect(outputRects[b])) {
trace("\tremoved rect " + outputRects[b] + ", it was contained in " + outputRects[a]);
outputRects.splice(b, 1);
b--;
a = 0;
}
}
}
}
return false;
}
private function removeDuplicatePoints(points : Vector.<Point>) : void {
var usedPoints : Object = {};
for (var i : int = 0; i < points.length; i++) {
if (usedPoints[points[i].toString()]) {
points.splice(i, 1);
i--;
} else {
usedPoints[points[i].toString()] = true;
}
}
}
}
}
import flash.geom.Point;
import flash.geom.Rectangle;
class CalculationData {
public var outputRects : Vector.<Rectangle> = new Vector.<Rectangle>;
public var lines : Vector.<int> = new Vector.<int>;
public var points : Vector.<Point> = new Vector.<Point>;
}

Related

Grouping points after cut plane three js

I found all intersection points between the object and plane, as in this great example. But now I want to connect these points between themselves (dividing into separate arrays) where the plane passes and connect them again. I tried to connect them by distance, but this does not give an effective result
//SORT POINTS DISTANCE
var pointsArray = []; //point after intersection
var sortedPoints = [];
var sortedPointsDis = [];
sortedPoints.push( pointsArray.pop() );
while( pointsArray.length ) {
var distance = sortedPoints[sortedPoints.length - 1].distanceTo( pointsArray[0] );
var index = 0;
for(var i = 1; i < pointsArray.length; i++) {
var tempDistance = sortedPoints[sortedPoints.length - 1].distanceTo( pointsArray[i] );
if( tempDistance < distance ) {
distance = tempDistance;
index = i;
}
}
sortedPoints.push( pointsArray.splice(index, 1)[0] );
sortedPointsDis.push( distance );
}
//GROUP POINTS
var result = [[]];
for(var i = 0; i < sortedPoints.length; i++) {
var lastArr = result[result.length - 1];
if( lastArr.length < 3 ) {
lastArr.push( sortedPoints[i] );
} else {
var distance = lastArr[0].distanceTo( sortedPoints[i] );
if( distance < sortedPointsDis[i - 1] ) {
result.push([]);
lastArr = result[result.length - 1];
}
lastArr.push(sortedPoints[i]);
}
}
JSfiddle.
Ideas? Examples? Thank in advance for your replies!
So, yes, this answer based on that one and extends it. The solution is rough and can be optimized.
I've used modified .equals() method of THREE.Vector3() (I hope it (or something similar) will be a part of the core one day as it's a very useful feature), taken from here:
THREE.Vector3.prototype.equals = function(v, tolerance) {
if (tolerance === undefined) {
return ((v.x === this.x) && (v.y === this.y) && (v.z === this.z));
} else {
return ((Math.abs(v.x - this.x) < tolerance) && (Math.abs(v.y - this.y) < tolerance) && (Math.abs(v.z - this.z) < tolerance));
}
}
The idea:
When we're getting points of intersection, to each point we add information about which face a point belongs to. It means that there are always pairs of points with the same face index.
Then, we recursively find all the contours our points form.
Also, all points mark as unchecked (.checked = false).
Find first unchecked point. Add it to the array of the current contour.
Find its pair point (with the same face index). Add it to the array of the current contour.
Find an unchecked point, the closest one to the point found last. Makr it as checked .checked = true.
Find its pair point (with the same face index). Mark it as checked .checked = true.
Check, if the last found point equals (with some tolerance) to the first found point (the beginning of the contour)
5.1. If no, then just add the last found point in the array of the current contour and go to step 3.
5.2. If yes, then clone the first point of the current contour and add it to the array of the current contour, add the contour to the array of contours.
Check, if we have have all points marked as checked.
6.1. If no, then go to step 1.
6.2. If yes, we finished. Return the array of contours.
Modified function of setting a point of intersection:
function setPointOfIntersection(line, plane, faceIdx) {
pointOfIntersection = plane.intersectLine(line);
if (pointOfIntersection) {
let p = pointOfIntersection.clone();
p.faceIndex = faceIdx;
p.checked = false;
pointsOfIntersection.vertices.push(p);
};
}
How to get contours and how to draw them:
var contours = getContours(pointsOfIntersection.vertices, [], true);
contours.forEach(cntr => {
let cntrGeom = new THREE.Geometry();
cntrGeom.vertices = cntr;
let contour = new THREE.Line(cntrGeom, new THREE.LineBasicMaterial({
color: Math.random() * 0xffffff
}));
scene.add(contour);
});
Where
function getContours(points, contours, firstRun) {
console.log("firstRun:", firstRun);
let contour = [];
// find first line for the contour
let firstPointIndex = 0;
let secondPointIndex = 0;
let firsPoint, secondPoint;
for (let i = 0; i < points.length; i++) {
if (points[i].checked == true) continue;
firstPointIndex = i;
firstPoint = points[firstPointIndex];
firstPoint.checked = true;
secondPointIndex = getPairIndex(firstPoint, firstPointIndex, points);
secondPoint = points[secondPointIndex];
secondPoint.checked = true;
contour.push(firstPoint.clone());
contour.push(secondPoint.clone());
break;
}
contour = getContour(secondPoint, points, contour);
contours.push(contour);
let allChecked = 0;
points.forEach(p => { allChecked += p.checked == true ? 1 : 0; });
console.log("allChecked: ", allChecked == points.length);
if (allChecked != points.length) { return getContours(points, contours, false); }
return contours;
}
function getContour(currentPoint, points, contour){
let p1Index = getNearestPointIndex(currentPoint, points);
let p1 = points[p1Index];
p1.checked = true;
let p2Index = getPairIndex(p1, p1Index, points);
let p2 = points[p2Index];
p2.checked = true;
let isClosed = p2.equals(contour[0], tolerance);
if (!isClosed) {
contour.push(p2.clone());
return getContour(p2, points, contour);
} else {
contour.push(contour[0].clone());
return contour;
}
}
function getNearestPointIndex(point, points){
let index = 0;
for (let i = 0; i < points.length; i++){
let p = points[i];
if (p.checked == false && p.equals(point, tolerance)){
index = i;
break;
}
}
return index;
}
function getPairIndex(point, pointIndex, points) {
let index = 0;
for (let i = 0; i < points.length; i++) {
let p = points[i];
if (i != pointIndex && p.checked == false && p.faceIndex == point.faceIndex) {
index = i;
break;
}
}
return index;
}
jsfiddle example r87.

Loading A world from external file webgl and HTML5

I am trying to load a world(kind of..) into webglfrom an external file which has information of vertex position and face positions but the problem is the file containing the data is very large..(about 100mb). In my approach I am using the file as buffer and have a single buffer in the init buffer which is over-written again and again. What I am doing is, I am reading the values for an object from the file and drawing it on the canvas, then over-writing the buffer with the data of other object in my scene and adding it to the scene. In short I am not saving the vertex and face information. While animating I am reading the entire file again and re-drawing. Its working fine with a file size of 20mb. but for file of large size I am not able to use high frame rate while animating. Which is not looking good.
My question is should I put all the vertex information into buffer and then draw the graphics and forget about the file…or my approach can be optimized…? Also if you can suggest any other method then it would be really helpful
try {
var fileInput = document.getElementById('fileInput');
var file = fileInput.files[0];
// read from filename
var reader = new FileReader();
reader.onload = function (e) {
var count=0;
var lastline=0;
var i;
var j;
var text = reader.result;
var lines = text.split("\r\n");
while(lastline<lines.length)
{
var vertices = [];
var VertexIndices = [];
var vertexNormals=[];
/////Position of the objects
for (i = lastline; i < lines.length; i++) {
if (lines[i] == "MESH_FACE_POSITION_LIST {") {
break;
}
}
for (j = i + 1; j < lines.length; j++) {
if (lines[j] == "}") {
break;
}
else {
var currentvertices = lines[j].split(" ");
for (var k = 0; k < currentvertices.length; k++) {
VertexIndices.push(parseInt(currentvertices[k]));//Check for ","
}
}
}
noOfVerticesForTriangles = VertexIndices.length;
for (i = j; i < lines.length; i++) {
if (lines[i] == "MODEL_POSITION_LIST {") {
break;
}
}
for (j = i + 1; j < lines.length; j++) {
if (lines[j] == "}") {
break;
}
else {
var currentvertices = lines[j].split(" ");
for (var k = 0; k < currentvertices.length; k++) {
vertices.push(parseFloat(currentvertices[k]));//Check for ","
}
}
}
noOfVertices = vertices.length / 3;
lastline=j;
//this is where i am calling the function to draw the graphics after reading the data for an object
initBuffers(vertices,VertexIndices);
drawScene();
}
}
reader.readAsText(file);
}
catch (e) {
}
}
Code for init buffer
function initBuffers(vertices,VertexIndices) {
vertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
vertexPositionBuffer.itemSize = 3;
vertexPositionBuffer.numItems = noOfVertices;
vertexIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(VertexIndices), gl.STATIC_DRAW);
vertexIndexBuffer.itemSize = 1;
vertexIndexBuffer.numItems = noOfVerticesForTriangles;
}
My question is should I put all the vertex information into buffer and
then draw the graphics and forget about the file…
Yes, this is pretty much how 3d works :)

Algorithms find shortest path to all cells on grid

I have a grid [40 x 15] with 2 to 16 units on it, and unknown amount of obstacles.
How to find the shortest path to all the units from my unit location.
I have two helper methods that we can consider as O(1)
getMyLocation() - return the (x, y) coordinates of my location on the grid
investigateCell(x, y) - return information about cell at (x,y) coordinates
I implemented A* search algorithm, that search simultaneously to all the directions. At the end it output a grid where each cell have a number representing the distance from my location, and collection of all the units on the grid. It performs with O(N) where N is the number of cells - 600 in my case.
I implement this using AS3, unfortunately it takes my machine 30 - 50 milliseconds to calculate.
Here is my source code. Can you suggest me a better way?
package com.gazman.strategy_of_battle_package.map
{
import flash.geom.Point;
/**
* Implementing a path finding algorithm(Similar to A* search only there is no known target) to calculate the shortest path to each cell on the map.
* Once calculation is complete the information will be available at cellsMap. Each cell is a number representing the
* number of steps required to get to that location. Enemies and Allies will be represented with negative distance. Also the enemy and Allys
* coordinations collections are provided. Blocked cells will have the value 0.<br><br>
* Worth case and best case efficiency is O(N) where N is the number of cells.
*/
public class MapFilter
{
private static const PULL:Vector.<MapFilter> = new Vector.<MapFilter>();
public var cellsMap:Vector.<Vector.<int>>;
public var allys:Vector.<Point>;
public var enemies:Vector.<Point>;
private var stack:Vector.<MapFilter>;
private var map:Map;
private var x:int;
private var y:int;
private var count:int;
private var commander:String;
private var hash:Object;
private var filtered:Boolean;
public function filter(map:Map, myLocation:Point, commander:String):void{
filtered = true;
this.commander = commander;
this.map = map;
this.x = myLocation.x;
this.y = myLocation.y;
init();
cellsMap[x][y] = 1;
excecute();
while(stack.length > 0){
var length:int = stack.length;
for(var i:int = 0; i < length; i++){
var mapFilter:MapFilter = stack.shift();
mapFilter.excecute();
PULL.push(mapFilter);
}
}
}
public function navigateTo(location:Point):Point{
if(!filtered){
throw new Error("Must filter before navigating");
}
var position:int = Math.abs(cellsMap[location.x][location.y]);
if(position == 0){
throw new Error("Target unreachable");
}
while(position > 2){
if(canNavigateTo(position, location.x + 1, location.y)){
location.x++;
}
else if(canNavigateTo(position, location.x - 1, location.y)){
location.x--;
}
else if(canNavigateTo(position, location.x, location.y + 1)){
location.y++;
}
else if(canNavigateTo(position, location.x, location.y - 1)){
location.y--;
}
position = cellsMap[location.x][location.y];
}
return location;
throw new Error("Unexpected filtering error");
}
private function canNavigateTo(position:int, targetX:int, targetY:int):Boolean
{
return isInMapRange(targetX, targetY) && cellsMap[targetX][targetY] < position && cellsMap[targetX][targetY] > 0;
}
private function excecute():void
{
papulate(x + 1, y);
papulate(x - 1, y);
papulate(x, y + 1);
papulate(x, y - 1);
}
private function isInMapRange(x:int, y:int):Boolean{
return x < cellsMap.length &&
x >= 0 &&
y < cellsMap[0].length &&
y >= 0;
}
private function papulate(x:int, y:int):void
{
if(!isInMapRange(x,y) ||
cellsMap[x][y] != 0 ||
hash[x + "," + y] != null ||
map.isBlocked(x,y)){
return;
}
// we already checked that is not block
// checking if there units
if(map.isEmpty(x,y)){
cellsMap[x][y] = count;
addTask(x,y);
}
else{
cellsMap[x][y] = -count;
if(map.isAlly(x,y, commander)){
hash[x + "," + y] = true;
allys.push(new Point(x,y));
}
else {
hash[x + "," + y] = true;
enemies.push(new Point(x,y));
}
}
}
private function addTask(x:int, y:int):void
{
var mapFilter:MapFilter = PULL.pop();
if(mapFilter == null){
mapFilter = new MapFilter();
}
mapFilter.commander = commander;
mapFilter.hash = hash;
mapFilter.map = map;
mapFilter.cellsMap = cellsMap;
mapFilter.allys = allys;
mapFilter..enemies = enemies;
mapFilter.stack = stack;
mapFilter.count = count + 1;
mapFilter.x = x;
mapFilter.y = y;
stack.push(mapFilter);
}
private function init():void
{
hash = new Object();
cellsMap = new Vector.<Vector.<int>>();
for(var i:int = 0; i < map.width;i++){
cellsMap.push(new Vector.<int>);
for(var j:int = 0; j < map.height;j++){
cellsMap[i].push(0);
}
}
allys = new Vector.<Point>();
enemies = new Vector.<Point>();
stack = new Vector.<MapFilter>();
count = 2;
}
}
}
You can use Floyd Warshall to find the shortest path between every pair of points. This would be O(|V|^3) and you would not have to run it for each unit, just once on each turn. It's such a simple algorithm I suspect it might be faster in practice than running something like BFS / Bellman Ford for each unit.

Why Is My Genetic Algorithm Terrible (Why Doesn't It Converge)?

I wrote a quick experiment with a genetic algorithm. It simply takes a grid of squares and tries to mutate their color to make them all yellow. It fails miserably and I can't seem to figure out why. I've included a link to JSFiddle that demonstrates working code, as well as a copy of the code in its entirety.
http://jsfiddle.net/mankyd/X6x9L/
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<div class="container">
<h1>The randomly flashing squares <i>should</i> be turning yellow</h1>
<div class="row">
<canvas id="input_canvas" width="100" height="100"></canvas>
<canvas id="output_canvas" width="100" height="100"></canvas>
</div>
<div class="row">
<span id="generation"></span>
<span id="best_fitness"></span>
<span id="avg_fitness"></span>
</div>
</div>
</body>
</html>
Note that the below javascript relies on jquery in a few places.
// A bit of code that draws several squares in a canvas
// and then attempts to use a genetic algorithm to slowly
// make those squares all yellow.
// Knobs that can be tweaked
var mutation_rate = 0.1; // how often should we mutate something
var crossover_rate = 0.6; // how often should we crossover two parents
var fitness_influence = 1; // affects the fitness's influence over mutation
var elitism = 1; // how many of the parent's generation to carry over
var num_offspring = 20; // how many spawn's per generation
var use_rank_selection = true; // false == roulette_selection
// Global variables for easy tracking
var children = []; // current generation
var best_spawn = null; // keeps track of our best so far
var best_fitness = null; // keeps track of our best so far
var generation = 0; // global generation counter
var clear_color = 'rgb(0,0,0)';
// used for output
var $gen_span = $('#generation');
var $best_fit = $('#best_fitness');
var $avg_fit = $('#avg_fitness');
var $input_canvas = $('#input_canvas');
var input_ctx = $input_canvas[0].getContext('2d');
var $output_canvas = $('#output_canvas');
var output_ctx = $output_canvas[0].getContext('2d');
// A spawn represents a genome - a collection of colored
// squares.
var Spawn = function(nodes) {
var _fitness = null; // a cache of our fitness
this.nodes = nodes; // the squares that make up our image
this.fitness = function() {
// fitness is simply a function of how close to yellow we are.
// This is defined through euclidian distance. Smaller fitnesses
// are better.
if (_fitness === null) {
_fitness = 0;
for (var i = 0; i < nodes.length; i++) {
_fitness += Math.pow(-nodes[i].color[0], 2) +
Math.pow(255 - nodes[i].color[1], 2) +
Math.pow(255 - nodes[i].color[2], 2);
}
_fitness /= 255*255*3*nodes.length; // divide by the worst possible distance
}
return _fitness;
};
this.mutate = function() {
// reset our cached fitness to unknown
_fitness = null;
var health = this.fitness() * fitness_influence;
var width = $output_canvas[0].width;
var height = $output_canvas[0].height;
for (var i = 0; i < nodes.length; i++) {
// Sometimes (most times) we don't mutate
if (Math.random() > mutation_rate) {
continue;
}
// Mutate the colors.
for (var j = 0; j < 3; j++) {
// colors can move by up to 32 in either direction
nodes[i].color[j] += 64 * (.5 - Math.random()) * health;
// make sure that our colors stay between 0 and 255
nodes[i].color[j] = Math.max(0, Math.min(255, nodes[i].color[j]));
}
}
};
this.draw = function(ctx) {
// This draw function is a little overly generic in that it supports
// arbitrary polygons.
ctx.save();
ctx.fillStyle = clear_color;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for (var i = 0; i < nodes.length; i++) {
ctx.fillStyle = 'rgba(' + Math.floor(nodes[i].color[0]) + ',' + Math.floor(nodes[i].color[1]) + ',' + Math.floor(nodes[i].color[2]) + ',' + nodes[i].color[3] + ')';
ctx.beginPath();
ctx.moveTo(nodes[i].points[0][0], nodes[i].points[0][1]);
for (var j = 1; j < nodes[i].points.length; j++) {
ctx.lineTo(nodes[i].points[j][0], nodes[i].points[j][1]);
}
ctx.fill();
ctx.closePath();
}
ctx.restore();
};
};
Spawn.from_parents = function(parents) {
// Given two parents, mix them together to get another spawn
var nodes = [];
for (var i = 0; i < parents[0].nodes.length; i++) {
if (Math.random() > 0.5) {
nodes.push($.extend({}, parents[0].nodes[i]));
}
else {
nodes.push($.extend({}, parents[1].nodes[i]));
}
}
var s = new Spawn(nodes);
s.mutate();
return s;
};
Spawn.random = function(width, height) {
// Return a complete random spawn.
var nodes = [];
for (var i = 0; i < width * height; i += 10) {
var n = {
color: [Math.random() * 256, Math.random() * 256, Math.random() * 256, 1],
points: [
[i % width, Math.floor(i / width) * 10],
[(i % width) + 10, Math.floor(i / width) * 10],
[(i % width) + 10, Math.floor(i / width + 1) * 10],
[i % width, Math.floor(i / width + 1) * 10],
]
};
nodes.push(n);
}
return new Spawn(nodes);
};
var select_parents = function(gene_pool) {
if (use_rank_selection) {
return rank_selection(gene_pool);
}
return roulette_selection(gene_pool);
};
var roulette_selection = function(gene_pool) {
var mother = null;
var father = null;
gene_pool = gene_pool.slice(0);
var sum_fitness = 0;
var i = 0;
for (i = 0; i < gene_pool.length; i++) {
sum_fitness += gene_pool[i].fitness();
}
var choose = Math.floor(Math.random() * sum_fitness);
for (i = 0; i < gene_pool.length; i++) {
if (choose <= gene_pool[i].fitness()) {
mother = gene_pool[i];
break;
}
choose -= gene_pool[i].fitness();
}
// now remove the mother and repeat for the father
sum_fitness -= mother.fitness();
gene_pool.splice(i, 1);
choose = Math.floor(Math.random() * sum_fitness);
for (i = 0; i < gene_pool.length; i++) {
if (choose <= gene_pool[i].fitness()) {
father = gene_pool[i];
break;
}
choose -= gene_pool[i].fitness();
}
return [mother, father];
};
var rank_selection = function(gene_pool) {
gene_pool = gene_pool.slice(0);
gene_pool.sort(function(a, b) {
return b.fitness() - a.fitness();
});
var choose_one = function() {
var sum_fitness = (gene_pool.length + 1) * (gene_pool.length / 2);
var choose = Math.floor(Math.random() * sum_fitness);
for (var i = 0; i < gene_pool.length; i++) {
// figure out the sume of the records up to this point. if we exceed
// our chosen spot, we've found our spawn.
if ((i + 1) * (i / 2) >= choose) {
return gene_pool.splice(i, 1)[0];
}
}
return gene_pool.pop(); // last element, if for some reason we get here
};
var mother = choose_one();
var father = choose_one();
return [mother, father];
};
var start = function() {
// Initialize our first generation
var width = $output_canvas[0].width;
var height = $output_canvas[0].height;
generation = 0;
children = [];
for (var j = 0; j < num_offspring; j++) {
children.push(Spawn.random(width, height));
}
// sort by fitness so that our best comes first
children.sort(function(a, b) {
return a.fitness() - b.fitness();
});
best_spawn = children[0];
best_fitness = best_spawn.fitness();
best_spawn.draw(output_ctx);
};
var generate = function(spawn_pool) {
// generate a new set of offspring
var offspring = [];
for (var i = 0; i < num_offspring; i++) {
var parents = select_parents(spawn_pool);
// odds of crossover decrease as we get closer
if (Math.random() * best_fitness < crossover_rate) {
var s = Spawn.from_parents(parents);
}
else {
// quick hack to copy our mother, with possible mutation
var s = Spawn.from_parents([parents[0], parents[0]]);
}
offspring.push(s);
}
// select a number of best from the parent pool (elitism)
for (var i = 0; i < elitism; i++) {
offspring.push(spawn_pool[i]);
}
// sort our offspring by fitness (this includes the parents from elitism). Fittest first.
offspring.sort(function(a, b) {
return a.fitness() - b.fitness();
});
// pick off the number that we want
offspring = offspring.slice(0, num_offspring);
best_spawn = offspring[0];
best_fitness = best_spawn.fitness();
best_spawn.draw(output_ctx);
generation++;
return offspring;
};
var average_fitness = function(generation) {
debugger;
var a = 0;
for (var i = 0; i < generation.length; i++) {
a += generation[i].fitness();
}
return a / generation.length;
};
//Draw yellow and then initialize our first generation
input_ctx.fillStyle = 'yellow';
input_ctx.fillRect(0, 0, input_ctx.canvas.width, input_ctx.canvas.height);
start();
// Our loop function. Use setTimeout to prevent things from freezing
var gen = function() {
children = generate(children);
$gen_span.text('Generation: ' + generation);
$best_fit.text('Best Fitness: ' + best_fitness);
$avg_fit.text('Avg. Fitness: ' + average_fitness(children));
if (generation % 100 === 0) {
console.log('Generation', generation);
console.log('Fitness', best_fitness);
}
setTimeout(gen, 1);
};
gen();​
I've commented the code to try to make parsing it easy. The basic idea is quite simple:
Select 1 or 2 parents from the current generation
Mix those one or two parents together
Mutate the result slightly and add it to the next generation
Select the best few parents (1 in the example) and add them to the next generation
Sort and slice off N results and use them for the next generation (potentially a mix of parents and offspring)
Rinse and repeat
The output never gets anywhere near yellow. It quickly falls into a steady state of a sort that looks awful. Where have I gone wrong?
Solved it. It was in the "from_parents" method:
if (Math.random() > 0.5) {
nodes.push($.extend({}, parents[0].nodes[i]));
}
else {
nodes.push($.extend({}, parents[1].nodes[i]));
}
The $.extend() was doing a shallow copy. The obvious solution was to either put true as the first argument which causes a deep copy. This, however, is incredibly slow performance-wise. The better solution was to remove the $.extend() from that chunk of code entirely and instead to move it up to the mutate() method, where I call $.extend() only if a node is actually about to be changed. In other words, it becomes a copy-on-write.
Also, the color I put in the fitness function was wrong :P

Making falling objects come at random angles in AS3

I am making a missile defense type of game and am trying to get the missiles to fall at random angles. I also need the bullet image to turn at the angle I am shooting at. I am very unfamiliar with angles in AS3 so I need some help.
Code:
import spark.components.Image;
public var missiles:Array;
public var bullets:Array;
public var playerLife:Number;
public var targetX:Number;
public var targetY:Number;
public function init():void {
startGame();
}
public function onEnterFrame(e:Event):void {
if(Math.random() <.05 ){
//make a new missle
var newMissile:Image = new Image();
//draw to is
newMissile.source = "assets/missileDown.jpg";
//position it
newMissile.x = Math.random() * stage.stageWidth;
//animate it
newMissile.addEventListener(Event.ENTER_FRAME, onMissileEnterFrame);
//add it to missle array
missiles.push(newMissile);
//add it to the screen
gameGroup.addElement(newMissile);
}
}
public function startGame():void {
//makes new arrays
//gets rid of old arrays
missiles = new Array();
bullets = new Array();
//set player life
playerLife = 5;
//show player life
playerHealth.text = String(playerLife);
//add animation and mouse interation
this.addEventListener(Event.ENTER_FRAME, onEnterFrame);
stage.addEventListener(MouseEvent.CLICK, fireWeapon);
//set game over alpha
gameEnd.alpha = 0;
reset.alpha = 0;
//set game start alpha
playerHealth.alpha = 1;
healthLabel.alpha = 1;
}
//updates the missle
public function onMissileEnterFrame(e:Event):void {
//reference to target
var targetMissile:Image = Image(e.currentTarget);
//move missle down
targetMissile.y += 10;
//if missle has gone too far, remove it and player loses life
if(targetMissile.y > stage.stageHeight) {
playerLife --;
removeMissile(targetMissile);
//show player life
playerHealth.text = String(playerLife);
}
//if player is dead, game over
if(playerLife <= 0) {
gameOver();
}
}
//update bullet
public function onBulletEnterFrame(e:Event):void {
//get reference to bullet
var thisBullet:Bullet = Bullet(e.currentTarget);
//animate towards point..
//calculate difference between current position and desired position
var diffX:Number = thisBullet.targX - thisBullet.x;
var diffY:Number = thisBullet.targY - thisBullet.y;
//move 10% of difference closer
thisBullet.x += diffX * .1;
thisBullet.y += diffY * .1;
//chekc for overlap between bullet and missles
for(var i:Number = 0; i < missiles.length; i++) {
//if they do overlap, remove missle
if( thisBullet.hitTestObject(missiles[i]) ) {
removeMissile(missiles[i]);
removeBullet(thisBullet);
break;
}
}
//if we're 'close enough' to the target position, remove bullet
if(Math.abs(diffX) < 10 && Math.abs(diffY) < 10) {
removeBullet(thisBullet);
}
}
//gets rid of a missle
public function removeMissile(targetMissile:Image):void {
//removes the missle from the missiles array
for(var i:Number = missiles.length - 1; i >= 0; i--) {
if(missiles[i] == targetMissile) {
missiles.splice(i,1);
break;
}
}
//don't animate anymore
targetMissile.removeEventListener(Event.ENTER_FRAME, onMissileEnterFrame);
//remove from stage
gameGroup.removeElement(targetMissile);
}
//removes bullet from stage
public function removeBullet(targetBullet:Bullet):void {
//stop animation
targetBullet.removeEventListener(Event.ENTER_FRAME, onBulletEnterFrame);
//remove from stage
gameGroup.removeElement(targetBullet);
}
//shoot a bullet at the mouse position
public function fireWeapon(e:MouseEvent):void {
//make a new bullet
var newBullet:Bullet = new Bullet();
newBullet.addEventListener(Event.ENTER_FRAME, onBulletEnterFrame);
//position near the earth in the center
var halfStage:Number = stage.stageWidth / 2;
newBullet.x = halfStage;
newBullet.y = 500;
//set target
newBullet.targX = stage.mouseX;
newBullet.targY = stage.mouseY;
//add it to the stage
gameGroup.addElement(newBullet);
}
//you lose
public function gameOver():void {
//remove missles
for(var i:Number = 0; i < missiles.length; i++) {
removeMissile(missiles[i]);
}
//stop interaction
stage.removeEventListener(MouseEvent.CLICK, fireWeapon);
//stop animation
this.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
//set game start alpha
playerHealth.alpha = 0;
healthLabel.alpha = 0;
//set game end alpha
gameEnd.alpha = 1;
reset.alpha = 1;
}
]]>
</fx:Script>
onEnterFrame
...
//position it
newMissile.x = Math.random() * stage.stageWidth;
//rotate it
newMissile.rotation = - (Math.random() * 60 - 30);
onMissileEnterFrame
...
//move missle down
//targetMissile.y += 10;
targetMissile.x -= 10 * Math.sin(targetMissile.rotation * Math.PI/180);
targetMissile.y += 10 * Math.cos(targetMissile.rotation * Math.PI/180);
fireWeapon
...
//set target
newBullet.targX = stage.mouseX;
newBullet.targY = stage.mouseY;
newBullet.rotation = - Math.atan((newBullet.x - newBullet.targX) / (newBullet.y - newBullet.targY)) * 180/Math.PI;

Resources