Java group tile matrix by largest possible AABB - java

i'm making a 2D tile based game and i was working with AABB collision algorithm
when i got stucked in this problem, i have a matrix of tile:
Tile[][] matrix = new Tile[WIDTH][HEIGHT];
interface Tile {
public boolean isSolid();
}
based on this matrix i want calculate the AABB pool
that is a simple list, defined here:
List<AABB> aabbPool = new ArrayList<AABB>();
class AABB {
private int x;
private int y;
private int w;
private int h;
// Getter and Setter //
}
i'm looking for an algorithm that is capable of iterate the tile matrix
and find the largest possible AABB-Rectangle of solid attached tiles in the matrix,
let me explain:
Grid = The matrix
White = Unsolid tile
Black = Solid Tile
Given this matrix the algorithm will create the aabb pool, like this
Red outline = Y preferred aabb
Green outline = X preferred aabb (Y is not possible)
Blue outline = XY group
At the end, i created this script for debug the algorithm
public class AABBDebugger {
// Public field just for debug
static class AABB {
public int xPosition;
public int yPosition;
public int width;
public int height;
}
// Public field just for debug
static class Tile {
public static final int SIZE = 10;
public boolean solid;
}
public static void main(String[] args) {
// Matrix size
int WIDTH = 50;
int HEIGHT = 50;
// Declaration of matrix and random initialization
Tile[][] matrix = new Tile[WIDTH][HEIGHT];
for (int xCoord = 0; xCoord < WIDTH; xCoord++) {
for (int yCoord = 0; yCoord < HEIGHT; yCoord++) {
matrix[xCoord][yCoord] = new Tile();
matrix[xCoord][yCoord].solid = Math.random() > 0.5;
}
}
// Inizialization of the collission pool
List<AABB> aabbPool = new ArrayList<AABB>();
// Magic method that create the collision pool
magicMethod(matrix, WIDTH, HEIGHT, aabbPool);
// Rendering of result
Canvas canvas = new Canvas();
canvas.setPreferredSize(new Dimension(WIDTH * Tile.SIZE, HEIGHT * Tile.SIZE));
JFrame frame = new JFrame();
frame.add(canvas);
frame.pack();
frame.setVisible(true);
while (!Thread.interrupted()) {
BufferStrategy bufferStrategy;
while ((bufferStrategy = canvas.getBufferStrategy()) == null) {
canvas.createBufferStrategy(2);
}
Graphics graphics = bufferStrategy.getDrawGraphics();
for (int xCoord = 0; xCoord < WIDTH; xCoord++) {
for (int yCoord = 0; yCoord < HEIGHT; yCoord++) {
graphics.setColor(matrix[xCoord][yCoord].solid ? Color.BLACK : Color.WHITE);
graphics.fillRect(xCoord * Tile.SIZE, yCoord * Tile.SIZE, Tile.SIZE, Tile.SIZE);
}
}
for (AABB aabb : aabbPool) {
graphics.setColor(Color.RED);
graphics.drawRect(aabb.xPosition, aabb.yPosition, aabb.width, aabb.height);
}
bufferStrategy.show();
graphics.dispose();
}
System.exit(0);
}
/*
* The algorithm that i'm looking for
* for cycle start from Y rather then X
*/
private static void magicMethod(Tile[][] matrix, int WIDTH, int HEIGHT, List<AABB> aabbPool) {
for (int yCoord = 0; yCoord < HEIGHT; yCoord++) {
AABB aabb = null;
for (int xCoord = 0; xCoord < WIDTH; xCoord++) {
if (matrix[xCoord][yCoord].solid) {
if (aabb == null) {
aabb = new AABB();
aabb.yPosition = yCoord * Tile.SIZE;
aabb.xPosition = xCoord * Tile.SIZE;
aabb.height = Tile.SIZE;
aabb.width = Tile.SIZE;
} else {
aabb.width += Tile.SIZE;
}
} else if (aabb != null) {
aabbPool.add(aabb);
aabb = null;
}
}
if (aabb != null) {
aabbPool.add(aabb);
aabb = null;
}
}
}
}
The script produce this result:
but isn't what i expect because this algorithm group the tiles
by y and is ok, but not by x when i can, like there
Finally (sorry for the long post) the algorithm must respect this rules:
Prefer group the tiles by Y
Group the tiles by X when is not possible by Y
Don't overlap existing group
Group all the tiles

You can still use your method with a slight change: Do not directly store single-tile AABB rectangles grouped by Y (those with width==Tile.SIZE); but store them in a temp HashMap: Map<Integer, List<Integer>>, where the key is the xCoord of each tile and the value yCoord is added to the list). This way you can track which tiles per xCoord have not been grouped already by Y. So, check if width!=Tile.SIZE before adding:
if (aabb.width != Tile.SIZE) {
aabbPool.add(aabb);
} else {
addToMap(xCoord,yCoord);
}
aabb = null;
The method addToMap can be implemented as follows:
private static void addToMap(Integer x, Integer y, Map<Integer, List<Integer>> xMap) {
if (xMap.containsKey(x)) {
xMap.get(x).add(y);
} else {
List<Integer> yList = new ArrayList<Integer>();
yList.add(y);
xMap.put(x, yList);
}
}
Then, call the following method as a last step of your magicMethod; it will either add single-tile AABB rectangles (not possible to be grouped anyway), or grouped by X (that are not grouped by Y already).
private static void groupByX(Map<Integer, List<Integer>> xMap, List<AABB> aabbPool) {
//for each X
for (Entry<Integer, List<Integer>> entry : xMap.entrySet()) {
List<Integer> curList = entry.getValue();
if (curList != null) {
int curY = curList.get(0); //current yCoord
int curH = 1; //current height
//for each Y
for (int i = 1; i < curList.size(); i++) {
if (curY + curH == curList.get(i)) {
curH++;
} else {
aabbPool.add(new AABB(entry.getKey()*Tile.SIZE, curY*Tile.SIZE, Tile.SIZE, curH*Tile.SIZE)); // *Tile.SIZE()
//reset
curY = curList.get(i);
curH = 1;
}
}
//add the last AABB
aabbPool.add(new AABB(entry.getKey()*Tile.SIZE, curY*Tile.SIZE, Tile.SIZE, curH*Tile.SIZE));
}
}
}
This is what you get:

Related

Grid Not Returning a Value in Processing 3

I'm trying to make a minesweeper clone using Processing's java. I have managed to create a grid and each tile has a randomized value assigned to it, but for some reason, only the top left tile can be clicked, the rest of the tiles do not return a value. If anyone knows whats wrong with my code or what I could try that would be great, thanks
Here's my Java code:
int realWidth = 500;
int realHeight = 500;
int tilemount = 0;
boolean mousePress = false;
//calls class
Tile[] tile = new Tile[100];
float tileSize = realWidth/10;
//sets size of window
void settings() {
size(realWidth, realHeight);
}
//Draws 100 tiles on the grid and assignemts each with a value from -1 - 9
void setup() {
int x = 0;
int y = 0;
for (int i = 0; i < tile.length; i++) {
tile[i] = new Tile(x, y, int(random(-1,9)), tilemount);
x += tileSize;
if (x > realWidth-tileSize) {
x = 0;
y += tileSize;
}
tilemount ++;
}
print("done");
}
//updates each tile in the list
void draw() {
background(50);
for (int i = 0; i < tile.length; i++) {
tile[i].display();
tile[i].tileClicked();
}
//checks if tiles are clicked
clickCheck();
}
//sets up tile class
class Tile {
int x;
int y;
int value;
int tilemount;
Tile(int x, int y, int value, int tilemount) {
this.x = x;
this.y = y;
this.value = value;
this.tilemount = tilemount;
}
//positions the tile based off of display values
void display() {
rect(x, y, tileSize, tileSize);
}
//the problem:
/*if tile is clicked, it should run through all 100 tiles in the list,
detect if the mouse is inside the x value assinged to each tile, and produce the
value of the tile the mouse is currently inside */
void tileClicked() {
if (mousePressed && mousePress == false) {
mousePress = true;
for (int i = 0; i < tile.length; i++) {
//println(tile[i].x, tile[i].y);
//println(tile[2].x, tile[2].y, mouseX, mouseY);
if (mouseX > tile[i].x && mouseX < tileSize && mouseY > tile[i].y && mouseY < tileSize) {
println(i, tile[i].value);
break;
}
}
}
}
}
//Checks if mouse is clicked
void clickCheck() {
if (!mousePressed) {
mousePress = false;
}
}
There is a bit of confusion over classes/objects and global variables in your code.
I say that for a few reasons:
boolean mousePress = false; is a global variable, accessible throughout the sketch, however you are accessing it and changing it from each Tile instance, resetting it's value: you probably meant to use a mousePress property for Tile. This way, each Tile has it's mousePress without interfering with one another.
in tileClicked() you're itereratting through all the tiles, from Tile which means unecessarily doing the loop for each time: each tile can check it's own coordinates and position to know if it's being clicked or not (no need for the loop)
speaking of checking bounds, checking if mouseX < tileSize and mouseY < tileSize will only check for the top left tile: you probably meant mouseX < x + tileSize and mouseY < y + tileSize
Here's a version of your sketch with the above notes applied
(and optional debug text rendering to double check the printed value against the tile):
int realWidth = 500;
int realHeight = 500;
int tilemount = 0;
//calls class
Tile[] tile = new Tile[100];
float tileSize = realWidth/10;
//sets size of window
void settings() {
size(realWidth, realHeight);
}
//Draws 100 tiles on the grid and assignemts each with a value from -1 - 9
void setup() {
int x = 0;
int y = 0;
for (int i = 0; i < tile.length; i++) {
tile[i] = new Tile(x, y, int(random(-1,9)), tilemount);
x += tileSize;
if (x > realWidth-tileSize) {
x = 0;
y += tileSize;
}
tilemount ++;
}
println("done");
}
//updates each tile in the list
void draw() {
background(50);
for (int i = 0; i < tile.length; i++) {
tile[i].display();
tile[i].tileClicked();
//checks if tiles are clicked
tile[i].clickCheck();
}
}
//sets up tile class
class Tile {
int x;
int y;
int value;
int tilemount;
boolean mousePress = false;
Tile(int x, int y, int value, int tilemount) {
this.x = x;
this.y = y;
this.value = value;
this.tilemount = tilemount;
}
//positions the tile based off of display values
void display() {
fill(255);
rect(x, y, tileSize, tileSize);
fill(0);
text(value,x + tileSize * 0.5, y + tileSize * 0.5);
}
//the problem:
/*if tile is clicked, it should run through all 100 tiles in the list,
detect if the mouse is inside the x value assinged to each tile, and produce the
value of the tile the mouse is currently inside */
void tileClicked() {
if (mousePressed && mousePress == false) {
mousePress = true;
//println(tile[2].x, tile[2].y, mouseX, mouseY);
if (mouseX > x && mouseX < x + tileSize && mouseY > y && mouseY < y + tileSize) {
println(value);
}
}
}
//Checks if mouse is clicked
void clickCheck() {
if (!mousePressed) {
mousePress = false;
}
}
}
I understand the intent of the mousePress variable, but I'd like to mousePressed() function which you can use to avoid de-bouncing/resetting this value and simplify code a bit.
Here's a version of the above sketch using mousePressed() (and renaming variables a touch to be in line with Java naming conventions):
int realWidth = 500;
int realHeight = 500;
int tileCount = 0;
//calls class
Tile[] tiles = new Tile[100];
float tileSize = realWidth/10;
//sets size of window
void settings() {
size(realWidth, realHeight);
}
//Draws 100 tiles on the grid and assignemts each with a value from -1 - 9
void setup() {
int x = 0;
int y = 0;
for (int i = 0; i < tiles.length; i++) {
tiles[i] = new Tile(x, y, int(random(-1,9)), tileCount);
x += tileSize;
if (x > realWidth-tileSize) {
x = 0;
y += tileSize;
}
tileCount ++;
}
println("done");
}
//updates each tile in the list
void draw() {
background(50);
for (int i = 0; i < tiles.length; i++) {
tiles[i].display();
}
}
void mousePressed(){
for (int i = 0; i < tiles.length; i++) {
tiles[i].click();
}
}
//sets up tile class
class Tile {
int x;
int y;
int value;
int index;
Tile(int x, int y, int value, int tileCount) {
this.x = x;
this.y = y;
this.value = value;
this.index = tileCount;
}
//positions the tile based off of display values
void display() {
fill(255);
rect(x, y, tileSize, tileSize);
// optional: display the tile value for debugging purposes
fill(0);
text(value, x + tileSize * 0.5, y + tileSize * 0.5);
}
//the problem:
/*if tile is clicked, it should detect if the mouse is inside the
value assinged to each tile, and produce the
value of the tile the mouse is currently inside */
void click() {
if (mouseX > x && mouseX < x + tileSize && mouseY > y && mouseY < y + tileSize) {
println("index", index, "value", value);
}
}
}
Good start though: keep going!
The rest of the code is good, congrats on using a 1D array for a 2D grid layout (and the logic for resetting the x position on each row). Have fun learning !
I think you did a good job naming functions and classes and what not. From this point, I would recommend breaking your code down so that you can make sure that at least 3 tiles function fully before allowing the tilelength to be something like 100 (for 100 tiles).
so from what I can see:
+ You have 3 apostrophes at the end of your code that need to be removed.
+ you use tile.length but never defined a length variable in your class, so when your for loop runs it has no number to use to determine it's number of cycles.
+ Please double check your logic in the tileClicked function with the help of a truth/logic table: https://en.wikipedia.org/wiki/Truth_table . I didn't look too deeply into this but this looks like an area where one misunderstanding could throw ;
Try adding this.length = 3; in your tile class and then re-run your code to see if more tiles pop up.
And it might be good to use some system.out.println calls to print conditional values out so that you can see the intermeediate results and compare them to what you expect to happen. Just some basic debugging.
Good luck.

Calculating 'color distance' between 2 points in a 3-dimensional space

I have a homework task where I have to write a class responsible for contour detection. It is essentially an image processing operation, using the definition of euclidean distance between 2 points in the 3-dimensional space. Formula given to us to use is:
Math.sqrt(Math.pow(pix1.red - pix2.red,2) + Math.pow(pix1.green- pix2.green,2) + Math.pow(pix1.blue- pix2.blue,2));
We need to consider each entry of the two dimensional array storing the colors of the pixels of an image, and if some pixel, pix, the color distance between p and any of its neighbors is more than 70, change the color of the pixel to black, else change it to white.
We are given a seperate class as well responsible for choosing an image, and selecting an output, for which method operationContouring is applied to. Java syntax and convention is very new to me having started with python. Conceptually, I'm struggling to understand what the difference between pix1 and pix2 is, and how to define them. This is my code so far.
Given:
import java.awt.Color;
/* Interface for ensuring all image operations invoked in same manner */
public interface operationImage {
public Color[][] operationDo(Color[][] imageArray);
}
My code:
import java.awt.Color;
public class operationContouring implements operationImage {
public Color[][] operationDo(Color[][] imageArray) {
int numberOfRows = imageArray.length;
int numberOfColumns = imageArray[0].length;
Color[][] results = new Color[numberOfRows][numberOfColumns];
for (int i = 0; i < numberOfRows; i++)
for (int j = 0; j < numberOfColumns; j++) {
int red = imageArray[i][j].getRed();
int green = imageArray[i][j].getGreen();
int blue = imageArray[i][j].getBlue();
double DistanceColor = Math.sqrt(Math.pow(pix1.red - pix2.red,2) + Math.pow(pix1.green- pix2.green,2) + Math.pow(pix1.blue- pix2.blue,2));
int LIMIT = 70;
if (DistanceColor> LIMIT ) {
results[i][j] = new Color((red=0), (green=0), (blue=0));
}
else {
results[i][j] = new Color((red=255), (green=255), (blue=255));
}
}
return results;
}
}
This is a solution I wrote that uses BufferedImages. I tested it and it should work. Try changing it such that it uses your data format (Color[][]) and it should work for you too. Note that "pix1" is nothing more than a description of the color of some pixel, and "pix2" is the description of the color of the pixel you are comparing it to (determining whether the color distance > 70).
public static boolean tooDifferent(Color c1, Color c2) {
return Math.sqrt(Math.pow(c1.getRed() - c2.getRed(),2) + Math.pow(c1.getGreen()- c2.getGreen(),2) + Math.pow(c1.getBlue()- c2.getBlue(),2)) > 70;
}
public static Color getColor(int x, int y, BufferedImage img) {
return new Color(img.getRGB(x, y));
}
public static BufferedImage operationDo(BufferedImage img) {
int numberOfRows = img.getHeight();
int numberOfColumns = img.getWidth();
BufferedImage results = new BufferedImage(numberOfColumns, numberOfRows, BufferedImage.TYPE_INT_ARGB);
for (int y = 0; y < numberOfRows; y++) {
for (int x = 0; x < numberOfColumns; x++) {
Color color = new Color(img.getRGB(x, y));
boolean aboveExists = y > 0;
boolean belowExists = y < numberOfRows - 1;
boolean leftExists = x > 0;
boolean rightExists = x < numberOfColumns - 1;
if ((aboveExists && tooDifferent(color, getColor(x, y - 1, img))) ||
(belowExists && tooDifferent(color, getColor(x, y + 1, img))) ||
(leftExists && tooDifferent(color, getColor(x - 1, y, img))) ||
(rightExists && tooDifferent(color, getColor(x + 1, y, img)))) {
results.setRGB(x, y, Color.black.getRGB());
} else {
results.setRGB(x, y, Color.white.getRGB());
}
}
}
return results;
}

Parallel and Single Threaded code having relatively same performance in Java

I wrote a program to render a Julia Set. The single threaded code is pretty straightforward and is essentially like so:
private Image drawFractal() {
BufferedImage img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < WIDTH; x++) {
for (int y = 0; y < HEIGHT; y++) {
double X = map(x,0,WIDTH,-2.0,2.0);
double Y = map(y,0,HEIGHT,-1.0,1.0);
int color = getPixelColor(X,Y);
img.setRGB(x,y,color);
}
}
return img;
}
private int getPixelColor(double x, double y) {
float hue;
float saturation = 1f;
float brightness;
ComplexNumber z = new ComplexNumber(x, y);
int i;
for (i = 0; i < maxiter; i++) {
z.square();
z.add(c);
if (z.mod() > blowup) {
break;
}
}
brightness = (i < maxiter) ? 1f : 0;
hue = (i%maxiter)/(float)maxiter;
int rgb = Color.getHSBColor(hue,saturation,brightness).getRGB();
return rgb;
}
As you can see it is highly inefficient. Thus I went for Parallelizing this code using the fork/join framework in Java and this is what I came up with:
private Image drawFractal() {
BufferedImage img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
ForkCalculate fork = new ForkCalculate(img, 0, WIDTH, HEIGHT);
ForkJoinPool forkPool = new ForkJoinPool();
forkPool.invoke(fork);
return img;
}
//ForkCalculate.java
public class ForkCalculate extends RecursiveAction {
BufferedImage img;
int minWidth;
int maxWidth;
int height;
int threshold;
int numPixels;
ForkCalculate(BufferedImage b, int minW, int maxW, int h) {
img = b;
minWidth = minW;
maxWidth = maxW;
height = h;
threshold = 100000; //TODO : Experiment with this value.
numPixels = (maxWidth - minWidth) * height;
}
void computeDirectly() {
for (int x = minWidth; x < maxWidth; x++) {
for (int y = 0; y < height; y++) {
double X = map(x,0,Fractal.WIDTH,-2.0,2.0);
double Y = map(y,0,Fractal.HEIGHT,-1.0,1.0);
int color = getPixelColor(X,Y);
img.setRGB(x,y,color);
}
}
}
#Override
protected void compute() {
if(numPixels < threshold) {
computeDirectly();
return;
}
int split = (minWidth + maxWidth)/2;
invokeAll(new ForkCalculate(img, minWidth, split, height), new ForkCalculate(img, split, maxWidth, height));
}
private int getPixelColor(double x, double y) {
float hue;
float saturation = 1f;
float brightness;
ComplexNumber z = new ComplexNumber(x, y);
int i;
for (i = 0; i < Fractal.maxiter; i++) {
z.square();
z.add(Fractal.c);
if (z.mod() > Fractal.blowup) {
break;
}
}
brightness = (i < Fractal.maxiter) ? 1f : 0;
hue = (i%Fractal.maxiter)/(float)Fractal.maxiter;
int rgb = Color.getHSBColor(hue*5,saturation,brightness).getRGB();
return rgb;
}
private double map(double x, double in_min, double in_max, double out_min, double out_max) {
return (x-in_min)*(out_max-out_min)/(in_max-in_min) + out_min;
}
}
I tested with a range of values varying the maxiter, blowup and threshold.
I made the threshold such that the number of threads are around the same as the number of cores that I have (4).
I measured the runtimes in both cases and expected some optimization in parallelized code. However the code ran in the same time if not slower sometimes. This has me baffled. Is this happening because the problem size isn't big enough? I also tested with varying image sizes ranging from 640*400 to 1020*720.
Why is this happening? How can I run the code parallely so that it runs faster as it should?
Edit
If you want to checkout the code in its entirety head over to my Github
The master branch has the single threaded code.
The branch with the name Multicore has the Parallelized code.
Edit 2 Image of the fractal for reference.
Here is your code rewritten to use concurrency. I found that my Lenovo Yoga misreported the number of processors by double. Also Windows 10 seems to take up an enormous amount of processing, so the results on my laptop are dubious. If you have more cores or a decent OS, it should be much better.
package au.net.qlt.canvas.test;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
public class TestConcurrency extends JPanel {
private BufferedImage screen;
final Fractal fractal;
private TestConcurrency(final Fractal f, Size size) {
fractal = f;
screen = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
setBackground(Color.BLACK);
setPreferredSize(new Dimension(size.width,size.height));
}
public void test(boolean CONCURRENT) {
int count = CONCURRENT ? Runtime.getRuntime().availableProcessors()/2 : 1;
Scheduler scheduler = new Scheduler(fractal.size);
Thread[] threads = new Thread[count];
long startTime = System.currentTimeMillis();
for (int p = 0; p < count; p++) {
threads[p] = new Thread() {
public void run() {
scheduler.schedule(fractal,screen);
}
};
threads[p].start();
try {
threads[p].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
DEBUG("test threads: %d - elasped time: %dms", count, (System.currentTimeMillis()-startTime));
}
#Override public void paint(Graphics g) {
if(g==null) return;
g.drawImage(screen, 0,0, null);
}
public static void main(String[]args) {
JFrame frame = new JFrame("FRACTAL");
Size size = new Size(1024, 768);
Fractal fractal = new Fractal(size);
TestConcurrency test = new TestConcurrency(fractal, size);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(test);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
for(int i=1; i<=10; i++) {
DEBUG("--- Test %d ------------------", i);
test.test(false);
test.repaint();
test.test(true);
test.repaint();
}
}
public static void DEBUG(String str, Object ... args) { Also System.out.println(String.format(str, args)); }
}
class Fractal {
ComplexNumber C;
private int maxiter;
private int blowup;
private double real;
private double imaginary;
private static double xs = -2.0, xe = 2.0, ys = -1.0, ye = 1.0;
public Size size;
Fractal(Size sz){
size = sz;
real = -0.8;
imaginary = 0.156;
C = new ComplexNumber(real, imaginary);
maxiter = 400;
blowup = 4;
}
public int getPixelColor(Ref ref) {
float hue;
float saturation = 1f;
float brightness;
double X = map(ref.x,0,size.width,xs,xe);
double Y = map(ref.y,0,size.height,ys,ye);
ComplexNumber Z = new ComplexNumber(X, Y);
int i;
for (i = 0; i < maxiter; i++) {
Z.square();
Z.add(C);
if (Z.mod() > blowup) {
break;
}
}
brightness = (i < maxiter) ? 1f : 0;
hue = (i%maxiter)/(float)maxiter;
return Color.getHSBColor(hue*5,saturation,brightness).getRGB();
}
private double map(double n, double in_min, double in_max, double out_min, double out_max) {
return (n-in_min)*(out_max-out_min)/(in_max-in_min) + out_min;
}
}
class Size{
int width, height, length;
public Size(int w, int h) { width = w; height = h; length = h*w; }
}
class ComplexNumber {
private double real;
private double imaginary;
ComplexNumber(double a, double b) {
real = a;
imaginary = b;
}
void square() {
double new_real = Math.pow(real,2) - Math.pow(imaginary,2);
double new_imaginary = 2*real*imaginary;
this.real = new_real;
this.imaginary = new_imaginary;
}
double mod() {
return Math.sqrt(Math.pow(real,2) + Math.pow(imaginary,2));
}
void add(ComplexNumber c) {
this.real += c.real;
this.imaginary += c.imaginary;
}
}
class Scheduler {
private Size size;
private int x, y, index;
private final Object nextSync = 4;
public Scheduler(Size sz) { size = sz; }
/**
* Update the ref object with next available coords,
* return false if no more coords to be had (image is rendered)
*
* #param ref Ref object to be updated
* #return false if end of image reached
*/
public boolean next(Ref ref) {
synchronized (nextSync) {
// load passed in ref
ref.x = x;
ref.y = y;
ref.index = index;
if (++index > size.length) return false; // end of the image
// load local counters for next access
if (++x >= size.width) {
x = 0;
y++;
}
return true; // there are more pixels to be had
}
}
public void schedule(Fractal fractal, BufferedImage screen) {
for(Ref ref = new Ref(); next(ref);)
screen.setRGB(ref.x, ref.y, fractal.getPixelColor(ref));
}
}
class Ref {
public int x, y, index;
public Ref() {}
}

How to add an image into a hexagon in a hexagonal grid?

I have a problem with a hexagonal grid. I found this code you can see below on Internet, so it's not mine. There are two public classes: hexgame which generates the grid and hexmech which draws and fills every single hexagon. What I'd like to do is basically insert an image into a specific hexagon, but I don't know how to code this and in which part of the classes I should put it. Am I thinking the wrong way?
Thank you very much for your help!
Hexgame
package hex;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class hexgame
{
private hexgame() {
initGame();
createAndShowGUI();
}
final static Color COLOURBACK = Color.WHITE;
final static Color COLOURCELL = Color.WHITE;
final static Color COLOURGRID = Color.BLACK;
final static Color COLOURONE = new Color(255,255,255,200);
final static Color COLOURONETXT = Color.BLUE;
final static Color COLOURTWO = new Color(0,0,0,200);
final static Color COLOURTWOTXT = new Color(255,100,255);
final static Color COLOURSAFE = Color.WHITE;
final static Color COLOURDANGEROUS = Color.LIGHT_GRAY;
final static int EMPTY = 0;
final static int UNKNOWN = -1;
final static int SAFE = 1;
final static int DANGEROUS = 2;
final static int CLICKED = 3;
final static int COLUMN_SIZE = 23;
final static int ROW_SIZE = 14;
final static int HEXSIZE = 45;
final static int BORDERS = 15;
int[][] board = new int[COLUMN_SIZE][ROW_SIZE];
void initGame(){
hexmech.setXYasVertex(false);
hexmech.setHeight(HEXSIZE);
hexmech.setBorders(BORDERS);
for (int i=0;i<COLUMN_SIZE;i++) {
for (int j=0;j<ROW_SIZE;j++) {
board[i][j]=EMPTY;
}
}
board[5][5] = SAFE;
board[5][6] = SAFE;
board[5][7] = SAFE;
board[6][5] = SAFE;
board [6][6] = SAFE;
board[4][4] = UNKNOWN;
}
private void createAndShowGUI()
{
DrawingPanel panel = new DrawingPanel();
JFrame frame = new JFrame("Hex Testing 4");
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
Container content = frame.getContentPane();
content.add(panel);
frame.setSize(825, 630);
frame.setResizable(true);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
class DrawingPanel extends JPanel
{
public DrawingPanel()
{
setBackground(COLOURBACK);
MyMouseListener ml = new MyMouseListener();
addMouseListener(ml);
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setFont(new Font("TimesRoman", Font.PLAIN, 15));
super.paintComponent(g2);
for (int i=0;i<COLUMN_SIZE;i++) {
for (int j=0;j<ROW_SIZE;j++) {
if (board[i][j] != UNKNOWN)
hexmech.drawHex(i,j,g2);
}
}
for (int i=0;i<COLUMN_SIZE;i++) {
for (int j=0;j<ROW_SIZE;j++) {
if (board[i][j] != UNKNOWN)
hexmech.fillHex(i,j,board[i][j],g2);
}
}
}
class MyMouseListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Point p = new Point( hexmech.pxtoHex(e.getX(),e.getY()) );
if (p.x < 0 || p.y < 0 || p.x >= COLUMN_SIZE || p.y >= ROW_SIZE) return;
board[p.x][p.y] = CLICKED;
repaint();
}
}
}
}
Hexmech
package hex;
import java.awt.*;
import javax.swing.*;
public class hexmech
{
#define HEXEAST 0
#define HEXSOUTHEAST 1
#define HEXSOUTHWEST 2
#define HEXWEST 3
#define HEXNORTHWEST 4
#define HEXNORTHEAST 5
public final static boolean orFLAT= true;
public final static boolean orPOINT= false;
public static boolean ORIENT= orFLAT;
public static boolean XYVertex=true;
private static int BORDERS=50
private static int s=0; // length of one side
private static int t=0; // short side of 30o triangle outside of each hex
private static int r=0; // radius of inscribed circle (centre to middle of each side). r= h/2
private static int h=0; // height. Distance between centres of two adjacent hexes. Distance between two opposite sides in a hex.
public static void setXYasVertex(boolean b) {
XYVertex=b;
}
public static void setBorders(int b){
BORDERS=b;
}
public static void setSide(int side) {
s=side;
t = (int) (s / 2); //t = s sin(30) = (int) CalculateH(s);
r = (int) (s * 0.8660254037844);
h=2*r;
}
public static void setHeight(int height) {
h = height;
r = h/2; // r = radius of inscribed circle
s = (int) (h / 1.73205); // s = (h/2)/cos(30)= (h/2) / (sqrt(3)/2) = h / sqrt(3)
t = (int) (r / 1.73205); // t = (h/2) tan30 = (h/2) 1/sqrt(3) = h / (2 sqrt(3)) = r / sqrt(3)
}
public static Polygon hex (int x0, int y0) {
int y = y0 + BORDERS;
int x = x0 + BORDERS;
if (s == 0 || h == 0) {
System.out.println("ERROR: size of hex has not been set");
return new Polygon();
}
int[] cx,cy;
if (XYVertex)
cx = new int[] {x,x+s,x+s+t,x+s,x,x-t}; //this is for the top left vertex being at x,y. Which means that some of the hex is cutoff.
else
cx = new int[] {x+t,x+s+t,x+s+t+t,x+s+t,x+t,x}; //this is for the whole hexagon to be below and to the right of this point
cy = new int[] {y,y,y+r,y+r+r,y+r+r,y+r};
return new Polygon(cx,cy,6);
}
public static void drawHex(int i, int j, Graphics2D g2) {
int x = i * (s+t);
int y = j * h + (i%2) * h/2;
Polygon poly = hex(x,y);
g2.setColor(hexgame.COLOURCELL);
//g2.fillPolygon(hexmech.hex(x,y));
g2.fillPolygon(poly);
g2.setColor(hexgame.COLOURGRID);
g2.drawString(String.format("%c;%d", 'A'+i, j+1), x+20, y+40);
g2.drawPolygon(poly);
}
public static void fillHex(int i, int j, int n, Graphics2D g2) {
char c='o';
int x = i * (s+t);
int y = j * h + (i%2) * h/2;
/*if (n < 0) {
g2.setColor(hexgame.COLOURONE);
g2.fillPolygon(hex(x,y));
g2.setColor(hexgame.COLOURONETXT);
c = (char)(-n);
g2.drawString(""+c, x+r+BORDERS, y+r+BORDERS+4); //FIXME: handle XYVertex
//g2.drawString(x+","+y, x+r+BORDERS, y+r+BORDERS+4);
}
if (n > 0) {
g2.setColor(hexgame.COLOURTWO);
g2.fillPolygon(hex(x,y));
g2.setColor(hexgame.COLOURTWOTXT);
c = (char)n;
if (n==3) {
g2.setColor(hexgame.COLOURTWO);
g2.fillPolygon(hex(x,y));
g2.setColor(hexgame.COLOURTWOTXT);
}
}
public static Point pxtoHex(int mx, int my) {
Point p = new Point(-1,-1);
//correction for BORDERS and XYVertex
mx -= BORDERS;
my -= BORDERS;
if (XYVertex) mx += t;
int x = (int) (mx / (s+t));
int y = (int) ((my - (x%2)*r)/h);
int dx = mx - x*(s+t);
int dy = my - y*h;
if (my - (x%2)*r < 0) return p; // prevent clicking in the open halfhexes at the top of the screen
//System.out.println("dx=" + dx + " dy=" + dy + " > " + dx*r/t + " <");
//even columns
if (x%2==0) {
if (dy > r) { //bottom half of hexes
if (dx * r /t < dy - r) {
x--;
}
}
if (dy < r) { //top half of hexes
if ((t - dx)*r/t > dy ) {
x--;
y--;
}
}
} else { // odd columns
if (dy > h) { //bottom half of hexes
if (dx * r/t < dy - h) {
x--;
y++;
}
}
if (dy < h) { //top half of hexes
//System.out.println("" + (t- dx)*r/t + " " + (dy - r));
if ((t - dx)*r/t > dy - r) {
x--;
}
}
}
p.x=x;
p.y=y;
return p;
}
In your implementation of paintComponent(), invoke setClip() with a suitable Shape, such as Polygon. You can size and translate the Polygon to match the destination hexagon using the createTransformedShape() method of AffineTransform. Use the coordinates of the polygon's boundary as the basis for the coordinates used in your call to drawImage(). A related example using Ellipse2D is shown here.

Java - Moving Randomly Generated Tilemap

I'm pretty fresh to java but I wanted to create a exploration type game. I researched for the last 2 weeks and was able to implement the diamond square algorithm for some pretty sweet terrain. But now I'm having trouble trying figure out how to move the map and how to continue the random generation. Here's what I have so far.
Game.java
package com.game.main;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.util.Random;
//import java.util.Random;
public class Game extends Canvas implements Runnable{
private static final long serialVersionUID = 5420209024354289119L;
public static final int WIDTH = 1000, HEIGHT = WIDTH / 12 * 9;
private Thread thread;
private boolean running = false;
//private Random r;
private Handler handler;
public Game(){
handler = new Handler();
this.addKeyListener(new KeyInput(handler));
new Window(WIDTH, HEIGHT, "Game", this);
final int[][] map = DSAlgorithm.makeHeightMap(10, 45, 200);
//r = new Random();
handler.addObject(new Player(WIDTH/2 - 32, HEIGHT/2 - 32, ID.Player));
//handler.addObject(new World(0, 0, ID.World));
int squareSize = 10;
for(int y = 0; y < map.length; y+=squareSize){
for(int x = 0; x < map.length; x+=squareSize){
int value = map[x][y];
handler.addObject(new TerrianTile(x, y, value));
}
}
}
public synchronized void start(){
thread = new Thread(this);
thread.start();
running = true;
}
public synchronized void stop(){
try {
thread.join();
running = false;
} catch (Exception e) {
e.printStackTrace();
}
}
public void run(){
long lastTime = System.nanoTime();
double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long timer = System.currentTimeMillis();
int frames = 0;
while (running){
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while(delta >= 1){
tick();
delta --;
}
if (running)
render();
frames++;
if (System.currentTimeMillis() - timer > 1000){
timer += 1000;
System.out.println("FPS: " + frames);
frames = 0;
}
}
stop();
}
private void tick(){
handler.tick();
}
private void render(){
BufferStrategy bs = this.getBufferStrategy();
if (bs == null){
this.createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(Color.black);
g.fillRect(0, 0, WIDTH, HEIGHT);
handler.render(g);
g.dispose();
bs.show();
}
public static void main(String args[]){
new Game();
}
public static int randInt(int min, int max){
Random rand = new Random();
int randomNum = rand.nextInt((max - min) + 1) + min;
return randomNum;
}
}
Window.java
package com.game.main;
import java.awt.Canvas;
import java.awt.Dimension;
import javax.swing.JFrame;
public class Window extends Canvas {
private static final long serialVersionUID = -1478604005915452565L;
public Window(int width, int height, String title, Game game) {
JFrame frame = new JFrame(title);
frame.setPreferredSize(new Dimension(width, height));
frame.setMaximumSize(new Dimension(width, height));
frame.setMinimumSize(new Dimension(width, height));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.add(game);
frame.setVisible(true);
game.start();
}
}
DSAlgorithm.java
package com.game.main;
public class DSAlgorithm {
/**
* This method uses the seed value to initialize the four corners of the
* map. The variation creates randomness in the map. The size of the array
* is determined by the amount of iterations (i.e. 1 iteration -> 3x3 array,
* 2 iterations -> 5x5 array, etc.).
*
* #param iterations
* the amount of iterations to do (minimum of 1)
* #param seed
* the starting value
* #param variation
* the amount of randomness in the height map (minimum of 0)
* #return a height map in the form of a 2-dimensional array containing
* integer values or null if the arguments are out of range
*/
public static int[][] makeHeightMap(int iterations, int seed, int variation) {
if (iterations < 1 || variation < 0) {
return null;
}
int size = (1 << iterations) + 1;
int[][] map = new int[size][size];
final int maxIndex = map.length - 1;
// seed the corners
map[0][0] = seed;
map[0][maxIndex] = seed;
map[maxIndex][0] = seed;
map[maxIndex][maxIndex] = seed;
for (int i = 1; i <= iterations; i++) {
int minCoordinate = maxIndex >> i;// Minimum coordinate of the
// current map spaces
size = minCoordinate << 1;// Area surrounding the current place in
// the map
diamondStep(minCoordinate, size, map, variation);
squareStepEven(minCoordinate, map, size, maxIndex, variation);
squareStepOdd(map, size, minCoordinate, maxIndex, variation);
variation = variation >> 1;// Divide variation by 2
}
return map;
}
/**
* Calculates average values of four corner values taken from the smallest
* possible square.
*
* #param minCoordinate
* the x and y coordinate of the first square center
* #param size
* width and height of the squares
* #param map
* the height map to fill
* #param variation
* the randomness in the height map
*/
private static void diamondStep(int minCoordinate, int size, int[][] map,
int variation) {
for (int x = minCoordinate; x < (map.length - minCoordinate); x += size) {
for (int y = minCoordinate; y < (map.length - minCoordinate); y += size) {
int left = x - minCoordinate;
int right = x + minCoordinate;
int up = y - minCoordinate;
int down = y + minCoordinate;
// the four corner values
int val1 = map[left][up]; // upper left
int val2 = map[left][down]; // lower left
int val3 = map[right][up]; // upper right
int val4 = map[right][down];// lower right
calculateAndInsertAverage(val1, val2, val3, val4, variation,
map, x, y);
}
}
}
/**
* Calculates average values of four corner values taken from the smallest
* possible diamond. This method calculates the values for the even rows,
* starting with row 0.
*
* #param minCoordinate
* the x-coordinate of the first diamond center
* #param map
* the height map to fill
* #param size
* the length of the diagonals of the diamonds
* #param maxIndex
* the maximum index in the array
* #param variation
* the randomness in the height map
*/
private static void squareStepEven(int minCoordinate, int[][] map,
int size, int maxIndex, int variation) {
for (int x = minCoordinate; x < map.length; x += size) {
for (int y = 0; y < map.length; y += size) {
if (y == maxIndex) {
map[x][y] = map[x][0];
continue;
}
int left = x - minCoordinate;
int right = x + minCoordinate;
int down = y + minCoordinate;
int up = 0;
if (y == 0) {
up = maxIndex - minCoordinate;
} else {
up = y - minCoordinate;
}
// the four corner values
int val1 = map[left][y]; // left
int val2 = map[x][up]; // up
int val3 = map[right][y];// right
int val4 = map[x][down]; // down
calculateAndInsertAverage(val1, val2, val3, val4, variation,
map, x, y);
}
}
}
/**
* Calculates average values of four corner values taken from the smallest
* possible diamond. This method calculates the values for the odd rows,
* starting with row 1.
*
* #param minCoordinate
* the x-coordinate of the first diamond center
* #param map
* the height map to fill
* #param size
* the length of the diagonals of the diamonds
* #param maxIndex
* the maximum index in the array
* #param variation
* the randomness in the height map
*/
private static void squareStepOdd(int[][] map, int size, int minCoordinate,
int maxIndex, int variation) {
for (int x = 0; x < map.length; x += size) {
for (int y = minCoordinate; y < map.length; y += size) {
if (x == maxIndex) {
map[x][y] = map[0][y];
continue;
}
int left = 0;
int right = x + minCoordinate;
int down = y + minCoordinate;
int up = y - minCoordinate;
if (x == 0) {
left = maxIndex - minCoordinate;
} else {
left = x - minCoordinate;
}
// the four corner values
int val1 = map[left][y]; // left
int val2 = map[x][up]; // up
int val3 = map[right][y];// right
int val4 = map[x][down]; // down
calculateAndInsertAverage(val1, val2, val3, val4, variation,
map, x, y);
}
}
}
/**
* Calculates an average value, adds a variable amount to that value and
* inserts it into the height map.
*
* #param val1
* first of the values used to calculate the average
* #param val2
* second of the values used to calculate the average
* #param val3
* third of the values used to calculate the average
* #param val4
* fourth of the values used to calculate the average
* #param variation
* adds variation to the average value
* #param map
* the height map to fill
* #param x
* the x-coordinate of the place to fill
* #param y
* the y-coordinate of the place to fill
*/
private static void calculateAndInsertAverage(int val1, int val2, int val3,
int val4, int variation, int[][] map, int x, int y) {
int avg = (val1 + val2 + val3 + val4) >> 2;// average
int var = (int) ((Math.random() * ((variation << 1) + 1)) - variation);
map[x][y] = avg + var;
}
public static void main(String[] args) {
}
}
Handler.java
package com.game.main;
import java.awt.Graphics;
import java.util.LinkedList;
public class Handler {
LinkedList<GameObject> object = new LinkedList<GameObject>();
LinkedList<Tiles> tile = new LinkedList<Tiles>();
public void tick(){
for (int i = 0; i < object.size(); i++){
GameObject tempObject = object.get(i);
tempObject.tick();
}
for (int t = 0; t < tile.size(); t++){
Tiles tempObject = tile.get(t);
tempObject.tick();
}
}
public void render(Graphics g){
for (int i = 0; i < object.size(); i++){
GameObject tempObject = object.get(i);
tempObject.render(g);
}
for (int t = 0; t < tile.size(); t++){
Tiles tempObject = tile.get(t);
tempObject.render(g);
}
}
public void addObject(GameObject object){
this.object.add(object);
}
public void removeObject(GameObject object){
this.object.remove(object);
}
public void addObject(Tiles tile){
this.tile.add(tile);
}
public void removeObject(Tiles tile){
this.tile.remove(tile);
}
}
Tiles.java
package com.game.main;
import java.awt.Graphics;
import java.util.LinkedList;
public class Handler {
LinkedList<GameObject> object = new LinkedList<GameObject>();
LinkedList<Tiles> tile = new LinkedList<Tiles>();
public void tick(){
for (int i = 0; i < object.size(); i++){
GameObject tempObject = object.get(i);
tempObject.tick();
}
for (int t = 0; t < tile.size(); t++){
Tiles tempObject = tile.get(t);
tempObject.tick();
}
}
public void render(Graphics g){
for (int i = 0; i < object.size(); i++){
GameObject tempObject = object.get(i);
tempObject.render(g);
}
for (int t = 0; t < tile.size(); t++){
Tiles tempObject = tile.get(t);
tempObject.render(g);
}
}
public void addObject(GameObject object){
this.object.add(object);
}
public void removeObject(GameObject object){
this.object.remove(object);
}
public void addObject(Tiles tile){
this.tile.add(tile);
}
public void removeObject(Tiles tile){
this.tile.remove(tile);
}
}
TerrianTile.java
package com.game.main;
import java.awt.Color;
import java.awt.Graphics;
public class TerrianTile extends Tiles {
public TerrianTile(int x, int y, int tileType) {
super(x, y, tileType);
}
public void tick(){
}
public void render(Graphics g){
if (tileType <= 0){
g.setColor(Color.BLUE);
g.fillRect(x, y, 10, 10);
}
//water
if (tileType > 0 && tileType < 40){
g.setColor(Color.BLUE);
g.fillRect(x, y, 10, 10);
}
//sand
if (tileType >= 40 && tileType < 55){
g.setColor(Color.YELLOW);
g.fillRect(x, y, 10, 10);
}
//grass
if (tileType >= 55 && tileType < 120){
g.setColor(Color.GREEN);
g.fillRect(x, y, 10, 10);
}
//forest
if (tileType >= 120 && tileType < 140){
g.setColor(Color.LIGHT_GRAY);
g.fillRect(x, y, 10, 10);
}
//stone
if (tileType >= 140 && tileType < 170){
g.setColor(Color.GRAY);
g.fillRect(x, y, 10, 10);
}
//snow
if (tileType >= 170){
g.setColor(Color.WHITE);
g.fillRect(x, y, 10, 10);
}
}
}
KeyInput.java
package com.game.main;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public class KeyInput extends KeyAdapter{
private Handler handler;
public KeyInput(Handler handler){
this.handler = handler;
}
public void keyPressed(KeyEvent e){
int key = e.getKeyCode();
for (int i = 0; i < handler.object.size(); i++){
GameObject tempObject = handler.object.get(i);
if (tempObject.getId() == ID.Player){
// key events for player 1
if (key == KeyEvent.VK_W) tempObject.setVelY(-5);
if (key == KeyEvent.VK_S) tempObject.setVelY(5);
if (key == KeyEvent.VK_D) tempObject.setVelX(5);
if (key == KeyEvent.VK_A) tempObject.setVelX(-5);
}
}
}
public void keyReleased(KeyEvent e){
int key = e.getKeyCode();
for (int i = 0; i < handler.object.size(); i++){
GameObject tempObject = handler.object.get(i);
if (tempObject.getId() == ID.Player){
// key events for player 1
if (key == KeyEvent.VK_W) tempObject.setVelY(0);
if (key == KeyEvent.VK_S) tempObject.setVelY(0);
if (key == KeyEvent.VK_D) tempObject.setVelX(0);
if (key == KeyEvent.VK_A) tempObject.setVelX(0);
}
}
}
}
Not sure if there is way to container the tiles in a rectangle and then move the rectangle. Any help would be much appreciated.
I dont really know what you mean by moving the tiles in a rectangle but if you just simply want to move all of them in a certain direction you could use g.translate(dx, dY) before you draw them.
Or if the terrain doesnt change you could draw them on an image at the beginning and then draw the image each frame at diffrent positions.

Categories