I'm developing a racing game like http://harmmade.com/vectorracer/ and I have implemented the A* algorithm to use for the AI players. The algorithm is working fine for 1-tile movements, but I don't want the AI players to only move 1 tile at a time (by using only their adjacent points), I need them to be able to accelerate and decelerate when they are closing in on a turn. Their next positions should depend on their previous one, just like Vector Racer.
public boolean createRoute() {
// The list where the points will be added in reverse order (from finish_point)
ArrayList<Track_Point> path = new ArrayList<>();
// The list where the unchecked points will be stored
ArrayList<Track_Point> open = new ArrayList<>();
// The list where the checked points will be stored
ArrayList<Track_Point> closed = new ArrayList<>();
// The starting point is always added as the first point to be checked
open.add(starting_point);
Track_Point current;
while (true) {
current = null;
// If all points from the open list have been removed (be being checked), it means that there isn't a possible path from the starting to the finish point
if (open.isEmpty()) {
System.out.println("no route available");
return false;
}
// Selects the point with the lowest F value from the open list
for (Track_Point temp : open) {
temp.show();
if (current == null || temp.getF() < current.getF()) {
current = temp;
}
}
// If the current point has reached the finish point, break the loop to construct the path
if (current.equals(finish_point)) {
break;
}
// Removes the current point (with the lowest F value) from the open list
open.remove(current);
// Adds the current point (with the lowest F value) to the closed list
closed.add(current);
ArrayList<Track_Point> possible_points = createNextPossibleTrackPoints(current);
//Sets the parent of the possible points
for (Track_Point tp : possible_points) {
if (!tp.equals(current)) {
tp.setParent(current);
}
}
for (Track_Point possible_point : possible_points) {
double nextG = current.getG() + current.distance(possible_point);
if (nextG < possible_point.getG()) {
open.remove(possible_point);
closed.remove(possible_point);
}
if (!open.contains(possible_point) && !closed.contains(possible_point)) {
possible_point.setParent(current);
open.add(possible_point);
}
}
}
//Track_Point current = finish_point;
while (current.getParent() != null) {
path.add(current);
current = current.getParent();
}
// optimalRacingLine is the list where all the points will be held in the correct order
optimalRacingLine.add(starting_point);
for (int k = path.size() - 1; k >= 0; k--) {
optimalRacingLine.add(path.get(k));
}
return true;
}
createPossiblePoints(Point current) so far returns a list of the current point's adjacents.
Each point's H value is calculated in their constructor, as I'm passing the finish point there and it calculates the distance between them.
Each point's G value is calculated when I set a parent to it, the G value is the distance from the new point to their parent + the parent's G value.
How do I modify this code to allow acceleration/deceleration?
The code of Track_Point:
package model;
import javafx.geometry.Point2D;
public class Track_Point extends Point2D {
private Track_Point parent, velocity;
private double f, g, h;
public Track_Point(double x, double y) {
super(x, y);
}
public Track_Point(double x, double y, Track_Point f) { // f is the finish point
super(x, y);
h = distance(f);
}
public void setParent(Track_Point tp) {
parent = tp;
g = distance(tp) + tp.getG();
f = g + h;
velocity = new Track_Point(getX() - parent.getX(), getY() - parent.getY());
}
public Track_Point getParent() {
return parent;
}
public double getG() {
return g;
}
public double getH() {
return h;
}
public double getF() {
return f;
}
public Track_Point getVelocity() {
return velocity;
}
#Override
public String toString() {
return "( " + (int) getX() + " , " + (int) getY() + " )";
}
public void show() {
System.out.println(toString());
}
}
Added some screenshots of my failed attempt and the working simple A* version
http://tinypic.com/r/zlakg2/8 - working version
http://tinypic.com/r/2e3u07o/8 - modified version (uses velocity as a parameter in the createNextPossiblePoints method)
Firstly, don't use an integers for the x/y position. There should be no such thing as '1 tile' in a racing game. Your game world and output can be completely different. For example, consider using doubles to store x and y. Ssh, don't worry, your JFrame doesn't need to know.
Heuristics
You are using A* to run your AI? Consider these additional heuristics:
Prefer high velocities; cost = max velocity - current velocity
Stay near inside edge of turn (imagine the turn as the outside edge of a circle); cost = distance_from(focus of turn)
Avoid walls; cost = isMovable(x, y) ? 0 : infinite/high
EDIT Prefer shortest path to avoid taking unnecessary moves as your second image does (Breadth First search not Djikstra); cost = steps from first node
The way A* works is as follows:
Use Djikstra (distance from origin) + Greedy (distance to target)
Insert your heuristics here
Add them all together and choose the lowest number
There's no such thing as f, g, or h; it's just mathematical nonsense you don't need to know.
Velocity
velocity = Math.abs(position1 - position2); so... position1 + velocity = position2.
You'll need to add the following variables:
int xVelocity
int yVelocity
Each moment, x += xVelocity; y += yVelocity.
The next position will be xf = x + xVelocity; yf = y + yVelocity. Then, you draw a ring around that position as follows:
+yVelocity
\|/
-xVelocity -0- +xVelocity
/|\
-yVelocity
So the center retains the same velocity, any adjacent side changes one velocity, and any diagonal side changes both velocities.
As for using A* the solution space of a turn is small enough that you can brute force it; don't add TrackPoints to the open list if you bump into a wall and prefer the highest velocity.
Really, that's all there is to it; simple stuff, but it can be tedious and difficult the first few times you need to do it.
EDIT: Just played vector racer and it's actually a lot simpler than I expected. I thought you were making a full blown 2d racing game. What I've told you is still very applicable, but you'll need to make a few adjustments, particularly to the way you handle rotation. You'll definitely want to look up the racing line. I don't have the time at the moment to go over the mathematics of the racing line, but this should help you calculate it.
EDIT2: Updated Velocity section. I'll do some calculations to figure out a faster heuristic, but what is present is enough to check 3-10 moves ahead without major performance issues.
Related
I am working on a project that is a basic robot application. I am using a few classes and enums and am to use my enums to move around, and here I am a bit confused..
So I am working on my main class right now -Robo. Here is the uml for Robo/what I am doing just for reference:
-currenLocate : Point
- directionFacing : FacingDirection
-map : FloorMap
- Carryingthing : boolean
-successfullyDropped : boolean
+Robo(map : FloorMap)
+Robo(map : FloorMap,currentLocatiom.
: Point)
+move(command : RoboCommand) : void
+iscarryingThing() : boolean
+isSuccessfullyDropped() :boolean.
Ok, so I am working on my move(), and I need to take into consideration my enums, FacingDirection(All 4 directions), MapObject (items to pick up) and then my movement enum(Forward, left, right, pickup and drop). And then my two booleans.
So I know I need to get my direction facing and take that into consideration when entering a command by my enum, but what do you suppose would be my best course of action? I am thinking else and else if and some &&, and an array perhaps but now I'm not totally sure. I have to do a forwardfromup, forwardfromdown, etc. I can give more info if needed, but I don't want to bog this post down with too much if I can help it. =D. I appreciate your time and thanks in advance for any assistance/tips.
You are not giving complete information so some assumptions:
The FloorMap represents a 2D area of Points with x,y coordinates (0-based)
A Point represents a single x,y coordinate and optionally a MapObject
FacingDirections are up/down (y-axis) and left/right (x-axis)
Robo will always be at a point on the floor facing a certain direction
A RoboCommand can be: move forward, turn left, turn right, pick up, drop
So for example Robo can be in the FloorMap at Point (4,2) facing up:
........
....a...
........
...^....
........
The point at (5,4) holds a MapObject (a)
To get the MapObject, Robo would need to execute the commands:
forward, forward, right, forward, pick up
So much for the context.
To start implementing this, you would need to create your domain classes:
FloorMap, Point, MapObject
FacingDirection
RoboCommand
You could implement the commands following the command pattern using an enum (although I prefer separate classes for this).
public enum RoboCommand {
FORWARD() {
void execute(Robo robo) {
// determine the new coordinates by adding the appropriate deltas
long x = robo.getCurrentLocation().getX() + robo.getFacingDirection().deltaX();
long y = robo.getCurrentLocation().getY() + robo.getFacingDirection().deltaY();
// get (or create) the matching Point from the floorMap
robo.getFloorMap().getPoint(x, y);
// move Robo to the new point
robo.setCurrentLocation(point);
}
}, RIGHT {
void execute(Robo robo) {
//use the left/right logic in FacingDirection
FacingDirection direction = robo.getFacingDirection().right();
// face Robo in the new direction
robo.setFacingDirection(direction);
}
}, LEFT {
void execute(Robo robo) {
robo.setFacingDirection(robo.getFacingDirection().left());
}
}, PICK_UP {
void execute(Robo robo) {
// get the optional item from the current Point
robo.getCurrentLocation().getItem()
.ifPresent(item -> {
// remove it from the Point
robo.getCurrentLocation().setItem(null);
// add it to Robo
robo.setCarried(item);
});
}
}, DROP {
void execute(Robo robo) {
// get the optional carried item from Robo
robo.getCarried()
.ifPresent(item -> {
// remove it from Robo
robo.setCarried(null);
// add it to the Point
robo.getCurrentLocation().setItem(item);
});
}
};
abstract void execute(Robo robo);
}
Some thoughts about the domain design.
I would like to design the FloorMap as a set, rather than an array, of Points. Only Points which hold items or have been visited by Robo will exist in the set.
class FloorMap {
Set<Point> points = new HashSet<>();
Point getPoint(long x, long y) {
// looks up the point, creating a new one if necessary
return points.stream()
.filter(p -> p.getX() == x && p.getY() == y)
.findAny()
.orElse(newPoint(x, y));
}
private Point newPoint(long x, long y) {
Point point = new Point(x, y);
points.add(point);
return point;
}
}
If Robo walks around a lot, one could envision a clean up algorithm which removes empty Points. One needs to think of a way to stop the Point that is currently visited by Robo to be cleaned up.
The Robo will have a Point as its currentLocation, referring to a Point in the FloorMap, and an optional MapObject:
class Robo {
Point currentLocation;
FacingDirection facingDirection;
MapObject carried;
Robo(FloorMap floorMap) {
currentLocation = floorMap.getPoint(0, 0);
facingDirection = FacingDirection.UP;
}
Optional<MapObject> getCarried() {
return Optional.ofNullable(carried);
}
}
A Point will have an optional item:
class Point{
long x;
long y;
MapObject item;
Point(long x, long y) {
this.x = x;
this.y = y;
}
Optional<MapObject> getItem() {
return Optional.ofNullable(mapObject);
}
}
The FacingDirection enum in my design has delta methods to indicate the changes to x and y coordinates and left/right methods which return a new FacingDirection:
enum FacingDirection {
UP() {
int deltaX() {
return 0;
}
int deltaY() {
return 1;
}
FacingDirection left() {
return LEFT;
}
FacingDirection right() {
return RIGHT;
}
},
RIGHT() {
int deltaX() {
return 1;
}
int deltaY() {
return 0;
}
FacingDirection left() {
return UP;
}
FacingDirection right() {
return DOWN;
}
},
// etc...
abstract int deltaX();
abstract int deltaY();
abstract FacingDirection left();
abstract FacingDirection right();
}
So, after many years of OOP, I got a pretty simple homework assignment from one of my university courses to implement a simple object-oriented structure.
The requested design:
Implement an objected oriented solution for creating the following shapes:
Ellipse, Circle, Square, Rectangle, Triangle, Parallelogram.
Each shape created must have the following parameters : unique id, color.
And following functions: color change, move, area, circumference, is inside, copy.
No validity tests are needed (not in the classes and not from user input).
My design:
Overall a pretty simple approach, shape_class / non-circular are abstract, and rectangle/square are combined into one class since they contain exactly the same parameters and no validity tests are needed (no reason to split them into two).
Shape class - implements a static id (unique id), and an init function dealing with the color name.
public abstract class shape_class {
static int STATIC_ID;
int id;
String color_name;
public shape_class(String color_name_input) {
this.id = STATIC_ID;
shape_class.STATIC_ID+=1;
if (Arrays.asList(toycad_globals.ALLOWED_COLORS).contains(color_name_input))
{
this.color_name = color_name_input;
}
}
public void change_color(String color_name_input) {
if (Arrays.asList(toycad_globals.ALLOWED_COLORS).contains(color_name_input)) {
this.color_name = color_name_input;
}
}
public abstract shape_class return_copy();
public abstract void move(double x, double y);
public abstract double area();
public abstract double circumference();
public abstract boolean is_inside(double x, double y);
}
** Non-circular** - Receives an array of points (Which define the object) and implements almost all required functions.
public abstract class non_circullar extends shape_class {
List<line> line_list = new ArrayList<line>();
List<point> point_list = new ArrayList<point>();
non_circullar(String color_name, point...input_point_list) {
super(color_name);
this.point_list = Arrays.asList(input_point_list);
for (int current_index =0; current_index< (input_point_list.length); current_index++) {
point current_first_point = input_point_list[current_index];
point current_second_point = input_point_list[(current_index+1)%input_point_list.length];
this.line_list.add(new line(current_first_point, current_second_point));
}
}
public point[] get_point_list_copy() {
int index = 0;
point [] new_array = new point[this.point_list.size()];
for (point current_point:this.point_list) {
new_array[index] = current_point.return_copy();
index+=1;
}
return new_array;
}
public double circumference() {
double sum = 0;
for (line current_line :this.line_list) {
sum += current_line.get_length();
}
return sum;
}
public void move(double x, double y) {
for (point current_point :this.point_list) {
current_point.move(x, y);
}
}
public boolean is_inside(double x, double y) {
int i;
int j;
boolean result = false;
for (i = 0, j = this.point_list.size() - 1; i < this.point_list.size(); j = i++) {
if ((this.point_list.get(i).y > y) != (this.point_list.get(j).y > y) &&
(x < (this.point_list.get(j).x - this.point_list.get(i).x) * (y - this.point_list.get(i).y) /
(this.point_list.get(j).y-this.point_list.get(i).y) + this.point_list.get(i).x))
{
result = !result;
}
}
return result;
}
int get_top_left_line_index() {
int top_left_line_index = 0;
int index = 0;
point best_point = this.line_list.get(0).get_average_point();
point current_point;
for (line current_line :this.line_list) {
current_point = current_line.get_average_point();
if (current_point.x < best_point.x) {
best_point = current_point;
top_left_line_index = index;
} else if (current_point.x == best_point.x && current_point.y > best_point.y) {
best_point = current_point;
top_left_line_index = index;
}
index +=1;
}
return top_left_line_index;
}
}
The problem:
For this assignment 40 points were reduced for design issues:
1) Circle is an ellipse and thus needs to inherit from it (Even though they share no parameters).
2) Rectangle / Square are two different entities even though in this implementation they are exactly the same (no validity tests).
I would be happy to get some inputs from the community regarding this design, are the design issues 'legit' or not, and what could have been done better?
Edit 1:
An ellipse is expressed by : two points and d (For a point to be on the ellipse the distance between it and the two points must be equal to d).
A circle is expressed by : center and radius.
I find it very hard to understand how they can share common params.
I suggest you follow this scheme:
You need to categorize shapes by the number of the edges first and then by the common characteristics. Then you have to recognize the following facts:
circle is just a special type of ellipse
square is just a special type of rectangle
both rectangle and parallelogram have 4 edges
unlikeparallelogram, rectangle have all the angles of 90°.
This is a simplified scheme according to your needs:
Ellipse, Circle, Square, Rectangle, Triangle, Parallelogram
Edit: Note that there exists the following hierarchy as well. Both rectangle and parallelogram have the opposite edges of the same length. Finally, it depends on the preferred interpretation and on what suits your situation better (thanks to #Federico klez Culloca):
Quadrilateral <- Parallelogram <- Rectangle <- Square
Make it scalable: In case of more complex shapes of elementary geometry included, I'd put probably place polygon below shape and then differentiate the descendants by the convexity and non-convexity first.
The design you have used is not idea (IMHO).
First, rename non-circular into Polygon (Also, us uppercase for the first letter).
Based on the implementation, a Circle is a specific Ellipse so I would have used inheritance here
Shape < -- Circular < -- Ellipse < -- Circle
< -- Polygon < -- Triangle < -- Equilateral
< -- ... //don't know the english names of those triangles
< -- Quadrilateral < -- Square
< -- Rectangle
< -- ...
< -- Hexagon
< -- ...
Each subclass of Polygon are abstract, those are used for the validation of the number of corners.
In general, I would have linked Square and Rectangle based on the geometry rule (same width and heigth) ( Square extends Rectangle) but based on your implementation using Point and Line, this is not required.
But using two classes would still allows some validation in the future ( every Line for a Square need to have the same length, ...).
This shows that a design depends mostly on the requirement, not only on the subject.
About Ellipse and Circle. An Ellipse is form of two points, if those point are the same, this is a Circle, this can be a link ;)
I am using a code I got from a site for a heartbeat sensor. The signal, when displayed by this code looks something like this:
Could you help me add a check which will increment an integer every time a signal goes above a certain threshold? This needs to happen for 10 seconds only, after 10 seconds the check stops and then gets multiplied by 6 to display the amount of beats per minute.
The code I'm using gets the imaging done, I would like to add the beats per minute onto it.
import processing.serial.*;
Serial myPort; // The serial port
int xPos = 1; // horizontal position of the graph
float oldHeartrateHeight = 0; // for storing the previous reading
void setup () {
// set the window size:
size(600, 400);
frameRate(25);
// List available serial ports.
println(Serial.list());
// Setup which serial port to use.
// This line might change for different computers.
myPort = new Serial(this, Serial.list()[1], 9600);
// set inital background:
background(0);
}
void draw () {
}
void serialEvent (Serial myPort) {
// read the string from the serial port.
String inString = myPort.readStringUntil('\n');
if (inString != null) {
// trim off any whitespace:
inString = trim(inString);
// convert to an int
println(inString);
int currentHeartrate = int(inString);
// draw the Heartrate BPM Graph.
float heartrateHeight = map(currentHeartrate, 0, 1023, 0, height);
stroke(0,255,0);
line(xPos - 1, height - oldHeartrateHeight, xPos, height - heartrateHeight);
oldHeartrateHeight = heartrateHeight;
// at the edge of the screen, go back to the beginning:
if (xPos >= width) {
xPos = 0;
background(0);
} else {
// increment the horizontal position:
xPos++;
}
}
}
Disclaimer: Of course, it goes without saying, this is only a guideline. Don't hook it up to someone's heart without thorough testing!
The simplest of checks is to look out for whenever the signal crosses a virtual line - typically the midpoint, like so:
That immediately makes things easier - we just need to check when our latest value is above the line, and the previous one is below it; whenever that happens, our signal must've crossed the line. That's as simple as this, using 750 as your midpoint:
int currentHeartrate = int(inString);
int midpoint=750;
if(currentHeartrate >= midpoint && oldHeartrateHeight < midpoint){
// It crossed the line!
beats++;
}
Looking closer at your signal, it's really noisy, which means we might get pretty unlucky with a sample which goes above the line then immediately drops below it giving us a false reading. To deal with that, you could add a moving average to your currentHeartrate value - that'll smooth out the fine noise for you. Add this to your project:
public class MovingAverage {
private final float[] window;
private float sum = 0f;
private int fill;
private int position;
public MovingAverage(int size) {
this.window=new float[size];
}
public void add(float number) {
if(fill==window.length){
sum-=window[position];
}else{
fill++;
}
sum+=number;
window[position++]=number;
if(position == window.length){
position=0;
}
}
public float getAverage() {
return sum / fill;
}
}
Rather than using currentHeartrate and oldHeartrateHeight, you'd instead first obtain the moving average - let's call it averageHeartrate - then cache that in oldAverageHeartrate and perform the same comparison with these two values instead.
Taking this a little further, you could make your BPM indicator realtime by counting the number of samples between these beat marks. As you've got a fixed number of samples per second, you then divide those and apply another moving average to this time reading. That then gives us this:
public int samplesSinceBeat; // Tracks the # of samples since the prev beat
public float oldAverageHeartrate; // Renamed
public int samplesPerSecond=10000; // However many samples/sec
public float midpoint=750; // The midpoint
public MovingAverage averageSamples=new MovingAverage(10); // Averaging over 10 samples
public MovingAverage beatTimeAverage=new MovingAverage(4); // Averaging over 4 beats
..
int currentHeartrate = int(inString);
// Add to your moving average buffer:
averageSamples.add(currentHeartrate);
float averageHeartrate=averageSamples.getAverage();
// Bump up the number of samples since the last beat:
samplesSinceBeat++;
if(averageHeartrate >= midpoint && oldAverageHeartrate < midpoint){
// It crossed the line - we have a beat!
// The time since the last beat is therefore:
float timeBetweenBeats=(float)samplesSinceBeat / (float)samplesPerSecond;
// Add time between beats to another moving average:
beatTimeAverage.add(timeBetweenBeats);
// Reset samples since beat:
samplesSinceBeat=0;
}
oldAverageHeartrate=averageHeartrate;
// The BPM is now this:
int realtimeBPM= (int)(60f / beatTimeAverage.getAverage() );
Some signals are evil and have a moving midpoint too. If this is the case, I would approach that by recording:
The largest value seen since the previous beat
The smallest value seen since the previous beat
Then simply take the midpoint from those. Essentially, you'd end up tracking the midpoint as you go:
if( currentHeartrate > maxHeartrateValue){
maxHeartrateValue=currentHeartrate;
}
if( currentHeartrate < minHeartrateValue){
minHeartrateValue=currentHeartrate;
}
// This line is unchanged:
if(averageHeartrate >= midpoint && oldAverageHeartrate < midpoint){
// It crossed the line - we have a beat!
midpoint=(minHeartrateValue + maxHeartrateValue) / 2;
// Clear!
minHeartrateValue=Integer.MAX_VALUE;
maxHeartrateValue=Integer.MIN_VALUE;
..
I am making a paint application and the flood fill tool works, but it takes about two minutes for it to fill a 400x180. What can I do to speed up this process? Here is the code I am currently using for this.
public void gradientSize(int x, int y, int origRGB, int index){
queue = new ArrayList<String>(); //queue is an ArrayList<String> that holds the points
time = System.currentTimeMillis(); // time is a long so I can calculate the time it takes to finish a flood fill
if(new Color(origRGB).equals(foreground)){ //foreground is the color the flood fill is using to fill in. origRGB is the RGB of the color I clicked
return;
}
if(!testFill(x, y, origRGB)){
return;
}
queue.add(pixel(x,y));
while(!queue.isEmpty()){
String pixel = queue.get(0);
int x2 = Integer.parseInt(pixel.substring(0, pixel.indexOf(",")));
int y2 = Integer.parseInt(pixel.substring(pixel.indexOf(",")+1,pixel.length()));
queue.remove(0);
if(testFill(x2, y2, origRGB)){
queue.add(pixel(x2+1, y2));
queue.add(pixel(x2-1,y2));
queue.add(pixel(x2,y2+1));
queue.add(pixel(x2,y2-1));
gradientPoints.add(pixel(x2, y2)); //gradientPoints is an ArrayList<String> that contains all the points for the fill
processed[y*image.getWidth()+x] = true; //processed[] is a boolean array that has a true or false value for each pixel to determine if it has been looked at yet.
}
}
}
public boolean testFill(int x, int y,int origRGB){ //testFill tests if the current pixel is okay to be filled or not
if(x>=0&&x<image.getWidth()&&y>=0&&y<image.getHeight()){
int testRGB = image.getRGB(x, y);
Color orig = new Color(origRGB,true);
Color test = new Color(testRGB,true);
if ((Math.abs(orig.getRed() - test.getRed()) <= difference) && (Math.abs(orig.getGreen() - test.getGreen()) <= difference)&& (Math.abs(orig.getBlue() - test.getBlue()) <= difference)&&(Math.abs(orig.getAlpha() - test.getAlpha()) <= difference)) {
if (!gradientPoints.contains(pixel(x,y))) {
if (!queue.contains(pixel(x,y))) {
if (processed[y*image.getWidth()+x]==false) {
return true;
}
}
}
}
}
return false;
}
public String pixel(int x, int y){//this returns the String value of a pixel's x and y coordinates.
return String.valueOf(x)+","+String.valueOf(y);
}
public void gradientFillSolid(){ //This gets all the points from gradientPoints and fills each pixel from there.
for(String s:gradientPoints){
int x = Integer.parseInt(s.substring(0, s.indexOf(',')));
int y = Integer.parseInt(s.substring(s.indexOf(',')+1,s.length()));
image.setRGB(x, y, foreground.getRGB());
}
System.out.println(System.currentTimeMillis()-time);
repaint();
}
The output for a 400x180 rectangle was 148566 milliseconds. Is there a way for me to speed up this process at all? Any help is appreciated.
Here's your problem:
queue.add(pixel(x2+1, y2));
queue.add(pixel(x2-1,y2));
queue.add(pixel(x2,y2+1));
queue.add(pixel(x2,y2-1));
You're adding every pixel multiple times (once here, and once for every block around that particular pixel) and rechecking it every time it's added again. If you had a 4x4 block, or something, you really wouldn't notice a slowdown, but when we're talking about 400x180 (72,000) pixels being added and checked 3 or 4 times per pixel, it gets to be huge.
My suggestion is very simple: Check before you add. Or even better, make a small little "MyPixel" class that has a boolean value that is flipped to true after you've already checked it. That way, you can skip doing any math on it and you can just do something like this:
if(my_pixel.has_been_checked == false)
queue.add(my_pixel);
You are converting the pixel coordinates to a String, then parsing them back out. I have found in my experience that string concatenation is an expensive action. Instead, just store pixels as java.awt.Point objects and read the coordinates from those.
This is more of a fun experiment than an issue, I'm more curious as to what causes it.
I'm generating 2D points, stored as floats, in order to draw a collection of lines in pattern. When generating a new point, I make a copy of the last point, and move it in a certain direction based on a heading (x += cos(heading), y += sin(heading)). Heading is also stored as a float. I change the heading at a given rate (say, 90 degrees), which would imply that every new point is either parallel with the last point or at a right angle to it.
This works, but after many (1000s) of iterations, the outer edges of the pattern start to get slightly thicker, and then the newly plotted points start being moved at a slightly askew (but consistent) location. The entire pattern then starts to spin.
What I'm curious of is how something that shouldn't break breaks. My best guess is the heading (a float which is constantly being reduced) is losing definition when it reaches a certain size.
Essentially, the code (for producing the exact pattern below) looks like this
float distance = 25;
int theta = 90;
float heading = 0;
public static void main(String[] args) {
turnLeft();
func();
turnRight();
func();
turnRight();
turnRight();
func();
}
public void func() {
drawForward();
turnRight();
drawForward();
turnRight();
turnRight();
drawForward();
turnRight();
drawForward();
drawForward();
}
public void turnLeft() {
heading += degToRad(theta);
}
public void turnRight() {
heading -= degToRad(theta);
}
public float degToRad(float degrees) {
return (float) (degrees * (Math.PI / 180.0));
}
public void drawForward() {
FPoint newFp = new FPoint(array[array.length - 1]); //copy last point
movePoint(newFp, (float) (distance * Math.cos(heading)),
(float) (distance * Math.sin(heading)));
appendPoint(newFp); //add point to end of array
}
public void movePoint(FPoint fp, float x, float y) {
fp.x += x;
fp.y += y;
}
Any thoughts on the matter would be greatly appreciated!
See if you are at the exact same place when you move forward and backward. If you are not at the same location, then you subtract half of the difference from each forward movement.
This could be in relation with numerical stability and precision of the calculations. http://en.wikipedia.org/wiki/Numerical_stability .
Since you dont have a go backward , you just use forward + right + forward +right .... until you reach the starting place, then subtract if it is different then starting value. Then use this as an offset error value to subtract(but divide the error by number of movements first)(of course the would be like "FRFRFRFECC" )
This is called BFECC back and forth error correction compensation. Greatly reduces the error if it is about unavoidable errors.
I saw someone testing BFECC on Zalesak's Turning Disk to see if disk becomes corrupt after thousands of rotation iterations.