2D isometric - SFML - Right formulas, wrong coordinate range - c++

I don't work with tiles but cubes drawn with sf::Vertex. Each cubes have 6 sides with 4 points each.
So i just have to cubes[numCube].sides()[numSide].... to select a side.
I create cubes layer.cpp :
for(int J = 0; J < mapSize; J++)
{
for(int I = 0; I < mapSize; I++)
{
x = (J - I) * (cubeSize/2);
y = (J + I) * (cubeSize/4);
c = new cube(cubeSize, x, y, z, I, J);
cs.push_back(*c);
}
}
In cube.cpp i create sides, then, in sides.cpp, i calcul each points' coordinates like this :
switch(typeSide)
{
case 0://DOWN_SIDE
light = 1;
tmp_x = x + (size/2);
tmp_y = y + (size/2);
p0 = new point(tmp_x, tmp_y, tmp_z);
tmp_x = x + size;
tmp_y = y + (3 * (size/4));
p1 = new point(tmp_x, tmp_y, tmp_z);
tmp_x = x + (size/2);
tmp_y = y + size;
p2 = new point(tmp_x, tmp_y, tmp_z);
tmp_x = x;
tmp_y = y + (3 * (size/4));
p3 = new point(tmp_x, tmp_y, tmp_z);
break;
case 1://BACK_LEFT_SIDE
//ETC. ....
Point.cpp :
/*
* point.cpp
*
* Created on: 21 nov. 2015
* Author: user
*/
#include "point.h"
point::point(float tx, float ty, float tz)
{
coords* dummyVar = new coords(tx, ty, tz);
coordinates = dummyVar;
}
std::vector<float> point::position()//Use : myPoint.getPosition[0] //get the x
{
std::vector<float> dummyVar;
dummyVar.push_back(coordinates->getX());
dummyVar.push_back(coordinates->getY() - coordinates->getZ());
return dummyVar;
}
void point::move(float tx, float ty, float tz)
{
coordinates->setX(tx);
coordinates->setY(ty);
coordinates->setZ(tz);
}
My problem come from the function i use to detect click :
if (event.type == sf::Event::MouseMoved)
{
currentSelectedCube = maps[currentMapID].getCubeIDAt(event.mouseMove.x, event.mouseMove.y, offsetLeft, offsetTop, enableOffset);
}
The function(don't bother with the comments) :
I try to get a cube's entry in my cube vector without 'for loop'.
Why ? to use less CPU when i click.
int map::getCubeIDAt(float x, float y, int offsetLeft, int offsetTop, bool enableOffset)//WIP ! //USED FOR CLICK DETECTION ON CUBES
{
//----------------------------------------------------------------//
int unsigned entry = -1;
int I = 0;
int J = 0;
//----------------------------------------------------------------//
if(currentLayerId() > -1)//If there is any layers
{
//IF CHECK IN MAP BOUDING BOX + ROTATION TO GOT DIAMOND SHAPE AREA(LAYER + OFFSETS)----------------------------------
//{
if(!enableOffset)//With offsets disabled
{
I = (y * 2 - x) / cubeSize;
J = (y * 2 + x) / cubeSize;
}
else //With offsets enabled
{
I = (((y-offsetTop)+(currentLayerId()*(cubeSize/2))) * 2 - (x-offsetLeft)) / cubeSize;
J = (((y-offsetTop)+(currentLayerId()*(cubeSize/2))) * 2 + (x-offsetLeft)) / cubeSize;
}
entry = I + J * size;
if (entry < 0 || entry >= layers()[currentLayerId()].cubes().size())
{
entry = -1;
}
else//DEBUG - DISPLAYING VALUES FOR TEST
{
std::cout << "Entry n°" << entry << " - ";
std::cout << "[" << I << "; " << J << "]" << std::endl;
}
//}
//END IF CHECK IN MAP BOUDING BOX + ROTATION TO GOT DIAMOND SHAPE AREA(LAYER + OFFSETS)----------------------------------
}
return entry;
}
The I-J and entryNumber are OK. i mean, for example, for the cube 0, i have I = 0; J = 0; etc ... This is working.
I don't understand why the coordinate range is like the red part(not accurate at 100%, i'm not a paint genius ha ha) in this picture :
But i should get that(2nd picture - the red part is where i click) :
But after few checks, the I-J and the entry i got are corresponding. This is so weird.
EDIT2:
Offsets and layer number implemented.
Problem left: wrong coordinates range.
Just in case, this is the 'function' handling events :
void GRAPHICS_HANDLER::listenEvents()
{
while (window->pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
window->close();
}
if(event.type == sf::Event::KeyPressed)
{
//DISPLAY/UNDISPLAY GRID -- DEBUG FUNCTION
if(event.key.code == sf::Keyboard::Escape)
{
if(grid)
grid = false;
else
grid = true;
}
//-----------------------------------------------------------------------------------DEBUG---------------------------------------------------------------//
if(event.key.code == sf::Keyboard::B)//ACTIVE BRUSHMODE -- NEED TO BLOCK IT WHEN ACCESS VIOLATION OF CUBES ARRAY(CRASH)
{
if(!brushMode)
{
brushMode = true;
std::cout << "Brush mode enabled" << std::endl;
}
else
{
brushMode = false;
std::cout << "Brush mode disabled" << std::endl;
}
}
if(event.key.code == sf::Keyboard::L)//ADD_LAYER
{
addLayer(getCurrentMapID());
}
if(event.key.code == sf::Keyboard::M)//DELETE_LAYER
{
deleteLayer(currentMapID, maps[currentMapID].currentLayerId());
}
if(event.key.code == sf::Keyboard::S)//ADD_LAYER
{
std::cout << "Select a texture: ";
std::cin >> currentSelectedTexture; std::cout << std::endl;
}
if(event.key.code == sf::Keyboard::Left)//Move in Layer
{
if(maps[currentMapID].currentLayerId() > 0)
{
maps[currentMapID].setCurrentLayerID(maps[currentMapID].currentLayerId()-1);
}
}
if(event.key.code == sf::Keyboard::Right)//Move in Layer
{
if(maps[currentMapID].currentLayerId() < maps[currentMapID].layers().size()-1)
{
maps[currentMapID].setCurrentLayerID(maps[currentMapID].currentLayerId()+1);
}
}
//-----------------------------------------------------------------------------------DEBUG---------------------------------------------------------------//
}
if (event.type == sf::Event::MouseMoved)
{
//--------------------------------------------------------------------------CURSOR-----------------------------------------------------------------------//
currentSelectedCube = maps[currentMapID].getCubeIDAt(event.mouseMove.x, event.mouseMove.y, offsetLeft, offsetTop, enableOffset);
//--------------------------------------------------------------------------CURSOR-----------------------------------------------------------------------//
}
if (event.type == sf::Event::MouseButtonPressed)
{
//--------------------------------------------------------------------------CURSOR-----------------------------------------------------------------------//
currentSelectedCube = maps[currentMapID].getCubeIDAt(event.mouseButton.x, event.mouseButton.y, offsetLeft, offsetTop, enableOffset);
//--------------------------------------------------------------------------CURSOR-----------------------------------------------------------------------//
if (event.mouseButton.button == sf::Mouse::Left)
{
//--------------------------------------------------------------------------CUBE CLICK DETECTION--------------------------------------------------//
if(maps.size() > 0 && maps[currentMapID].layers().size() > 0 && currentSelectedCube > -1)
{
cubeClicked = true;
}
}
if (event.mouseButton.button == sf::Mouse::Right)
{
if(maps.size() > 0 && maps[currentMapID].layers().size() > 0 && currentSelectedCube > -1)
{
maps[currentMapID].layers()[maps[currentMapID].currentLayerId()].cubes()[currentSelectedCube].setTexture(1);
}
}
//--------------------------------------------------------------------------CUBE CLICK DETECTION--------------------------------------------------//
}
}
}
EDIT3: I updated my code to allow me to draw only the down side of the cube, so i can do this(the grass) :
The coordinate range(the red isometric square shown before in the screenshots) change a little when i put flat square(green).
I don't know why, i prefer to precise it, just in case.

You need to store the "heigth" of each element from the tiles plane in order to distinguish which cube are you actually selecting (the closer to the observer):
Same screen coordinates, but different tiles.
It's not clear to me how you modeled your world, so I'll give you a partial algorithm to check what face of what cube is the one clicked. Please, adapt it to your actual code and to the classes you have written to make it work.
// I'll let you to add the offsets for the screen coordinates
I = (y * 2 - x) / cubeSize;
J = (y * 2 + x) / cubeSize;
// find out if it is a left or right triangle
if ( x < (J - I) * (cubeSize/2) ) {
// left triangle
for ( k = max_n_layer; k > -1; --k ) {
// you create the cubes nesting the I loop in the J loop, so to get the index of a cube,
// assuming that you have created all the cubes (even the invisible ones, like it seems from your code)
index = (J+1+k)*mapsize + I+1+k;
// I don't really get how you define the existence or not of a face, but I guess something like this:
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[top_side] != 0 ) {
// the face selected is the top side of cube[index] of layer k
// you have to return index and k to select the right face, or simply a pointer to that face
// if this makes any sense with how you have designed your model
return &map.layer[k].cubes[index].sides[top_side];
}
// now check for the side
index = (J+k)*mapsize + I+1+k;
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[right_side] != 0 ) {
return &map.layer[k].cubes[index].sides[right_side];
}
index = (J+k)*mapsize + I+k;
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[left_side] != 0 ) {
return &map.layer[k].cubes[index].sides[left_side];
}
}
} else {
// right triangle
for ( k = max_n_layer; k > -1; --k ) {
index = (J+1+k)*mapsize + I+1+k;
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[top_side] != 0 ) {
return &map.layer[k].cubes[index].sides[top_side];
}
index = (J+1+k)*mapsize + I+k;
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[left_side] != 0 ) {
return &map.layer[k].cubes[index].sides[left_side];
}
index = (J+k)*mapsize + I+k;
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[right_side] != 0 ) {
return &map.layer[k].cubes[index].sides[right_side];
}
}
}
// well, no match found. As I said is up to you to decide how to do in this case
return nullptr;
Edit
I suggest you to try another way.
Consider the screen as divided not by quadrangular tiles but by the triangles you already depicted. Every 2D tile of your model will be formed by two of those triangles and so all the sides of the cubes you want to draw. For every cube don't draw nor even create the back sides, those will never be drawn.
You can try to implement a sort of specialized z-buffer algorithm by storing for each one of the triangles you have to draw on the screen the index of the side which is closer to the observer.
The coordinates of the vertex of all the triangles are calculated (once) with the code you already have.
(I,J) //For every node (I,J) you have a left and a right triangle
. * .
(I+1,J) * . | . * (I,J+1)
*
(I+1,J+1)
You are creating your cubes layer by layer, I guess, each layer having a different heigth over the base plane. Create every side of the cube using the coordinates calculated earlier. For every face (only the 3 pointing to the observer) consider each one of its 2 triangles. You can easily determine if it is visible or not if you proceed in order, then you only have to update the ID stored in the corresponding triangle.
Once finished this fase, you'll have to draw each triangle once as you already have dropped the hidden ones.
To determine the inverse transformation from screen coordinates to cell indexes, you only have to calculate which triangle is hitted and then look up which ID correspond to that. So transform back x,y to I,J (you already have those equations) and choose the left triangle if x < (J-I)/cubesize the right one otherwise.

Related

Getting errors when trying to draw complex polygons with triangles in OpenGL

I am trying to draw complex 2d polygons in OpenGL. I wrote all my rendering methods with GL_TRIANGLES so I'm not trying to change to GL_TRIANGLE_STRIP or anything like that.
Essentially, I have a list of ordered coordinates and I want to create a polygon from them like this:
The method I was originally using was to create a triangle between the first vertex and the next two and do that until the triangle is betweeen the first and last two vertices. However, on an L shaped polygon as the one above, I get something like this:
As you can see, indexing the vertices this way draws triangles in areas where there should be no triangles. How can I index the vertices with GL_TRIANGLES to get something like the first result? The vertices will be different every single time but are always in clockwise order so I need a generalized approach for any polygon.
Decompose your polygon into triangles or use the stencil buffer method.
You can think of the problem in two stages consisting of turning the polygon into convex sub-polygons, and then triangulate each of the sub-polygons. The algorithm to triangulate a sub polygon (triangulatePoly) is a fairly simple recursive function that takes in a polygon and checks if it has 3 points. If it does, it returns, if not, it creates a triangle from the first 3 points adds it to a list and decrements the polygon by that triangle, leaving you with a list of triangles that comprise the polygon.
The convex sub-polygon algorithm (decomposePoly) is harder to explain as it is quite complicated and so if you want to understand it, it is here.
Finally, here is an implementation, written with OpenGL2 and quite clustered for brevity.
// ######################
public class Point {
public float x;
public float y;
public Point(float _x, float _y) {
x = _x;
y = _y;
}
public static float area(Point a, Point b, Point c) {
return (((b.x - a.x)*(c.y - a.y))-((c.x - a.x)*(b.y - a.y)));
}
public static boolean left(Point a, Point b, Point c) {
return area(a, b, c) > 0;
}
public static boolean leftOn(Point a, Point b, Point c) {
return area(a, b, c) >= 0;
}
public static boolean rightOn(Point a, Point b, Point c) {
return area(a, b, c) <= 0;
}
public static boolean right(Point a, Point b, Point c) {
return area(a, b, c) < 0;
}
public static float sqdist(Point a, Point b) {
float dx = b.x - a.x;
float dy = b.y - a.y;
return dx * dx + dy * dy;
}
}
// ######################
import java.util.Vector;
public class Polygon extends Vector<Point> {
#Override
public Point get(int i) {
// hacky way of getting the modulo
return super.get(((i % this.size()) + this.size()) % this.size());
}
}
// ######################
import org.lwjgl.*;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;
import java.nio.*;
import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.system.MemoryUtil.*;
import java.util.Collections;
import java.util.Vector;
public class DecomposePolyExample {
private long window;
private int WIDTH = 300;
private int HEIGHT = 300;
private float mouse_x = WIDTH / 2;
private float mouse_y = HEIGHT / 2;
private Polygon incPoly = new Polygon();
private Vector<Polygon> polys = new Vector<Polygon>();
private Vector<Polygon> tris = new Vector<Polygon>();
private Vector<Point> steinerPoints = new Vector<Point>();
private Vector<Point> reflexVertices = new Vector<Point>();
private boolean polyComplete = false;
public void run() {
System.out.println("Hello LWJGL" + Version.getVersion() + "!");
init();
loop();
// Free the window callbacks and destroy the window
glfwFreeCallbacks(window);
glfwDestroyWindow(window);
// Terminate GLFW and free the error callback
glfwTerminate();
glfwSetErrorCallback(null).free();
}
private void init() {
// Setup and error callback. The default implementation
// will print the error message in System.err.
GLFWErrorCallback.createPrint(System.err).set();
// Initialize GLFW. Most GLFW functions will not work before doing this.
if (!glfwInit()) {
throw new IllegalStateException("Unable to initialize GLFW");
}
// Create the window
window = glfwCreateWindow(WIDTH, HEIGHT, "Hello World!", NULL, NULL);
if (window == NULL) {
throw new RuntimeException("Failed to create the GLFW window");
}
// Setup a key callback. It will be called every time a key is pressed, repeated or released.
glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
}
});
glfwSetCursorPosCallback(window, (window, x, y) -> {
mouse_x = (float)x;
mouse_y = HEIGHT - (float)y;
});
glfwSetMouseButtonCallback(window, (window, button, action, mods) -> {
if (action != GLFW_PRESS){
return;
}
int lClick = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT);
if (lClick == GLFW_PRESS)
{
Point p = new Point(mouse_x, mouse_y);
incPoly.add(p);
}
int rClick = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT);
if (rClick == GLFW_PRESS)
{
polyComplete = true;
incPoly = makeCCW(incPoly);
decomposePoly(incPoly);
triangulatePoly(polys);
}
});
// Make the OpenGL context current
glfwMakeContextCurrent(window);
// Enable v-sync
glfwSwapInterval(1);
// Make the window visible
glfwShowWindow(window);
}
public Point toNDC(Point p) {
float x = 2*p.x / WIDTH - 1;
float y = 2*p.y / HEIGHT - 1;
return new Point(x, y);
}
public Polygon makeCCW(Polygon poly) {
int br = 0;
// find bottom right point
for (int i = 1; i < poly.size(); ++i) {
if (poly.get(i).y < poly.get(br).y || (poly.get(i).y == poly.get(br).y && poly.get(i).x > poly.get(br).x)) {
br = i;
}
}
// reverse poly if clockwise
if (!Point.left(poly.get(br - 1), poly.get(br), poly.get(br + 1))) {
Collections.reverse(poly);
}
return poly;
}
public boolean isReflex(Polygon poly, int i) {
return Point.right(poly.get(i - 1), poly.get(i), poly.get(i + 1));
}
public boolean eq(float a, float b) {
return Math.abs(a - b) <= 1e-8;
}
Point intersection(Point p1, Point p2, Point q1, Point q2) {
Point i = new Point(0,0);
float a1, b1, c1, a2, b2, c2, det;
a1 = p2.y - p1.y;
b1 = p1.x - p2.x;
c1 = a1 * p1.x + b1 * p1.y;
a2 = q2.y - q1.y;
b2 = q1.x - q2.x;
c2 = a2 * q1.x + b2 * q1.y;
det = a1 * b2 - a2*b1;
if (!eq(det, 0)) { // lines are not parallel
i.x = (b2 * c1 - b1 * c2) / det;
i.y = (a1 * c2 - a2 * c1) / det;
}
return i;
}
public void decomposePoly(Polygon poly) {
Point upperInt = new Point(0,0);
Point lowerInt = new Point(0,0);
Point p = new Point(0,0);
Point closestVert = new Point(0,0);
float upperDist, lowerDist, d, closestDist;
int upperIndex = 0;
int lowerIndex = 0;
int closestIndex = 0;
Polygon lowerPoly = new Polygon();
Polygon upperPoly = new Polygon();
for (int i = 0; i < poly.size(); ++i) {
if (isReflex(poly, i)) {
reflexVertices.add(poly.get(i));
upperDist = lowerDist = Float.MAX_VALUE;
for (int j = 0; j < poly.size(); ++j) {
if (Point.left(poly.get(i - 1), poly.get(i), poly.get(j))
&& Point.rightOn(poly.get(i - 1), poly.get(i), poly.get(j - 1))) { // if line intersects with an edge
p = intersection(poly.get(i - 1), poly.get(i), poly.get(j), poly.get(j - 1)); // find the point of intersection
if (Point.right(poly.get(i + 1), poly.get(i), p)) { // make sure it's inside the poly
d = Point.sqdist(poly.get(i), p);
if (d < lowerDist) { // keep only the closest intersection
lowerDist = d;
lowerInt = p;
lowerIndex = j;
}
}
}
if (Point.left(poly.get(i + 1), poly.get(i), poly.get(j + 1))
&& Point.rightOn(poly.get(i + 1), poly.get(i), poly.get(j))) {
p = intersection(poly.get(i + 1), poly.get(i), poly.get(j), poly.get(j + 1));
if (Point.left(poly.get(i - 1), poly.get(i), p)) {
d = Point.sqdist(poly.get(i), p);
if (d < upperDist) {
upperDist = d;
upperInt = p;
upperIndex = j;
}
}
}
}
// if there are no vertices to connect to, choose a point in the middle
if (lowerIndex == (upperIndex + 1) % poly.size()) {
p.x = (lowerInt.x + upperInt.x) / 2;
p.y = (lowerInt.y + upperInt.y) / 2;
steinerPoints.add(p);
if (i < upperIndex) {
for (int j = i; j < upperIndex + 1; j++) {
lowerPoly.add(poly.get(j));
}
lowerPoly.add(p);
upperPoly.add(p);
if (lowerIndex != 0) {
for (int j = lowerIndex; j < poly.size(); j++) {
upperPoly.add(poly.get(j));
}
}
for (int j = 0; j < i + 1; j++) {
upperPoly.add(poly.get(j));
}
} else {
if (i != 0) {
for (int j = 0; j < i; j++) {
lowerPoly.add(poly.get(j));
}
}
for (int j = 0; j < upperIndex + 1; j++) {
lowerPoly.add(poly.get(j));
}
lowerPoly.add(p);
upperPoly.add(p);
for (int j = lowerIndex; j < i + 1; j++) {
upperPoly.add(poly.get(j));
}
}
} else {
// connect to the closest point within the triangle
if (lowerIndex > upperIndex) {
upperIndex += poly.size();
}
closestDist = Float.MAX_VALUE;
for (int j = lowerIndex; j <= upperIndex; ++j) {
if (Point.leftOn(poly.get(i - 1), poly.get(i), poly.get(j))
&& Point.rightOn(poly.get(i + 1), poly.get(i), poly.get(j))) {
d = Point.sqdist(poly.get(i), poly.get(j));
if (d < closestDist) {
closestDist = d;
closestVert = poly.get(j);
closestIndex = j % poly.size();
}
}
}
if (i < closestIndex) {
for (int j = i; j < closestIndex + 1; j++) {
lowerPoly.add(poly.get(j));
}
if (closestIndex != 0) {
for (int j = closestIndex; j < poly.size(); j++) {
upperPoly.add(poly.get(j));
}
}
for (int j = 0; j < i + 1; j++) {
upperPoly.add(poly.get(j));
}
} else {
if (i != 0) {
for (int j = i; j < poly.size(); j++) {
lowerPoly.add(poly.get(j));
}
}
for (int j = 0; j < closestIndex + 1; j++) {
lowerPoly.add(poly.get(j));
}
for (int j = closestIndex; j < i + 1; j++) {
upperPoly.add(poly.get(j));
}
}
}
// solve smallest poly first
if (lowerPoly.size() < upperPoly.size()) {
decomposePoly(lowerPoly);
decomposePoly(upperPoly);
} else {
decomposePoly(upperPoly);
decomposePoly(lowerPoly);
}
return;
}
}
polys.add(poly);
}
public void triangulatePoly(Vector<Polygon> polys) {
for (int i = 0; i < polys.size(); i++) {
Polygon poly = polys.get(i);
// return if poly is a triangle
if (poly.size() == 3) {
tris.add(poly);
polys.remove(i);
}
else {
// split poly into new triangle and poly
Polygon tri = new Polygon();
for (int j = 0; j < 3; j++) {
tri.add(poly.get(j));
}
Polygon newPoly = new Polygon();
newPoly.add(poly.get(0));
for (int k = 2; k < poly.size(); k++) {
newPoly.add(poly.get(k));
}
polys.set(i, newPoly);
tris.add(tri);
}
}
if (polys.size() != 0) {
triangulatePoly(polys);
}
}
private void loop() {
GL.createCapabilities();
// Set the clear color
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
while (!glfwWindowShouldClose(window)) {
glClear(GL_COLOR_BUFFER_BIT); // clear the framebuffer
System.out.println(tris.size());
if (!polyComplete) {
GL11.glBegin(GL_LINE_STRIP);
for (int i = 0; i < incPoly.size(); ++i) {
Point p_ndc = toNDC(incPoly.get(i));
GL11.glVertex2f(p_ndc.x, p_ndc.y);
}
GL11.glEnd();
} else {
// polygon outlines (thin)
for (int i = 0; i < tris.size(); ++i) {
GL11.glBegin(GL_LINE_LOOP);
for (int j = 0; j < tris.get(i).size(); ++j) {
Point p_ndc = toNDC(tris.get(i).get(j));
GL11.glVertex2f(p_ndc.x, p_ndc.y);
}
GL11.glEnd();
}
GL11.glBegin(GL_LINE_LOOP);
for (int i = 0; i < incPoly.size(); ++i) {
Point p_ndc = toNDC(incPoly.get(i));
GL11.glVertex2f(p_ndc.x, p_ndc.y);
}
GL11.glEnd();
}
glfwSwapBuffers(window); // swap the color buffers
// Poll for window events. The key callback above will only be
// invoked during this call.
glfwPollEvents();
}
}
public static void main(String[] args) {
new DecomposePolyExample().run();
}
}
Demo:
In OpenGL only convex polygons can be drawn correctly. As mentioned in an an other answer you can use the Stencil Test buffet to draw a concave polygons. The algorithm is described in detail at
Drawing Filled, Concave Polygons Using the Stencil Buffer
or Drawing Filled, Concave Polygons Using the Stencil Buffer (OpenGL Programming).
Fraw the polygon by the Triangle primitiv type GL_TRIANGLE_FAN. e.g:
1 2
+-----+
| |
| |3 4
| +-----+
| |
| |
+-----------+
0 5
Draw the GL_TRIANGLE_FAN 1 - 2 - 3 - 4 - 5 - 0
Of course it is possible to start with any point e.g. 3 - 4 - 5 - 0 - 1 - 2
The polygon has to be draw twice. The first time the stencil buffer is set, but nothing is drawn in the color buffer at all. The stencil buffer is inverted, every time when a fragment is drawn. If a pixel is covered an even number of times, the value in the stencil buffers is zero; otherwise, it's 1.
At the end the polygon is drawn a 2nd time. This time the color buffer is drawn. The stencil test is enabled and ensures that only the fragments are drawn where the stencil buffer is 1:
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
GL11.glEnable(GL11.GL_STENCIL_TEST);
GL11.glColorMask(false, false, false, false);
GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_INVERT);
GL11.glStencilFunc(GL11.GL_ALWAYS, 0x1, 0x1);
// draw the polygon the 1st time: set the stencil buffer
// GL_TRIANGLE_FAN: 1 - 2 - 3 - 4 - 5 - 0
GL11.glColorMask(true, true, true, true);
GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
GL11.glStencilFunc(GL11.GL_EQUAL, 0x1, 0x1);
// draw the polygon the 2nd time: draw to color buffer by using the stencil test
// GL_TRIANGLE_FAN: 1 - 2 - 3 - 4 - 5 - 0
GL11.glDisable(GL11.GL_STENCIL_TEST);

C++/SDL - Rectangle Coordinates Mapping

I am working on a 2D game using SDL. Recently I implemented various functions that put objects (and their rectangles) into motion, but encountered performance issues that are most likely caused by inefficient mapping of rectangle coordinates. Please see below:
2D coordinates of the rectangle are stored in an integer array whenever move() is called. For example, coordinate[0] is the first point on the x axis and coordinate[1] is the last point on the x axis. Coordinates [2] and [3] work for points on the y axis.
The map() function takes the coordinates of a rectangle and stores them in static std::map (Status class). Each x and y pair is either 0 or 1, depending on whether a rectangle is present or not. Player's coordinates are not mapped.
When the player moves, the bool function collide() checks whether the player's rectangle is adjacent to another recantgle in a particular direction. If there is no rectangle blocking the way, the player is allowed to move.
Everything works well, but it seems like all these for loops in the map() function are very CPU-heavy. When rectangles are being moved on the screen, the program lags horribly. How can I map rectangle coordinates more efficiently?
void move(int x, int y) {
dstRect.x = x;
dstRect.y = y;
coordinate[0] = dstRect.x;
coordinate[1] = dstRect.x + dstRect.w;
coordinate[2] = dstRect.y;
coordinate[3] = dstRect.y + dstRect.h;
}
void map() {
for (int x = coordinate[0]; x != coordinate[1]; x++) {
for (int y = coordinate[2]; y != coordinate[3]; y++) {
Status::map().insert(std::pair<std::vector<int>, int>({ x, y }, 1));
}
}
}
bool collide(DIRECTION direction) {
if (direction == UP || direction == DOWN) {
for (int x = texture.coordinate[0]; x != texture.coordinate[1]; x++) {
if (direction == UP) {
if (Status::map().find({ x, texture.coordinate[2] - 1 })->second == 1) { return true; }
}
if (direction == DOWN) {
if (Status::map().find({ x, texture.coordinate[3] + 1 })->second == 1) { return true; }
}
}
}
if (direction == RIGHT || direction == LEFT) {
for (int y = texture.coordinate[2]; y != texture.coordinate[3]; y++) {
if (direction == RIGHT) {
if (Status::map().find({ texture.coordinate[1] + 1, y })->second == 1) { return true; }
}
if (direction == LEFT) {
if (Status::map().find({ texture.coordinate[0] - 1, y })->second == 1) { return true; }
}
}
}
return false;
}
void moveRight() {
for (int i = 0; i < speed; i ++) {
if (!collide(RIGHT)) {
int x = texture.dstRect.x + 1;
int y = texture.dstRect.y;
texture.move(x, y);
}
}
}
Followed #FrançoisAndrieux advice and created multidimensional vector for storing the coordinates.

C++ Recursive depth-first search maze algorithm and where to go now?

I've been making a attempt to work out a maze generator using a recursive depth first search style algorithm. I know it's been done many times in the past but for my own understanding I've been trying to implement it from scratch myself in order to better understand it.
So, this is my attempt so far.
#include <iostream>
#include <vector>
#include <algorithm>
#include <unistd.h>
struct vertex {
int x;
int y;
};
int main(int argc, char **argv)
{
// Init the map
int map_size = 24;
int half_point = map_size / 2;
int map[map_size+1][map_size+1];
for(int y = 0; y < map_size+1; y++) {
for(int x = 0; x < map_size+1; x++) {
map[x][y] = 0;
}
}
vertex start;
start.x = half_point;
start.y = half_point;
std::vector<vertex> history;
// Set start point as visited
history.push_back(start);
while( !history.empty() ) {
vertex v = history.back();
map[v.x][v.y] = 1;
// Calculate the directions for each vertex
vertex N; N.x = v.x+1; N.y = v.y;
vertex S; S.x = v.x-1; S.y = v.y;
vertex E; E.x = v.x; E.y = v.y-1;
vertex W; W.x = v.x; W.y = v.y + 1;
bool can_north = false; bool can_south = false; bool can_east = false; bool can_west = false;
// Check north and add if relevant
if( N.x >= 0 && N.x <= map_size && N.y >= 0 && N.y <= map_size && map[N.x][N.y] == 0 ) { can_north = true; }
// Check south and add if relevant
if( S.x >= 0 && S.x <= map_size && S.y >= 0 && S.y <= map_size && map[S.x][S.y] == 0 ) { can_south = true; }
// Check east and add if relevant
if( E.x >= 0 && E.x <= map_size && E.y >= 0 && E.y <= map_size && map[E.x][E.y] == 0 ) { can_east = true; }
// Check west and add if relevant
if( W.x >= 0 && W.x <= map_size && W.y >= 0 && W.y <= map_size && map[W.x][W.y] == 0 ) { can_west = true; }
std::vector<vertex> available;
if(can_north) { available.push_back(N); }
if(can_south) { available.push_back(S); }
if(can_east) { available.push_back(E); }
if(can_west) { available.push_back(W); }
// Select random element from availables
if( !available.empty() )
{
std::random_shuffle( available.begin(), available.end() );
vertex aV = available.back();
history.push_back(aV);
available.clear();
} else {
if( !history.empty() ) {
history.pop_back();
}
}
// Animate the output to the console
system("clear");
for(int y = 0; y < map_size+1; y++) {
for(int x = 0; x < map_size+1; x++) {
std::cout << map[x][y] << ", ";
}
std::cout << std::endl;
}
std::cout << std::endl;
usleep(5000);
}
return 0;
}
It's using a Linux system call in there to clear the terminal each time before displaying the update to have a basic animation of the paths it takes.
What I don't know and I trying to understand is...
1) Is this actually a recursive depth-first search algorithm?
If so, then at least I have understood the concept correctly.
2) How would I go about drawing this to an image file?
I will answer your first question
Is this actually a recursive depth-first search algorithm?
At the Recursive depth-first search algorithm each cell has on of three states (Unvisitied, Visit In Progress and Visitied). The idea is to traverse through a graph and visit each node. If a node has 'Unvisited' condition it becomes 'Visit In Progress' and each neighbor will be visited. After each neighbor was visited the node becomes 'Visited'. To do so you have to remember each cell in your maze you visited once and never travers it again. Each member of history full fill condition 'Visit In Progress', and each cell which is set in map full fill condition 'Visited'. My answer is: Yes.
Comment to your second question:
How would I go about drawing this to an image file?
To draw an image I recommend you The CImg library. See more at question The easiest way to draw an image?
If you like to draw the traversed path you need a second stack or similar container where you record all the steps you have done. Perhaps you can draw a raster and arrows from one to an other cell for each step. If you draw arrows from north to south right shifted to them form south to north and those from east to west down shifted to them from west to east you won't have any overlapping.

A* pathfinding not taking shortest path

My A* pathfinding function always gets to its intended destination, but it almost always goes a bit out of the way. Here's an example:
[I made a nice image to show my issue, but apparently it won't post until my reputation reaches 10; sorry, I'm new. :P]
Essentially, it pulls left or up as much as possible without actually adding more tiles to the path. It sounds like an issue with calculating the gScore or possibly the part where a tile's parent can be reassigned based on neighboring tiles' gScores, but I just can't figure out where it's going wrong. I've combed over my code for weeks and browsed dozens of online posts, but I'm still stuck. Fyi, the compiler/debugger I have to use doesn't support breakpoints or step-through debugging, so I'm stuck with simple text output. Can anyone spot what I'm doing wrong?
Here's the primary function (Note: this is all in Angelscript. It's based on C++, but there are small differences):
int CARDINAL_COST = 10;
int DIAGONAL_COST = 14;
array<vector2> findPath(vector2 startPosition, vector2 endPosition)
{
//Translate the start and end positions into grid coordinates
startPosition = _level.getTileGridPosition(startPosition);
endPosition = _level.getTileGridPosition(endPosition);
//The path to be returned
array<vector2> path(0);
//Create the closed
array<vector2> closedSet(0);
//Create the open set. These are nodes to be considered.
array<vector2> openSet(0);
//Add the startPosition to the open set.
openSet.insertLast(startPosition);
//Create the cameFrom (path) array. Each entry hods that tile's parent tile.
array<array<vector2>> cameFrom;
cameFrom = array<array<vector2>>(_level.width(), array<vector2>(_level.height()));
//Create the gScore array. gScore is the cost to get from the start to the current tile.
array<array<int>> gScore;
gScore = array<array<int>>(_level.width(), array<int>(_level.height()));
//Set the start position score to 0
gScore[startPosition.x][startPosition.y] = 0;
//Create the fScore array. fScore is the gScore + heuristic cost.
array<array<int>> fScore;
fScore = array<array<int>>(_level.width(), array<int>(_level.height()));
//Set the start position score to the estimated (heuristic) cost.
//gScore for start is 0, so that's not included in the equation.
fScore[startPosition.x][startPosition.y] = getHeuristicCost(startPosition, endPosition);
//Required variables
bool searchComplete = false;
vector2 currentTile = startPosition;
int x = 0;
int y = 0;
string tileType = "";
vector2 nextTile(0,0);
vector2 neighborTile(0,0);
int lowestScore = 0;
int tempScore = 0;
int index = 0;
while(!searchComplete)
{
//Find the tile in the openSet with the lowest fScore.
lowestScore = fScore[openSet[0].x][openSet[0].y];
neighborTile = openSet[0];//May not actually be a "neighbor" in this case, just looking for the lowest fScore.
for(int i = 0; i < openSet.length(); i++)
{
if(fScore[neighborTile.x][neighborTile.y] < lowestScore || i == 0)
{
lowestScore = fScore[neighborTile.x][neighborTile.y];
nextTile.x = neighborTile.x;
nextTile.y = neighborTile.y;
}
}
//Drop the "nextTile" from the openSet and add it to the closedSet
index = openSet.find(nextTile);
openSet.removeAt(openSet.find(nextTile));
closedSet.insertLast(nextTile);
//Set the currentTile
currentTile = nextTile;
//Get the fScore for each neighboring tile
for(x = currentTile.x - 1; x <= currentTile.x + 1; x++)
{
for(y = currentTile.y - 1; y <= currentTile.y + 1; y++)
{
//Safety: make sure x and y aren't out of bounds
if(x < 0)
x = 0;
else if(x > _level.width())
x = _level.width();
if(y < 0)
y = 0;
else if (y > _level.height())
y = _level.height();
//Set this x,y pair to be the neighborTile
neighborTile.x = x;
neighborTile.y = y;
//Get the tile type
if(_level.tileArray()[neighborTile.x][neighborTile.y] != null)
tileType = _level.tileArray()[neighborTile.x][neighborTile.y].GetString("type");
else
tileType = "";
//Make sure we aren't looking at the current tile, the tile is not closed, and the tile is a floor or door.
if(neighborTile != currentTile && closedSet.find(neighborTile) == -1 && (tileType == "floor" || tileType == "door"))
{
//If the neighboring tile is already in the open set, check to see if the currentTile's gScore would be less if that tile was its parent.
//If it is, set the it as the currentTile's parent and reset the fScore and gScore for it.
if(openSet.find(neighborTile) != -1)
{
if(gScore[neighborTile.x][neighborTile.y] < gScore[cameFrom[currentTile.x][currentTile.y].x][cameFrom[currentTile.x][currentTile.y].y])
{
cameFrom[currentTile.x][currentTile.y] = neighborTile;
//If the tile is a diagonal move
if(neighborTile.x - currentTile.x != 0 && neighborTile.y - currentTile.y != 0)
gScore[currentTile.x][currentTile.y] = gScore[neighborTile.x][neighborTile.y] + DIAGONAL_COST;
else//If the tile is a cardinal (N,S,E,W) move
gScore[currentTile.x][currentTile.y] = gScore[neighborTile.x][neighborTile.y] + CARDINAL_COST;
fScore[currentTile.x][currentTile.y] = gScore[currentTile.x][currentTile.y] + getHeuristicCost(currentTile, endPosition);
}
}
else//Add this tile to the open set
{
openSet.insertLast(neighborTile);
//Record this tile's parent
cameFrom[neighborTile.x][neighborTile.y] = currentTile;
//If the tile is a diagonal move
if(neighborTile.x - currentTile.x != 0 && neighborTile.y - currentTile.y != 0)
gScore[neighborTile.x][neighborTile.y] = gScore[currentTile.x][currentTile.y] + DIAGONAL_COST;
else//If the tile is a cardinal (N,S,E,W) move
gScore[neighborTile.x][neighborTile.y] = gScore[currentTile.x][currentTile.y] + CARDINAL_COST;
//Get the fScore for this tile
fScore[neighborTile.x][neighborTile.y] = gScore[neighborTile.x][neighborTile.y] + getHeuristicCost(neighborTile, endPosition);
}
}
}
}
//Check to see if we have arrived at the endTile
if(currentTile == endPosition)
{
searchComplete = true;
path = reconstructPath(cameFrom, startPosition, endPosition);
}
else
{
//Check to see if the openSet is empty
if(openSet.length() == 0)
searchComplete = true;
}
}//while(!searchComplete)
return path;
}
My heuristic uses the Manhattan method:
int getHeuristicCost(vector2 startPosition, vector2 endPosition)
{
//Using Manhattan method:
int x = abs(startPosition.x - endPosition.x)*10;
int y = abs(startPosition.y - endPosition.y)*10;
return x+y;
}
And finally, here's my path reconstructing function:
array<vector2> reconstructPath(array<array<vector2>> &in cameFrom, vector2 &in startPosition, vector2 &in endPosition)
{
//Start by adding in the end position
array<vector2> totalPath(1);
vector2 currentTile = endPosition;
totalPath[0] = endPosition;
int x = endPosition.x;
int y = endPosition.y;
int angle = 0;
while(vector2(x, y) != startPosition)
{
currentTile = cameFrom[x][y];
totalPath.insertAt(0,currentTile);
x = currentTile.x;
y = currentTile.y;
}
return totalPath;
}
for(int i = 0; i < openSet.length(); i++)
{
if(fScore[neighborTile.x][neighborTile.y] < lowestScore || i == 0)
{
lowestScore = fScore[neighborTile.x][neighborTile.y];
nextTile.x = neighborTile.x;
nextTile.y = neighborTile.y;
}
}
This loop just looks at neighborTile over and over. Did you mean to go over the elements of openSet?

How can I draw a "line" in a 2-D array (simulacrum for a screen)

I'm working on a project that's going to print out to a bitmap(more specifically a RAW, but that's not important to the question), but I'm working in a 2-D array in-program.
I want to be able to draw a line from point (a,b) to point (x,y) for any arbitrary values of a,b,x, and y. I don't need anything fancy like anti-aliasing; at this point nearest-neighbor is fine. for the sake of example, let's assume I've got a 5x5 2d array, like so:
00,10,20,30,40
01,11,21,31,41
02,12,22,32,42
03,13,23,33,43
04,14,24,34,44
now, lets assume I want to draw a line between 04 and 42. I want a way of reliably coming up with something like this:
0,0,0,0,0
0,0,0,0,0
0,0,0,1,1
0,1,1,1,0
1,1,0,0,0
I'm sure there's someone thinking "guh, is this guy retarded? did he fail here?", but humor me, please!
I'm working in C++, but that should be secondary to the actual question.
Bresenham's line algorithm is what you need:
Illustration of the result of Bresenham's line algorithm.
Like Simucal said, Bresenham is the way to go. Here is a naive implementation.
Not perfect C code, and you have to do some magic if you want thickness on the line segments. Also, you should traverse along x, instead of y like I do here. It is more cache-friendly. If you want anti-aliasing, search for "Wu-lines". It's a clever trick to use the fraction from the positions as a gradient.
Tips for line thickness:
Calculate the normalized vector V(-y,x) from v1 - v0 if your vertices are in counter-clockwise order, or V(y,-x) if your vertices are in clockwise order. Then you have four points defined by: v0, v0 + V * linewidth, v1 and v1 + V * linewidth. Rasterize that quadrangle by interpolating along the edges. But if you already want to go that far, you would probably code a triangle rasterizer instead.
typedef struct Point
{
int x, y;
} Point;
typedef struct Color {
unsigned char r,g,b;
} Color;
#define RGB(x) (x->r << 16) | (x->g << 8) | (x->b)
int DrawLinestrip(int width, int height, unsigned int* buffer,
Color* color, Point* verts, int count)
{
int i, x,y,xbegin, xdelta, ydelta, xdiff, ydiff, accum, sign;
Point *p1, *p2;
if(!verts || count < 2)
return -1;
for(i=1; i<count; ++i){
if(verts[i].y > verts[i-1].y){ /* sort by y */
p1 = &verts[i-1];
p2 = &verts[i];
} else {
p1 = &verts[i];
p2 = &verts[i-1];
}
xdelta = p2->x - p1->x;
ydelta = p2->y - p1->y;
accum = 0;
sign = 0;
if(!xdelta && !ydelta)
continue;
else if(!xdelta && ydelta){ /* Special case: straight vertical line */
x = p1->x;
for(y=p1->y; y<(p1->y + ydelta); ++y){
buffer[x + y*width] = RGB(color);
}
}
else if(xdelta && !ydelta){ /* Special case: straight horisontal line */
y = p1->y;
xbegin = (p1->x < p2->x ? p1->x : p2->x);
for(x=xbegin; x<=xbegin+abs(xdelta); ++x){
buffer[x + y*width] = RGB(color);
}
}
else {
xdiff = (xdelta << 16) / ydelta;
ydiff = (ydelta << 16) / xdelta;
if( abs(xdiff) > abs(ydiff) ){ /* horizontal-major */
y = p1->y;
if(xdelta < 0){ /* traversing negative x */
for(x=p1->x; x >= p2->x; --x){
buffer[x + y*width] = RGB(color);
accum += abs(ydiff);
while(accum >= (1<<16)){
++y;
accum -= (1<<16);
}
}
} else { /* traversing positive x */
for(x=p1->x; x <= p2->x; ++x){
buffer[x + y*width] = RGB(color);
accum += abs(ydiff);
while(accum >= (1<<16)){
++y;
accum -= (1<<16);
}
}
}
} else if( abs(ydiff) > abs(xdiff) ){ /* vertical major */
sign = (xdelta > 0 ? 1 : -1);
x = p1->x;
for(y=p1->y; y <= p2->y; ++y){
buffer[x + y*width] = RGB(color);
accum += abs(xdiff);
while(accum >= (1<<16)){
x += sign;
accum -= (1<<16);
}
}
} else if( abs(ydiff) == abs(xdiff) ){ /* 45 degrees */
sign = (xdelta > 0 ? 1 : -1);
x = p1->x;
for(y=p1->y; y <= p2->y; ++y){
buffer[x + y*width] = RGB(color);
x+= sign;
}
}
}
}
return 0;
}