I am building a game with libgdx.
The game screen is a grid with Scene2D actors. Actors are displayed on the front.
I would like to draw a background that looks like a checkerboard, coloring every 2 cells with one color and the other cells with another color.
It is easy to do but I was wondering if there are classes in libgdx that could optimize such a background, to make it as light and optimized as possible. For example, coloring each cell individually should work but doesn't seem like the best approach.
I have started to dig into the TiledMapTileLayer class but I'd have to fill each cell with a Tile object and that seems heavier than just a color.
Ideally, I would like to simply define 2 colors, set coordinates of the cells, and fill them with the colors without having to use objects or color the cells one by one.
What would be the best approach?
I think the most simple way would be, that you define 2 Tiles black and white or what ever and create an TiledMap on runtime just with those two tiles. It's just filling the cells in 2 for loops(x,y) with the same tiles. Should be easy to manage. Put the texture of it inside of one texture so you minimize the rendertime. You than dont need to handle the rendering and stuff like that yourself. You just need to implement the creation of the board with the TiledMap system. There is no solution where you can just define 2 colors. You always need to iterate over the cells somehow i think.
Take a look at the example code of libgdx for creating a tiledmap on runtime.
You can nearly copy paste it and just change this line
cell.setTile(new StaticTiledMapTile(splitTiles[ty][tx]));
Simply add the texture region for the back and white there depending on which cell you are currently are. something like this
if(y % 2 !=0){
cell.setTile(new StaticTiledMapTile(blackTextureRegion)); //or what color you need
}else{
cell.setTile(new StaticTiledMapTile(whiteTextureRegion)); //or what color you need
}
this would now create black and white rows. I recomend to use a Atlas for the TextureRegions or define them yourself.
Play around with the x and y values and check which you need. Maybe change the if statement i wrote to the right you need.
If you really just want to define a color you need to create a Pixmap with the 32x32 filled with the color on runtime and create a texture out of it. You can than use this to create the Tilemap as shown above.
Here is how you can create the 32x32 tiles you need. You can even create just one texture with 32x64 for both tiles. Just create the TextureRegions of 0,0,32,32 and 32,0,32,32.
Pixmap pixmap = new Pixmap(64, 32, Format.RGBA8888);
pixmap.setColor(Color.BLUE); // add your 1 color here
pixmap.fillRectangle(0, 0, 32, 32);
pixmap.setColor(Color.RED); // add your 2 color here
pixmap.fillRectangle(32, 0, 32, 32);
// the outcome is an texture with an blue left square and an red right square
Texture t = new Texture(pixmap);
TextureRegion reg1 = new TextureRegion(t, 0, 0, 32, 32);
TextureRegion reg2 = new TextureRegion(t, 32, 0, 32, 32);
//now use this to create the StaticTiledMapTile
If you glue this together you should have your system you would like to have.
Here you go:
Pixmap pixmap = new Pixmap(64, 32, Format.RGBA8888);
pixmap.setColor(Color.BLUE); // add your 1 color here
pixmap.fillRectangle(0, 0, 32, 32);
pixmap.setColor(Color.RED); // add your 2 color here
pixmap.fillRectangle(32, 0, 32, 32);
// the outcome is an texture with an blue left square and an red right
// square
Texture t = new Texture(pixmap);
TextureRegion reg1 = new TextureRegion(t, 0, 0, 32, 32);
TextureRegion reg2 = new TextureRegion(t, 32, 0, 32, 32);
TiledMap map = new TiledMap();
MapLayers layers = map.getLayers();
for (int l = 0; l < 20; l++) {
TiledMapTileLayer layer = new TiledMapTileLayer(150, 100, 32, 32);
for (int x = 0; x < 150; x++) {
for (int y = 0; y < 100; y++) {
Cell cell = new Cell();
if (y % 2 != 0) {
if (x % 2 != 0) {
cell.setTile(new StaticTiledMapTile(reg1));
} else {
cell.setTile(new StaticTiledMapTile(reg2));
}
} else {
if (x % 2 != 0) {
cell.setTile(new StaticTiledMapTile(reg2));
} else {
cell.setTile(new StaticTiledMapTile(reg1));
}
}
layer.setCell(x, y, cell);
}
}
layers.add(layer);
}
To render this you simply create an OrthogonalTiledMapRenderer and call the render() method.
In an minimum example this is the output:
already has some parameters for width and height and layercount. I think you wont need more than one layer
Code:
public class MainClass implements ApplicationListener {
private OrthographicCamera camera;
private OrthogonalTiledMapRenderer render;
private final static int width = 150, height = 100, layercount = 1;
private TiledMap map;
#Override
public void create() {
float w = Gdx.graphics.getWidth();
float h = Gdx.graphics.getHeight();
camera = new OrthographicCamera(w, h);
Pixmap pixmap = new Pixmap(64, 32, Format.RGBA8888);
pixmap.setColor(Color.BLUE); // add your 1 color here
pixmap.fillRectangle(0, 0, 32, 32);
pixmap.setColor(Color.RED); // add your 2 color here
pixmap.fillRectangle(32, 0, 32, 32);
// the outcome is an texture with an blue left square and an red right
// square
Texture t = new Texture(pixmap);
TextureRegion reg1 = new TextureRegion(t, 0, 0, 32, 32);
TextureRegion reg2 = new TextureRegion(t, 32, 0, 32, 32);
map = new TiledMap();
MapLayers layers = map.getLayers();
for (int l = 0; l < layercount; l++) {
TiledMapTileLayer layer = new TiledMapTileLayer(width, height, 32,
32);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Cell cell = new Cell();
if (y % 2 != 0) {
if (x % 2 != 0) {
cell.setTile(new StaticTiledMapTile(reg1));
} else {
cell.setTile(new StaticTiledMapTile(reg2));
}
} else {
if (x % 2 != 0) {
cell.setTile(new StaticTiledMapTile(reg2));
} else {
cell.setTile(new StaticTiledMapTile(reg1));
}
}
layer.setCell(x, y, cell);
}
}
layers.add(layer);
}
render = new OrthogonalTiledMapRenderer(map);
render.setView(camera);
camera.translate(Gdx.graphics.getWidth() / 2,
Gdx.graphics.getHeight() / 2);
}
#Override
public void dispose() {
render.dispose();
map.dispose();
}
private static final float movmentspeed = 5f;
#Override
public void render() {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
camera.update();
render.setView(camera);
render.render();
if (Gdx.input.isKeyPressed(Keys.LEFT)) {
camera.translate(-movmentspeed, 0);
} else if (Gdx.input.isKeyPressed(Keys.RIGHT)) {
camera.translate(movmentspeed, 0);
} else if (Gdx.input.isKeyPressed(Keys.UP)) {
camera.translate(0, movmentspeed);
} else if (Gdx.input.isKeyPressed(Keys.DOWN)) {
camera.translate(0, -movmentspeed);
}
}
#Override
public void resize(int width, int height) {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
}
In the end. i can't tell you if this is efficient! Maybe there are more efficient ways but this create your thing on runtime and if you dont do the creation over and over again it shouldn't be a problem since you just do it once you load the game for example. I think its efficient because the render is efficient and just draw the tiles that are visible on the screen. Moreover you just use 1 Texture for the background so its just one OpenGl bind for the whole background.
You could always just use ShapeRenderer to create a bunch of filled squares - http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/graphics/glutils/ShapeRenderer.html
Related
I coded a program on Processing where all the pixels on the screen are scrambled, but around the cursor. The code works by replacing the pixels with a random pixel between 0 and the pixel the loop is currently on. To find that pixel, I used the code (y*width+x)-1. This code, however, is taking pixels from the entire screen. I want the code to instead take the pixels from a 40m square around the mouse coordinates. How can I do this?
import processing.video.*;
Capture video;
void setup() {
size(640, 480);
video = new Capture(this, 640, 480);
video.start();
}
void draw() {
loadPixels();
if (video.available()){
video.read();
video.loadPixels();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pixels[y*width+x] = video.pixels[y*video.width+(width-x-1)];
// the code should only be applied 20 pixels around the mouse
if (dist(mouseX, mouseY, x, y) < 20){
int d = int(random(0, y*width+x-1));
pixels[y*width+x] = video.pixels[d];
}
}
}
}
updatePixels();
}
You don't need to iterate through all the pixels to only change a few.
Luckily your sketch is the same size as the webcam feed, so you're on the right track using the x + (y + width) arithmetic to convert from a 2D array index to the 1D pixels[] index. Remember that you're sampling from a 1D array currently (random 0, coords). Even if you upate the start/end index that's still a range that will span a few full image rows which means pixels to the left and right of the effect selection. I recommend picking the random x, y indices in 2D, then converting these random values to 1D (as opposed to a single index from the 1D array).
Here's what I mean:
import processing.video.*;
Capture video;
void setup() {
size(640, 480);
video = new Capture(this, 640, 480);
video.start();
}
void draw() {
loadPixels();
if (video.available()) {
video.read();
video.loadPixels();
//for (int y = 0; y < height; y++) {
// for (int x = 0; x < width; x++) {
// pixels[y*width+x] = video.pixels[y*video.width+(width-x-1)];
// // the code should only be applied 20 pixels around the mouse
// if (dist(mouseX, mouseY, x, y) < 20) {
// int d = int(random(0, y*width+x-1));
// pixels[y*width+x] = video.pixels[d];
// }
// }
//}
// mouse x, y shorthand
int mx = mouseX;
int my = mouseY;
// random pixels effect size
int size = 40;
// half of size
int hsize = size / 2;
// 2D pixel coordinates of the effect's bounding box
int minX = mx - hsize;
int maxX = mx + hsize;
int minY = my - hsize;
int maxY = my + hsize;
// apply the effect only where the bounding can be applied
// e.g. avoid a border (of hsize) around edges of the image
if (mx >= hsize && mx < width - hsize &&
my >= hsize && my < height - hsize) {
for(int y = minY; y < maxY; y++){
for(int x = minX; x < maxX; x++){
// pick random x,y coordinates to sample a pixel from
int rx = (int)random(minX, maxX);
int ry = (int)random(minY, maxY);
// convert the 2D random coordinates to a 1D pixel[] index
int ri = rx + (ry * width);
// replace current pixel with randomly sampled pixel (within effect bbox)
pixels[x + (y * width)] = video.pixels[ri];
}
}
}
}
updatePixels();
}
(Note that the above isn't tested, but hopefully the point gets across)
I am working QCustomPlot with Qt and need to change the color of a particular vertical grid line within the graph please let us know how we can change that I attached the image of my requirement.
The bleo code solve the issue
GraphTesting(QCustomPlot * customPlot)
{
// generate some data:
QVector<double> x(101), y(101); // initialize with entries 0..100
for (int i = 0; i < 101; ++i)
{
x[i] = i; //i / 50.0 - 1; // x goes from -1 to 1
y[i] = x[i]/2; // let's plot a quadratic function
}
// create graph and assign data to it:
customPlot->addGraph();
customPlot->graph(0)->setData(x, y);
// give the axes some labels:
customPlot->xAxis->setLabel("x");
customPlot->yAxis->setLabel("y");
customPlot->rescaleAxes();
QCPItemLine *step = new QCPItemLine(customPlot);
step->setPen(QPen(QColor(140, 0, 0)));
double begin = 25;
double first = customPlot->yAxis->range().lower;
double end = customPlot->yAxis->range().upper; //example values
step->start->setCoords(begin, first);
step->end->setCoords(begin, end);
customPlot->replot();
}
I'm very new to JavaFX and currently working on a rouge like game in 2D, which uses GUI made with JavaFX. The moving character is drawn on the canvas with GraphicContext2D. There is only one canvas, and I'd like to solve my problem without overlaying if possible. Canvas is added to Borderpane as center, Scene created with this Borderpane, for the Stage this Scene was set. Canvas is redrawn after moving with character on KeyEvent. I've already made the Stage resizable. Everything is running well, with the exception that I just cannot keep my moving character on the center in the window and when canvas is bigger than the window it can move out of this window.
How can I keep my dear moving character in the middle?
OK, here is a very, very simplified version, but with same problem, character does not stay in middle, it can run off from window then return as canvas is set greater than window.
public class Main extends Application {
String[][] map = new String[40][20];
int[] playerCoords = {3,10};
//colored.png downloaded from https://kenney.nl/assets/bit-pack
Image tileset = new Image("/colored.png", 815 * 2, 373 * 2, true, false);
Canvas canvas = new Canvas(
20 * 32,
40 * 32);
GraphicsContext context = canvas.getGraphicsContext2D();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage){
BorderPane borderPane = new BorderPane();
borderPane.setCenter(canvas);
map[3][10] = "p";
refresh();
Scene scene = new Scene(borderPane);
scene.setOnKeyPressed(keyEvent -> {switch (keyEvent.getCode()) {
case UP:
this.movePlayer(true);
refresh();
break;
case DOWN:
this.movePlayer(false);
refresh();
break;}});
stage.setScene(scene);
stage.show();
}
void movePlayer(boolean up){
int direction = up ? -1 : 1;
int newPosRow = playerCoords[0] + direction;
if ( newPosRow >= 0 && newPosRow < map.length) {
map[playerCoords[0]][playerCoords[1]] = null;
playerCoords[0] = newPosRow;
map[playerCoords[0]][playerCoords[1]] = "p";
}
}
void refresh(){
context.setFill(Color.BLACK);
context.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
for (int i = 0; i < map.length; i++ ){
for (int j = 0; j < map[0].length; j++){
if (map[i][j] != null && map[i][j].equals("p")){
//player Tile 28, 0
context.drawImage(tileset, 952,0, 32,32,j * 32,i * 32, 32,32);
} else{
context.drawImage(tileset, 0,0, 32,32,j * 32,i * 32, 32,32);
}
}
}
}
}
Ok, so it seems there is not an easy JavaFX solution currently. But if someone knows one, thanks for sharing. :-)
In the meantime I thought about an algorythmic solution by transforming coords according to absolute and relative (player placed in middle) coordinates of the player. It's very far from perfect, it changes only the rowCoords of map, as the player can move only vertically in this example, and it works with a canvas not much greater than the window size. I needed to change only the refresh method. There is something I do not understand at the moment,
it works with substracting centerCol from player's row coord
int centerRow = map.length/2;
int centerCol = map[0].length/2;
int mapRowDiff = playerCoords[0] - centerCol;
not with substracting centerRow from player's row coord
int centerRow = map.length/2;
int centerCol = map[0].length/2;
int mapRowDiff = playerCoords[0] - centerRow;
But at least it started working.
And here is my refresh() with the transformation, it became a bit complex.
void refresh(){
context.setFill(Color.BLACK);
context.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
int centerRow = map.length/2;
int centerCol = map[0].length/2;
int mapRowDiff = playerCoords[0] - centerCol;
for (int i = 0; i < map.length; i++ ){
for (int j = 0; j < map[0].length; j++){
if (mapRowDiff >= 0
&& mapRowDiff < map.length) {
if (map[mapRowDiff][j] != null && map[mapRowDiff][j].equals("p")) {
//player Tile 28, 0
context.drawImage(tileset, 952, 0, 32, 32, j * 32, i * 32, 32, 32);
} else {
context.drawImage(tileset, 0, 0, 32, 32, j * 32, i * 32, 32, 32);
}
} else {
context.drawImage(tileset, 0, 34, 32, 32, j * 32, i * 32, 32, 32);
}
}
mapRowDiff++;
}
}
I have a program which visualizes several Images through an ImageView , which are Fit to a size of 55x55 pixels up from around 32x32 pixels.
Unfortunately, all images have a "border" of transparent background, so the images are displayed with a gap inbetween.
Is there a way to crop an Image in javaFX so that it gets reduced to the actual picture?
Example:
desired look (Badly cropped out by hand)
actual look
Afaik there is no build in method for this. As #Slaw mentioned in his comment, you need to use the PixelReader to check for empty rows/columns. Based on that info you can set the viewport property for the ImageView:
#Override
public void start(Stage primaryStage) {
// using stackoverflow logo, since your image is completely opaque
Image image = new Image("https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-icon.png");
ImageView imageView = new ImageView(image);
int w = (int) image.getWidth();
int h = (int) image.getHeight();
int firstNonEmptyColumn = 0;
int firstNonEmptyRow = 0;
int lastNonEmptyColumn = w - 1;
int lastNonEmptyRow = h - 1;
PixelReader reader = image.getPixelReader();
outer: for (; firstNonEmptyColumn < w; firstNonEmptyColumn++) {
for (int y = 0; y < h; y++) {
// stop, if most significant byte (alpha channel) is != 0
if ((reader.getArgb(firstNonEmptyColumn, y) & 0xFF000000) != 0) {
break outer;
}
}
}
if (firstNonEmptyColumn == w) {
imageView.setImage(null); // image completely transparent
} else {
outer: for (; lastNonEmptyColumn > firstNonEmptyColumn; lastNonEmptyColumn--) {
for (int y = 0; y < h; y++) {
if ((reader.getArgb(lastNonEmptyColumn, y) & 0xFF000000) != 0) {
break outer;
}
}
}
outer: for (; firstNonEmptyRow < h; firstNonEmptyRow++) {
// use info for columns to reduce the amount of pixels that need checking
for (int x = firstNonEmptyColumn; x <= lastNonEmptyColumn; x++) {
if ((reader.getArgb(x, firstNonEmptyRow) & 0xFF000000) != 0) {
break outer;
}
}
}
outer: for (; lastNonEmptyRow > firstNonEmptyRow; lastNonEmptyRow--) {
for (int x = firstNonEmptyColumn; x <= lastNonEmptyColumn; x++) {
if ((reader.getArgb(x, lastNonEmptyRow) & 0xFF000000) != 0) {
break outer;
}
}
}
// set viewport to only show the opaque parts
imageView.setViewport(new Rectangle2D(
firstNonEmptyColumn,
firstNonEmptyRow,
lastNonEmptyColumn - firstNonEmptyColumn + 1,
lastNonEmptyRow - firstNonEmptyRow + 1));
}
// visualize image bounds
Rectangle rect = new Rectangle(imageView.prefWidth(-1), imageView.prefHeight(-1), Color.LIGHTGREEN);
StackPane root = new StackPane(rect, imageView);
root.setStyle("-fx-background-color:blue");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
so this is my first post ever on asking a question about programming, so please be patient :)
For a little project in school I made a little physics class, handling collision. Although it worked out fine I still have a bug I couldn't figure out after some hours of searching and I still don't really know where the problem lies in.
For the implementation we used the on Java based language Processing which is used for an introduction to programming and prototyping.
With the a left mouseclick I can spawn some balls which collide pixel-wise with a certain color on the screen. When colliding with a 90 degree corner they just fall through the obstacle. Sadly I can't post a screenshot because of my lack in reputation.
So my question is about what the problem is. Someone I asked said it could be a problem with the dot product I use for calculating the new mirrored velocity, but I couldn't find anything in that direction. I suspect the error lies somewhere in the part where the new velocity is calculated, in the update method of the PhysicsEntity class.
So thanks to everyone who is answering, I am grateful for every useful hint :)
Here is my code, it consists of three classes. I am going to post everything so you can run the code yourself. If you don't have processing you'll need to download it from http://processing.org/ in order to run the code sample below.
Main.pde NOTE: This part is only an example for using my physics class.
ArrayList<PhysicsEntity> entities = new ArrayList<PhysicsEntity>();
boolean mouseClicked = false;
boolean paused = false;
void setup()
{
size(800, 600);
background(0);
frameRate(60);
}
void draw()
{
if (!paused)
{
clear();
float gameTime = 1 / frameRate;
loadPixels();
for (int x = 0; x < width; ++x)
{
for (int y = height - 100; y < height; ++y)
{
pixels[x + y * width] = color(0, 200, 0, 128);
}
}
for (int x = 0; x < width; ++x)
{
for (int y = 0; y < 20; ++y)
{
pixels[x + y * width] = color(0, 200, 0, 128);
}
}
for (int x = 0; x < 100; ++x)
{
for (int y = 0; y < height; ++y)
{
pixels[x + y * width] = color(0, 200, 0, 128);
}
}
for (int x = width - 100; x < width; ++x)
{
for (int y = 0; y < height; ++y)
{
pixels[x + y * width] = color(0, 200, 0, 128);
}
}
updatePixels();
if (mousePressed)
{
entities.add(new PhysicsEntity(new Vector2(width / 2, height / 2), new Vector2(random(-100, 100), random(-100, 100)), new Vector2(0.0f, 250.0f)));
}
for (int i = 0; i < entities.size(); ++i)
{
entities.get(i).update(gameTime);
entities.get(i).show();
}
}
}
Vector2.pde NOTE: This class is just necessary for calculting things in the physics class.
class Vector2
{
float a;
float b;
Vector2()
{
a = 0.0f;
b = 0.0f;
}
Vector2(float _a, float _b)
{
a = _a;
b = _b;
}
/* Return exact copy of the vector */
Vector2 Copy()
{
return new Vector2(a, b);
}
Vector2 Add(Vector2 vecB)
{
return new Vector2(a + vecB.a, b + vecB.b);
}
Vector2 Substract(Vector2 vecB)
{
return new Vector2(a - vecB.a, b - vecB.b);
}
/* Scale the vector by a scalar x */
Vector2 Scale(float x)
{
return new Vector2(a * x, b * x);
}
Vector2 Divide(float x)
{
return new Vector2(a / x, b / x);
}
float Dot(Vector2 vecB)
{
return (a * vecB.a + b * vecB.b);
}
float SqrLength()
{
return (pow(a, 2) + pow(b, 2));
}
float Length()
{
return sqrt(SqrLength());
}
boolean Equals(Vector2 vecB)
{
return (a != vecB.a || b != vecB.b) ? false : true;
}
}
Vector2 ZeroVector()
{
return new Vector2(0.0f, 0.0f);
}
PhysicsEntity.pde NOTE: That's the class where actually failed.
class PhysicsEntity
{
Vector2 m_Pos;
Vector2 m_PrevPos;
Vector2 m_Vel;
Vector2 m_Acc;
/* bouncyness in case of collision; gets multiplied with the velocity */
float m_fBouncyness = 1.0f;
color collisionKey = color(0, 200, 0, 128);
public PhysicsEntity(Vector2 _pos, Vector2 _vel, Vector2 _acc)
{
if (_vel == null)
_vel = new Vector2(0.0f, 0.0f);
m_Pos = new Vector2(_pos.a, _pos.b);
m_PrevPos = m_Pos;
m_Vel = _vel;
m_Acc = _acc;
}
public void update(float dt)
{
/* Euler Integration more accurate Version */
/* x = x + vt + 0.5*at^2 */
m_Pos = m_Pos.Add(m_Vel.Scale(dt)).Add(m_Acc.Scale(pow(dt, 2)).Scale(0.5));
/* v = v + at */
m_Vel = m_Vel.Add(m_Acc.Scale(dt));
/* Collision based on color key */
if (isCollidable(m_Pos.a, m_Pos.b, collisionKey))
{
float speed = m_Vel.Length();
if (speed > 0.0f)
{
/* normalized vector of velocity */
Vector2 velNorm = m_Vel.Divide(speed);
/* getting the floor normal */
Vector2 floorNorm = interp(m_Pos, m_PrevPos);
if (!floorNorm.Equals(ZeroVector()))
{
/* mirror velocity on floor normal vector */
/* C = A - (2 * B * (A dot B)) where A is original vector, B the mirror, C result. */
Vector2 mirVel = velNorm.Substract(floorNorm.Scale(2.0f).Scale(velNorm.Dot(floorNorm)));
/* caculate new velocity */
m_Vel = mirVel.Scale(speed).Scale(m_fBouncyness);
/* add to position to move out of collision */
m_Pos = m_Pos.Add(m_Vel.Scale(dt));
}
}
}
m_PrevPos = m_Pos;
}
public void show()
{
ellipse(m_Pos.a, m_Pos.b, 10, 10);
}
public Vector2 interp(Vector2 pos, Vector2 PrevPos)
{
/* Vector from previous position to current position */
Vector2 line = pos.Substract(PrevPos);
float iLength = line.Length();
Vector2 lineFraction = ZeroVector();
/* checks if there the is vectorlength greater zero that connects the current and the previous position */
if (iLength > 0.0f)
lineFraction = line.Divide(iLength);
/* loop from through positions between previous position and current position */
for (int i = 0; i <= iLength; ++i)
{
Vector2 normVec = getNormal(PrevPos.Add(lineFraction.Scale(i)), collisionKey);
if (!normVec.Equals(ZeroVector()))
return normVec;
}
return ZeroVector();
}
}
/* returns normal vector of a 2d landscape in a certain area */
public Vector2 getNormal(Vector2 pos, color col)
{
int area = 10;
/* prevent coordinates from being out of the window */
if (pos.a <= area || pos.a >= width - area || pos.b <= area || pos.b >= height - area)
return ZeroVector();
Vector2 avg = new Vector2();
float loops = 0;
/* loop through an area of pixels */
for (int x = -area; x <= area; ++x)
{
for (int y = -area; y <= area; ++y)
{
if (x*x + y*y <= area*area)
{
float sumX = pos.a + float(x);
float sumY = pos.b + float(y);
/* count collidable pixels in area */
if (isCollidable(sumX, sumY, col))
{
/* add up positions of these pixels */
avg.a += sumX;
avg.b += sumY;
++loops;
}
}
}
}
if (loops == 0)
return ZeroVector();
/* calculate average position */
avg = avg.Divide(loops);
/* calculate length of the vector from initial position to average position */
float avgLength = dist(avg.a, avg.b, pos.a, pos.b);
/* check if avgLenth is zero or in other words: if avg is equals to pos */
if (avgLength == 0.0f)
return ZeroVector();
/* calculate vector(connection vector) from initial position to average position */
Vector2 conVec = pos.Substract(avg);
/* return normalized connection vector */
return conVec.Divide(avgLength);
}
/* method to check if pixel on a certain position is collidable */
public boolean isCollidable(float pixelX, float pixelY, color col)
{
if (pixelX >= width || pixelX < 0 || pixelY >= height || pixelY < 0)
return false;
return pixels[int(pixelX) + int(pixelY) * width] == col;
}
Edit1:
So thanks to the friendly first replay I stripped my code by a few lines :) If there is still a problem with my post let me know!
I cant analyze correctness of your whole physic calculation but in my opinion problem is with calculation of new velocity and :
/* caculate new velocity */
m_Vel = mirVel.Scale(speed).Scale(m_fBouncyness);
/* add to position to move out of collision */
m_Pos = m_Pos.Add(m_Vel.Scale(dt));
Because if you change m_fBouncyness to real value simulating some gravitation (0.8f or less) your problem will never occur but if you change it to some unreal value like 2.0f you will lose all your balls after few bounces.
This indicate problem in algorithm. Your approach consist (in simple) of this steps in loop:
update position of ball
calculate new position
correct position depending on bounce
draw ball
Here can be problem because you calculate new position of ball - this position is out of black box so you calculate average position then new velocity and correct new position. Then draw ball and repeat but what if this new position is also out of the black box? This ball will bounce out of border ... this happens in corner because of calculation of average position (in corner you got far away from black box then at classic border (when you set m_fBouncyness to some bigger value this will happen even on normal border not only in corner!))
Hope this could help you to find your problem.
So finally I've got a solution.
It appears that the answer of Majlik was very helpful. According to his answer I did a few changes which I will explain now.
First of all I put the if-statement if (speed > 0.0f) way up, over the whole movement code so nothing happens anymore if the speed is too low. Of course you can define a certain treshold which works for you.
In addition to that I introduced an else-case, for the if(colliding) statement, in which the movement code is handled, so if the ball is currently colliding it doesn't move at all apart from the collision handling code.
Finally I thought of a new way to move the ball out of the collision. The suggestion of Maljik proved to be right. My previous method didn't move the ball out of the collision at all.
For that I made a while loop which loops as long as the ball is still in collision. In every runthrough the ball gets moved by a normalized vector with the same direction as my mirrored velocity vector. For safety reasons I still got an iterator incrementing every time, so it doesn't end in an infinite loop.
After all the solution was very obvious. But thanks to those who answered.
Below the new changed code:
public void update(float dt)
{
float speed = m_Vel.Length();
if (speed > 0.0f)
{
/* Collision based on color key */
if (isCollidable(m_Pos.a, m_Pos.b, collisionKey))
{
/* normalized vector of velocity */
Vector2 velNorm = m_Vel.Divide(speed);
/* getting the floor normal */
Vector2 floorNorm = interp(m_Pos, m_PrevPos);
if (!floorNorm.Equals(ZeroVector()))
{
/* mirror velocity on floor normal vector */
/* C = A - (2 * B * (A dot B)) where A is original vector, B the mirror, C result. */
Vector2 mirVel = velNorm.Substract(floorNorm.Scale(2.0f).Scale(velNorm.Dot(floorNorm)));
/* caculate new velocity */
m_Vel = mirVel.Scale(speed).Scale(m_fBouncyness);
int it = 0;
Vector2 normMirVel = mirVel.Divide(mirVel.Length());
while (isCollidable(m_Pos.a, m_Pos.b, collisionKey) && it < 100)
{
/* add to position to move out of collision */
m_Pos = m_Pos.Add(normMirVel);
++it;
}
}
}
else
{
/* Euler Integration more accurate Version */
/* x = x + vt + 0.5*at^2 */
m_Pos = m_Pos.Add(m_Vel.Scale(dt)).Add(m_Acc.Scale(pow(dt, 2)).Scale(0.5));
/* v = v + at */
m_Vel = m_Vel.Add(m_Acc.Scale(dt));
}
}
m_PrevPos = m_Pos;
}
Edit: I might that this is not an ideal soluation since the ball gets moved further than it should in this frame. Maybe you should only calculate the necessary distance to move out of collision and add the actual velocity step by step. Also you could compare the current velocity direction to the direction where it should go. If it's already moving in the right direction there is no interference needed.