(Sorry for the long post... at least it has pictures?)
I have written an algorithm that creates a mosaic from an image by statistically generating N convex polygons that cover the image with no overlap. These polygons have anywhere between 3-8 sides, and each side has an angle that is a multiple of 45 degrees. These polygons are stored internally as a rectangle with displacements for each corner. Below is an image that explains how this works:
getRight() returns x + width - 1, and getBottom() returns y + height - 1. The class is designed to maintain a tight bounding box around filled pixels so the coordinates shown in this image are correct. Note that width >= ul + ur + 1, width >= ll + lr + 1, height >= ul + ll + 1, and height >= ur + ul + 1, or there would be empty pixels on a side. Note also that it is possible for a corner's displacement to be 0, thus indicating all pixels are filled in that corner. This enables this representation to store 3-8 sided convex polygons, each of whose sides are at least one pixel in length.
While it's nice to mathematically represent these regions, I want to draw them so I can see them. Using a simple lambda and a method that iterates over each pixel in the polygon, I can render the image perfectly. As an example, below is Claude Monet's Woman with a Parasol using 99 polygons allowing all split directions.
The code that renders this image looks like this:
public void drawOnto(Graphics graphics) {
graphics.setColor(getColor());
forEach(
(i, j) -> {
graphics.fillRect(x + i, y + j, 1, 1);
}
);
}
private void forEach(PerPixel algorithm) {
for (int j = 0; j < height; ++j) {
int nj = height - 1 - j;
int minX;
if (j < ul) {
minX = ul - j;
} else if (nj < ll) {
minX = ll - nj;
} else {
minX = 0;
}
int maxX = width;
if (j < ur) {
maxX -= ur - j;
} else if (nj < lr) {
maxX -= lr - nj;
}
for (int i = minX; i < maxX; ++i) {
algorithm.perform(i, j);
}
}
}
However, this is not ideal for many reasons. First, the concept of graphically representing a polygon is now part of the class itself; it is better to allow other classes whose focus is to represent these polygons. Second, this entails many, many calls to fillRect() to draw a single pixel. Finally, I want to be able to develop other methods of rendering these polygons than drawing them as-is (for example, performing weighted interpolation over the Voronoi tessellation represented by the polygons' centers).
All of these point to generating a java.awt.Polygon that represents the vertices of the polygon (which I named Region to differentiate from the Polygon class). No problem; I wrote a method to generate a Polygon that has the corners above with no duplicates to handle the cases that a displacement is 0 or that a side has only one pixel on it:
public Polygon getPolygon() {
int[] xes = {
x + ul,
getRight() - ur,
getRight(),
getRight(),
getRight() - lr,
x + ll,
x,
x
};
int[] yes = {
y,
y,
y + ur,
getBottom() - lr,
getBottom(),
getBottom(),
getBottom() - ll,
y + ul
};
int[] keptXes = new int[8];
int[] keptYes = new int[8];
int length = 0;
for (int i = 0; i < 8; ++i) {
if (
length == 0 ||
keptXes[length - 1] != xes[i] ||
keptYes[length - 1] != yes[i]
) {
keptXes[length] = xes[i];
keptYes[length] = yes[i];
length++;
}
}
return new Polygon(keptXes, keptYes, length);
}
The problem is that, when I try to use such a Polygon with the Graphics.fillPolygon() method, it does not fill all of the pixels! Below is the same mosaic rendered with this different method:
So I have a few related questions about this behavior:
Why does the Polygon class not fill in all these pixels, even though the angles are simple multiples of 45 degrees?
How can I consistently code around this defect (as far as my application is concerned) in my renderers so that I can use my getPolygon() method as-is? I do not want to change the vertices it outputs because I need them to be precise for center-of-mass calculations.
MCE
If the above code snippets and pictures are not enough to help explain the problem, I have added a Minimal, Complete, and Verifiable Example that demonstrates the behavior I described above.
package com.sadakatsu.mce;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Main {
#FunctionalInterface
private static interface PerPixel {
void perform(int x, int y);
}
private static class Region {
private int height;
private int ll;
private int lr;
private int width;
private int ul;
private int ur;
private int x;
private int y;
public Region(
int x,
int y,
int width,
int height,
int ul,
int ur,
int ll,
int lr
) {
if (
width < 0 || width <= ll + lr || width <= ul + ur ||
height < 0 || height <= ul + ll || height <= ur + lr ||
ul < 0 ||
ur < 0 ||
ll < 0 ||
lr < 0
) {
throw new IllegalArgumentException();
}
this.height = height;
this.ll = ll;
this.lr = lr;
this.width = width;
this.ul = ul;
this.ur = ur;
this.x = x;
this.y = y;
}
public Color getColor() {
return Color.BLACK;
}
public int getBottom() {
return y + height - 1;
}
public int getRight() {
return x + width - 1;
}
public Polygon getPolygon() {
int[] xes = {
x + ul,
getRight() - ur,
getRight(),
getRight(),
getRight() - lr,
x + ll,
x,
x
};
int[] yes = {
y,
y,
y + ur,
getBottom() - lr,
getBottom(),
getBottom(),
getBottom() - ll,
y + ul
};
int[] keptXes = new int[8];
int[] keptYes = new int[8];
int length = 0;
for (int i = 0; i < 8; ++i) {
if (
length == 0 ||
keptXes[length - 1] != xes[i] ||
keptYes[length - 1] != yes[i]
) {
keptXes[length] = xes[i];
keptYes[length] = yes[i];
length++;
}
}
return new Polygon(keptXes, keptYes, length);
}
public void drawOnto(Graphics graphics) {
graphics.setColor(getColor());
forEach(
(i, j) -> {
graphics.fillRect(x + i, y + j, 1, 1);
}
);
}
private void forEach(PerPixel algorithm) {
for (int j = 0; j < height; ++j) {
int nj = height - 1 - j;
int minX;
if (j < ul) {
minX = ul - j;
} else if (nj < ll) {
minX = ll - nj;
} else {
minX = 0;
}
int maxX = width;
if (j < ur) {
maxX -= ur - j;
} else if (nj < lr) {
maxX -= lr - nj;
}
for (int i = minX; i < maxX; ++i) {
algorithm.perform(i, j);
}
}
}
}
public static void main(String[] args) throws IOException {
int width = 10;
int height = 8;
Region region = new Region(0, 0, 10, 8, 2, 3, 4, 1);
BufferedImage image = new BufferedImage(
width,
height,
BufferedImage.TYPE_3BYTE_BGR
);
Graphics graphics = image.getGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, width, height);
region.drawOnto(graphics);
ImageIO.write(image, "PNG", new File("expected.png"));
image = new BufferedImage(
width,
height,
BufferedImage.TYPE_3BYTE_BGR
);
graphics = image.getGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, width, height);
graphics.setColor(Color.BLACK);
graphics.fillPolygon(region.getPolygon());
ImageIO.write(image, "PNG", new File("got.png"));
}
}
I spent all day working on it, and I seem to have a fix for this. The clue was found in the documentation for the Shape class, which reads:
Definition of insideness: A point is considered to lie inside a Shape if and only if:
it lies completely inside theShape boundary or
it lies exactly on the Shape boundary and the space immediately adjacent to the point in the increasing X direction is entirely inside the boundary or
it lies exactly on a horizontal boundary segment and the space immediately adjacent to the point in the increasing Y direction is inside the boundary.
Actually, this text is a bit misleading; the third case overrides second (i.e., even if a pixel in a horizontal boundary segment on the bottom of a Shape has a filled point to its right, it still will not be filled). Represented pictorially, the Polygon below will not draw the x'ed out pixels:
The red, green, and blue pixels are part of the Polygon; the rest are not. The blue pixels fall under the first case, the green pixels fall under the second case, and the red pixels fall under the third case. Note that all of the rightmost and lowest pixels along the convex hull are NOT drawn. To get them to be drawn, you have to move the vertices to the orange pixels as shown to make a new rightmost/bottom-most portion of the convex hull.
The easiest way to do this is to use camickr's method: use both fillPolygon() and drawPolygon(). At least in the case of my 45-degree-multiple-edged convex hulls, drawPolygon() draws the lines to the vertices exactly (and probably for other cases as well), and thus will fill the pixels that fillPolygon() misses. However, neither fillPolygon() nor drawPolygon() will draw a single-pixel Polygon, so one has to code a special case to handle that.
The actual solution I developed in trying to understand the insideness definition above was to create a different Polygon with the modified corners as shown in the picture. It has the benefit (?) of calling the drawing library only once and automatically handles the special case. It probably is not actually optimal, but here is the code I used for anyone's consideration:
package com.sadakatsu.mosaic.renderer;
import java.awt.Polygon;
import java.util.Arrays;
import com.sadakatsu.mosaic.Region;
public class RegionPolygon extends Polygon {
public RegionPolygon(Region region) {
int bottom = region.getBottom();
int ll = region.getLL();
int lr = region.getLR();
int right = region.getRight();
int ul = region.getUL();
int ur = region.getUR();
int x = region.getX();
int y = region.getY();
int[] xes = {
x + ul,
right - ur + 1,
right + 1,
right + 1,
right - lr,
x + ll + 1,
x,
x
};
int[] yes = {
y,
y,
y + ur,
bottom - lr,
bottom + 1,
bottom + 1,
bottom - ll,
y + ul
};
npoints = 0;
xpoints = new int[xes.length];
ypoints = new int[xes.length];
for (int i = 0; i < xes.length; ++i) {
if (
i == 0 ||
xpoints[npoints - 1] != xes[i] ||
ypoints[npoints - 1] != yes[i]
) {
addPoint(xes[i], yes[i]);
}
}
}
}
Related
I'm brand new to java and I'm making a snake game. My next step is to add a rectangle to the snake whenever it eats food. My current thinking is, I want to add an identical rectangle that's translated to the previous position of (mouseX, mouseY). At least, translated a distance of one rectangle from the previous one, but oriented where the mouse previously was, to "follow" behind the piece in front of it. I'm not sure how to go about doing that, but here is my code thus far.
//snake
void snake() {
rect(mouseX, mouseY, 10, 10);
}
class Snake {
//variables
int len;
int wid;
int xcord;
int ycord;
//constructor
Snake(int x,int y, int len, int wid) {
this.len = len;
this.wid = wid;
this.xcord = x;
this.ycord = y;
rect(xcord, ycord, wid, len);
}
//clear screen
void update() {
background(255);
rectMode(CENTER);
rect(mouseX, mouseY, wid, len);
}
}
class Food {
//variables
int xcord;
int ycord;
int wid;
int len;
//constructor
Food() {
this.xcord = int(random(width - 5));
this.ycord = int(random(height - 5));
this.wid = 10;
this.len = 10;
rect(xcord, ycord, wid, len);
}
//update food position
void update() {
if( (mouseX > xcord) && (mouseX < xcord + wid) &&
(mouseY > ycord) && (mouseY < ycord + len)) {
xcord = int(random(width - 5));
ycord = int(random(height - 5));
//lengthen snake
}
}
//display food
void displayFood() {
rect(xcord, ycord, 10, 10);
}
}
Snake s;
Food f;
void setup() {
background(255);
s = new Snake(mouseX, mouseY, 10, 10);
f = new Food();
}
void draw() {
s.update();
f.update();
f.displayFood();
}
You make 2 variables
float pMouseX = mouseX;
float pMouseY = mouseY;
Then, in draw, after updating the snake, you update those variables:
s.update
pMouseX = mouseX;
pMouseX = mouseY;
For having more than 2 rectangles, instead of single variables, consider using an ArrayList of arrays.
An Array is basically a way to store multiple variables in one variable.
An ArrayList is similar, but it doesn't have a set size. This means that you can keep adding elements to ArrayLists, which is something you can't do with Arrays.
You can declare such an ArrayList like this:
ArrayList<float[]> arrayList = new ArrayList<float[]>(); //each element of the ArrayList is an array, which contains an x and y position
To get the x and y coordinates of any rectangle, use arrayList.get(indexOfTheRectangle)[0] //use 1 instead of 0 for the y coordinate
and update them like this:
for (int i = arrayList.length - i; i > 0; i++) { //you need to go through the array backwards, because otherwise, for each element, you end up changing the value it is supposed to get, which results in all elements having the same value.
arrayList[i] = arrayList[i - 1];
}
arrayList[0] = new float[]{mouseX, mouseY}
I'm having some problems whit drawing a sierpinski carpet, and would apreciate any help.
I was able to define the stoping condition, draw the central rectangle, and recursively, draw the next level of the image, all while keeping count.
It just so happens that I can only draw on the top left side. I'd say I'm confusing variables, but I can't seem to figure it out. Would apreciate any help
This is the part of the code where i'm having problems.
int smallerWidth = newWidth / 3;
int smallerHeight = newHeight / 3;
int sX = 0;
int sY = 0;
if (currentDeep > 1) {
for (int i = 0; i < 3; i++) {
sX = width / 9 + (i * 3 * (width / 9));
sY = height / 9;
g.fillRect(sX, sY, smallerWidth, smallerHeight);
for (int j = 0; j < 3; j++) {
sY = height / 9 + (j * 3 * (height / 9));
g.fillRect(sX, sY, smallerWidth, smallerHeight);
}
}
return 1 + printSquares(g, sX, sY, newWidth, newHeight, currentDeep
- 1);
} else
return 1;
}
This is the full code
https://pastebin.com/WPJ5tG8w
In sum my question is. What should I change/create in order for my program to draw the remaining 7 squares?
The issue with your code is, that you are trying to perform actions for multiple layers of the recursion at once. Normally, in the recursion, you would only paint the Quadrado central, calculate the sizes and coordinates of the smaller rectangles, and call the method recursively. That way you ensure that the recursive calls do not influence the stuff that is already there.
private int printSquares(Graphics g, int xi, int yi, int width, int height, int currentDeep) {
//Quadrado central
int newWidth = width / 3;
int newHeight = height / 3;
int x = (width / 3) + xi;
int y = (height / 3) + yi;
g.fillRect(x, y, newWidth, newHeight);
int sX = 0;
int sY = 0;
if (currentDeep > 1) {
int sum = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
//This is the position of each of the small rectangles
sX = i * (width / 3) + xi;
sY = j * (height / 3) + yi;
// Call the method recursively in order to draw the smaller rectangles
sum += printSquares(g, sX, sY, newWidth, newHeight, currentDeep - 1);
}
}
return 1 + sum;
} else
return 1;
}
I hope, this resolves you issue.
I hope this is ok. This is amazing code. I took the liberty to complete this with the original code provided in the question and added the code that fixed it that Illedhar recommended as a added method. Here it is. Thank you for sharing this.
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class sierpinskicarpet {
public static Color BACKGROUNDCOLOR = new Color(0, 0, 150);
public static Color FOREGROUNDCOLOR = new Color(255, 180, 0);
// Padrao = 5, alterado
public static int DEEP = 10;
/**
* Build the frame and shows it
*/
public sierpinskicarpet(int deep) {
// the frame and title
JFrame frame = new JFrame();
frame.setTitle("...: Recursive Squares with deep " + deep + " :...");
// Dispose frame on click on close button
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
// set size and center frame on screen
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
// add print area occupying all the frame content area
frame.add(new PrintArea(deep));
// put frame visible
frame.setVisible(true);
}
/**
* Main method
*/
public static void main(String[] args)
{
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
// launch for 1 to DEEP squares frames
for (int i = DEEP; i >= 1; --i) {
// build a new object each time: objects will run
// independently
new sierpinskicarpet(i);
}
}
});
}
}
/**
* Our print area is, in fact, a label extended with the paint squares behavior
*/
class PrintArea extends JLabel {
private static final long serialVersionUID = 1L;
// local deep variable, will keep the registered deep for this the print
// area
int deep;
/**
* constructor
*/
public PrintArea(int deep) {
// call super, that is JLabel, constructor
super();
// set background color and set as well opaque to allow the background
// to be visible
setBackground(sierpinskicarpet.BACKGROUNDCOLOR);
setOpaque(true);
// save the deep
this.deep = deep;
}
/**
* paint method, called by JVM, when it is needed to update the PrintArea
*/
public void paint(Graphics g) {
// call paint from the JLABEL, draws the background of the PrintArea
super.paint(g);
// set drawing color
g.setColor(sierpinskicarpet.FOREGROUNDCOLOR);
// call the amazing print square method
int n = printSquares(g, 0, 0, getWidth(), getHeight(), this.deep);
// put to the world how much squares we printed
System.out.println("Deep = " + deep + ", squares painted: " + n);
}
/**
* Auxiliary method that will to the work. It must print a square with 1/3
* of the length of the frame and at the center and if not the bottom level
* ask to do the same for each of the other 8 square with 1/3 of length but
* called with the new deep
*/
private int printSquares(Graphics g, int xi, int yi, int width, int height, int currentDeep) {
//Quadrado central
int newWidth = width / 3;
int newHeight = height / 3;
int x = (width / 3) + xi;
int y = (height / 3) + yi;
g.fillRect(x, y, newWidth, newHeight);
int sX = 0;
int sY = 0;
if (currentDeep > 1) {
int sum = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
//This is the position of each of the small rectangles
sX = i * (width / 3) + xi;
sY = j * (height / 3) + yi;
// Call the method recursively in order to draw the smaller rectangles
sum += printSquares(g, sX, sY, newWidth, newHeight, currentDeep - 1);
}
}
return 1 + sum;
} else
return 1;
}
}
/*
Works Cited:
Recursive changing variables - sierpinski carpet. Stack Overflow. Retrieved May 4, 2022,
from https://stackoverflow.com/questions/49945862/recursive-changing-variables-sierpinski-carpet
*/
I am attempting to make a rendering system with a depth map involved with the usual pixels for dealing with alpha. My problem is that no color is being set correctly! I have tried to debug using System.out.println and testing various components, but to no avail I have not found a solution.
The Variables
The variables that are involved with dealing with drawing, setting, and clearing of pixels are: private int[][] node, private int[] pixels, and private ArrayList<Integer> changedPixels.
private int[][] node deals with storing pixels and dealing with depth [depth][x + y * width] before transferring over to the BufferedImage pixels. The data is set to a clear black and the lowest depth it is a fully visible black.
private int[] pixels is the data from a BufferedImage to change it up, it is the only image every used! All data is by default fully visible black
private ArrayList<Integer> changedPixels deals with pixels that are there from the last frame so as to help boost FPS by not clearing the entire screen if not needed. Empty by default since not pixels were changed from a previous frame.
The Methods
I have several methods for the rendering system: setNode(int x, int y, int z, int color, int alpha, drawScreen(), and clearScreen(). I also have a drawing rectangle and sprite function which deals with adding pixels by calling the setNode() method to add in colors.
private void setNode(int x, int y, int z, int color, float alpha)
{
color = Pixel.getColor(alpha, color);
if (translate) // Move the pixel to the correct location
{
x -= transX;
y -= transY;
}
if (x < 0 || x >= width || y < 0 || y >= height || alpha <= 0.0f || nodeMap[z][x + y * width] == color) // Check if we need to draw the pixel
return;
for (int zz = z + 1; zz < maxDepth; zz++)
if (Pixel.getAlpha(nodeMap[zz][x + y * width]) >= 1)
return;
if (alpha < 1.0f) // If pixel isn't completely opaque, then set it's alpha to the given one
if (nodeMap[z][x + y * width] != color) // If color isn't equal to the one we supply, change it up correctly
color = Pixel.getColorBlend(color, nodeMap[z][x + y * width]);
if (color == Pixel.WHITE) System.out.println("Pixel is white at x: " + x + ", y: " + y);
nodeMap[z][x + y * width] = color;
}
public void drawScreen()
{
int color = clearColor;
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
for (int z = maxDepth - 1; z > 0; z--)
{
if (Pixel.getAlpha(nodeMap[z][x + y * width]) > 0f)
color = Pixel.getColorBlend(color, nodeMap[z][x + y * width]);
if (Pixel.getAlpha(color) >= 1f)
break;
}
if (pixels[x + y * width] != color)
{
pixels[x + y * width] = color;
changedPixels.add(x + y * width);
}
}
}
public void clearScreen()
{
for (Integer pixel : changedPixels)
{
for (int z = 0; z < maxDepth; z++)
{
if (z > 0)
nodeMap[z][pixel] = clearColor;
else
nodeMap[z][pixel] = bgColor;
}
}
changedPixels.clear();
}
public void drawRect(int offX, int offY, int z, int width, int height, int color)
{
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
setNode(x + offX, y + offY, z, color);
}
public static int getColorBlend(int color1, int color2)
{
float a1 = getAlpha(color1);
float a2 = getAlpha(color2);
float a = Math.max(a1, a2);
float r = ((getRed(color1) * a1) + (getRed(color2) * a2 * (1 - a1))) / a;
float g = ((getGreen(color1) * a1) + (getGreen(color2) * a2 * (1 - a1))) / a;
float b = ((getBlue(color1) * a1) + (getBlue(color2) * a2 * (1 - a1))) / a;
return Pixel.getColor(a, r, g, b);
}
The Test
What I do currently is initalize the rendering system and set the nodeMap and pixel map to the previously mentioned settings. After this has been completed a game engine begins and then a method in a gui button (you might need it), but it calls drawRect(0(x), 0(y), 1(z), 100(width), 20(height), Pixel.WHITE(color)) which works as I have testing to see if it's running the method and which pixels it's drawing to.
The Problem
The overall problem is that the screen is completely white, I can't quite figure out the reason! I do know it has nothing with the alpha blending, which works fine as I have used it will a previous version of a rendering system I did.
Any help is appreciate and sorry that this is quite a long question, I just wanted to make sure you had everything you may need help me solve this. I do realize this is not be very effiecent, but I still like the system. Thanks again!
I am trying to copy a set of tiles(Using Tiled and libGDX) that are within the camera's viewport. Right now I have a copy and paste code:
package com.divergent.tapdown;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.maps.tiled.TiledMapTile;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell;
public abstract class TileMapCopier {
public static TiledMapTile[][] copyRegion(TiledMapTileLayer layer, int x, int y, int width, int height) {
TiledMapTile[][] region = new TiledMapTile[width][height];
for (int ix = x; ix < x + width; ix++)
for (int iy = y; iy < y + height; iy++) {
Cell cell = layer.getCell(ix, iy);
if (cell == null)
continue;
region[ix - x][iy - y] = cell.getTile();
}
return region;
}
public static void pasteRegion(TiledMapTileLayer layer, TiledMapTile[][] region, int x, int y) {
for (int ix = x; ix < x + region.length; ix++)
for (int iy = y; iy < y + region[ix].length; iy++) {
Cell cell = layer.getCell(ix, iy);
if (cell == null) {
Gdx.app.debug(TileMapCopier.class.getSimpleName(), "pasteRegion: skipped [" + ix + ";" + iy + "]");
continue;
}
cell.setTile(region[ix - x][iy - y]);
}
}
}
This takes all the cells on the layer, and pastes it to the screen when I want it to:
public void show() {
final TiledMapTileLayer layer = ((TiledMapTileLayer) map.getLayers().get(0));
camera.position.x = layer.getWidth() * layer.getTileWidth() / 2;
camera.position.y = layer.getHeight() * layer.getTileHeight() / 2;
camera.zoom = 3;
Gdx.input.setInputProcessor(new InputAdapter() {
TiledMapTile[][] clipboard;
#Override
public boolean keyDown(int keycode) {
if(keycode == Keys.C) // copy
clipboard = TileMapCopier.copyRegion(layer, 0, 0, layer.getWidth(), layer.getHeight() / 2);
if(keycode == Keys.P) // paste
TileMapCopier.pasteRegion(layer, clipboard, 0, layer.getHeight() / 2);
return true;
}
});
}
This is great, but its not what I want. Instead of copying the whole layer I only want to copy what is inside of my camera's viewport at the time of copy. I then want to paste it out to the top of the screen and reset the camera's viewport in a way that makes that paste un noticable. (Im essentially taking the lower part of the screen and putting it at the top to generate new values beneath)
How can I do this?
Thanks!
You can go through all the tiles and if they are in bounds of the camera viewport, then add it into some array (or just do something with them). You could check the bounds like this:
for(int i = 0; i < allTiles.size; i++) {
Tile t = allTiles.get(i);
MapProperties props = t.getProperties();
float x = (float) props.get("x");
float y = (float) props.get("y");
if(x > camera.position.x && x < camera.position.x + camera.vieportWidth
y > camera.position.y && y < camera.position.y + camera.vieportHeight)
//do something with the tile, because it IS inside the camera sight
}
Ok so I am working on a game on Android. I need to implement pixel perfect collision detection. I already have the bounding boxes set up around each of the images, each bounding box is transformed to match the current rotation of the image. That all works great. I also have the pixel data from each bitmap stored in an array. Can someone help me figure out the most efficient way to go about detecting if the pixels overlap? Thanks in advance for any help!
I have based my code on Mayra's example and made bitmap pixel collision handling. I hope this will help.
public class CollisionUtil {
public static boolean isCollisionDetected(Sprite sprite1, Sprite sprite2){
Rect bounds1 = sprite1.getBounds();
Rect bounds2 = sprite2.getBounds();
if( Rect.intersects(bounds1, bounds2) ){
Rect collisionBounds = getCollisionBounds(bounds1, bounds2);
for (int i = collisionBounds.left; i < collisionBounds.right; i++) {
for (int j = collisionBounds.top; j < collisionBounds.bottom; j++) {
int sprite1Pixel = getBitmapPixel(sprite1, i, j);
int sprite2Pixel = getBitmapPixel(sprite2, i, j);
if( isFilled(sprite1Pixel) && isFilled(sprite2Pixel)) {
return true;
}
}
}
}
return false;
}
private static int getBitmapPixel(Sprite sprite, int i, int j) {
return sprite.getBitmap().getPixel(i-(int)sprite.getX(), j-(int)sprite.getY());
}
private static Rect getCollisionBounds(Rect rect1, Rect rect2) {
int left = (int) Math.max(rect1.left, rect2.left);
int top = (int) Math.max(rect1.top, rect2.top);
int right = (int) Math.min(rect1.right, rect2.right);
int bottom = (int) Math.min(rect1.bottom, rect2.bottom);
return new Rect(left, top, right, bottom);
}
private static boolean isFilled(int pixel) {
return pixel != Color.TRANSPARENT;
}
}
The basic idea is to create a bitmask for each object where you indicate in each pixel if the object is actually there or not. Then you compare each pixel of the bitmasks for the two objects.
You could minimize the number of pixels you need to check by calculating the rectangular area in which the two bounding boxes overlap. The pixels within this area are what you need to check.
Iterate through all of those pixels, and check if the pixel is filled in both objects. If any of them are, then you have a collision.
If your rectangles are aligned with the x/y axis, to find the overlap, find the left, right, top and bottom of the overlap. It would look something like this (I could have screwed up the edge cases, haven't tried this):
int left = max(obj1.left, obj2.left)
int right = min(obj1.right, obj2.right)
int top = min(obj1.top, obj2.top)
int bottom = max(obj1.bottom, obj2.bottom)
for (int x = left; x < right; x++) {
for (int y = top; y < bottom; y++) {
if (obj1.isFilled(x,y) && obj2.isFilled(x,y)) {
return true;
}
}
}
I changed arcones' code, so the method works with Bitmaps instead of Sprites.
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
public class KollisionsErkennung {
/**
* #param bitmap1 First bitmap
* #param x1 x-position of bitmap1 on screen.
* #param y1 y-position of bitmap1 on screen.
* #param bitmap2 Second bitmap.
* #param x2 x-position of bitmap2 on screen.
* #param y2 y-position of bitmap2 on screen.
*/
public static boolean isCollisionDetected(Bitmap bitmap1, int x1, int y1,
Bitmap bitmap2, int x2, int y2) {
Rect bounds1 = new Rect(x1, y1, x1+bitmap1.getWidth(), y1+bitmap1.getHeight());
Rect bounds2 = new Rect(x2, y2, x2+bitmap2.getWidth(), y2+bitmap2.getHeight());
if (Rect.intersects(bounds1, bounds2)) {
Rect collisionBounds = getCollisionBounds(bounds1, bounds2);
for (int i = collisionBounds.left; i < collisionBounds.right; i++) {
for (int j = collisionBounds.top; j < collisionBounds.bottom; j++) {
int bitmap1Pixel = bitmap1.getPixel(i-x1, j-y1);
int bitmap2Pixel = bitmap2.getPixel(i-x2, j-y2);
if (isFilled(bitmap1Pixel) && isFilled(bitmap2Pixel)) {
return true;
}
}
}
}
return false;
}
private static Rect getCollisionBounds(Rect rect1, Rect rect2) {
int left = (int) Math.max(rect1.left, rect2.left);
int top = (int) Math.max(rect1.top, rect2.top);
int right = (int) Math.min(rect1.right, rect2.right);
int bottom = (int) Math.min(rect1.bottom, rect2.bottom);
return new Rect(left, top, right, bottom);
}
private static boolean isFilled(int pixel) {
return pixel != Color.TRANSPARENT;
}
}
For my needs it works fast enough.
If anyone of you is interested, I'd like to share the code I wrote:
Important for you to know is that Sprite.getWidth() and Sprite.getHeight() simply return the width/height of the Bitmap that the Sprite holds. You can easily adjust the code for your needs, it should be pretty easy to understand how the code works :)
public static boolean touchesSprite(Sprite s1, Sprite s2) {
Bitmap b1 = s1.getBmp();
Bitmap b2 = s2.getBmp();
int xshift = s2.getX()-s1.getX();
int yshift = s2.getY()-s1.getY();
//Test if the Sprites overlap at all
if((xshift > 0 && xshift > s1.getWidth()) || (xshift < 0 && -xshift > s2.getWidth())) {
return false;
}
if((yshift > 0 && yshift > s1.getHeight()) || (yshift < 0 && -yshift > s2.getHeight())) {
return false;
}
//if they overlap, find out in which regions they do
int leftx, rightx, topy, bottomy;
int leftx2, topy2;
if(xshift >= 0) {
leftx = xshift;
leftx2 = 0;
rightx = Math.min(s1.getWidth(), s2.getWidth()+xshift);
} else {
rightx = Math.min(s1.getWidth(), s2.getWidth()+xshift);
leftx = 0;
leftx2 = -xshift;
}
if(yshift >= 0) {
topy = yshift;
topy2 = 0;
bottomy = Math.min(s1.getHeight(), s2.getHeight()+yshift);
} else {
bottomy = Math.min(s1.getHeight(), s2.getHeight()+yshift);
topy = 0;
topy2 = -yshift;
}
//then compare the overlapping regions,
//if in any spot both pixels are not transparent, return true
int ys = bottomy-topy;
int xs = rightx-leftx;
for(int x=0; x<xs; x++) {
for(int y=0; y<ys; y++) {
int pxl = b1.getPixel(leftx+x, topy+y);
int pxl2 = b2.getPixel(leftx2+x, topy2+y);
if(!((pxl & 0xff000000) == 0x0) && !((pxl2 & 0xff000000) == 0x0)) {
return true;
}
}
}
return false;
}