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

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;
}

Related

Fill Matrix in Spiral Form from center

I recently finished making an algorithm for a project I'm working on.
Briefly, a part of my project needs to fill a matrix, the requirements of how to do it are these:
- Fill the matrix in form of spiral, from the center.
- The size of the matrix must be dynamic, so the spiral can be large or small.
- Every two times a cell of the matrix is filled, //DO STUFF must be executed.
In the end, the code that I made works, it was my best effort and I am not able to optimize it more, it bothers me a bit having had to use so many ifs, and I was wondering if someone could take a look at my code to see if it is possible to optimize it further or some constructive comment (it works well, but it would be great if it was faster, since this algorithm will be executed several times in my project). Also so that other people can use it!
#include <stdio.h>
typedef unsigned short u16_t;
const u16_t size = 7; //<-- CHANGE HERE!!! just odd numbers and bigger than 3
const u16_t maxTimes = 2;
u16_t array_cont[size][size] = { 0 };
u16_t counter = 3, curr = 0;
u16_t endColumn = (size - 1) / 2, endRow = endColumn;
u16_t startColumn = endColumn + 1, startRow = endColumn + 1;
u16_t posLoop = 2, buffer = startColumn, i = 0;
void fillArray() {
if (curr < maxTimes) {
if (posLoop == 0) { //Top
for (i = buffer; i <= startColumn && curr < maxTimes; i++, curr++)
array_cont[endRow][i] = counter++;
if (curr == maxTimes) {
if (i <= startColumn) {
buffer = i;
} else {
buffer = endRow;
startColumn++;
posLoop++;
}
} else {
buffer = endRow;
startColumn++;
posLoop++;
fillArray();
}
} else if (posLoop == 1) { //Right
for (i = buffer; i <= startRow && curr < maxTimes; i++, curr++)
array_cont[i][startColumn] = counter++;
if (curr == maxTimes) {
if (i <= startRow) {
buffer = i;
} else {
buffer = startColumn;
startRow++;
posLoop++;
}
} else {
buffer = startColumn;
startRow++;
posLoop++;
fillArray();
}
} else if (posLoop == 2) { //Bottom
for (i = buffer; i >= endColumn && curr < maxTimes; i--, curr++)
array_cont[startRow][i] = counter++;
if (curr == maxTimes) {
if (i >= endColumn) {
buffer = i;
} else {
buffer = startRow;
endColumn--;
posLoop++;
}
} else {
buffer = startRow;
endColumn--;
posLoop++;
fillArray();
}
} else if (posLoop == 3) { //Left
for (i = buffer; i >= endRow && curr < maxTimes; i--, curr++)
array_cont[i][endColumn] = counter++;
if (curr == maxTimes) {
if (i >= endRow) {
buffer = i;
} else {
buffer = endColumn;
endRow--;
posLoop = 0;
}
} else {
buffer = endColumn;
endRow--;
posLoop = 0;
fillArray();
}
}
}
}
int main(void) {
array_cont[endColumn][endColumn] = 1;
array_cont[endColumn][endColumn + 1] = 2;
//DO STUFF
u16_t max = ((size * size) - 1) / maxTimes;
for (u16_t j = 0; j < max; j++) {
fillArray();
curr = 0;
//DO STUFF
}
//Demostration
for (u16_t x = 0; x < size; x++) {
for (u16_t y = 0; y < size; y++)
printf("%-4d ", array_cont[x][y]);
printf("\n");
}
return 0;
}
Notice that the numbers along the diagonal (1, 9, 25, 49) are the squares of the odd numbers. That's an important clue, since it suggests that the 1 in the center of the matrix should be treated as the end of a spiral.
From the end of each spiral, the x,y coordinates should be adjusted up and to the right by 1. Then the next layer of the spiral can be constructed by moving down, left, up, and right by the same amount.
For example, starting from the position of the 1, move up and to the right (to the position of the 9), and then form a loop with the following procedure:
move down, and place the 2
move down, and place the 3
move left, and place the 4
move left, and place the 5
etc.
Thus the code looks something like this:
int size = 7;
int matrix[size][size];
int dy[] = { 1, 0, -1, 0 };
int dx[] = { 0, -1, 0, 1 };
int directionCount = 4;
int ringCount = (size - 1) / 2;
int y = ringCount;
int x = ringCount;
int repeatCount = 0;
int value = 1;
matrix[y][x] = value++;
for (int ring = 0; ring < ringCount; ring++)
{
y--;
x++;
repeatCount += 2;
for (int direction = 0; direction < directionCount; direction++)
for (int repeat = 0; repeat < repeatCount; repeat++)
{
y += dy[direction];
x += dx[direction];
matrix[y][x] = value++;
}
}
I saw already many approaches for doing a spiral. All a basically drawing it, by following a path.
BUT, you can also come up with an analytical calculation formula for a spiral.
So, no recursion or iterative solution by following a path or such. We can directly calculate the indices in the matrix, if we have the running number.
I will start with the spiral in mathematical positive direction (counter clockwise) in a cartesian coordinate system. We will concentrate on X and Y coordinates.
I made a short Excel and derived some formulas from that. Here is a short picture:
From the requirements we know that the matrix will be quadratic. That makes things easier. A little bit trickier is, to get the matrix data symmetrical. But with some simple formulas, derived from the prictures, this is not really a problem.
And then we can calculate x and y coordinates with some simple statements. See the below example program with long variable names for better understanding. The code is made using some step by step approach to illustrate the implementation. Of course it can be made more compact easily. Anyway. Let's have a look.
#include <iostream>
#include <cmath>
#include <iomanip>
int main() {
// Show some example values
for (long step{}; step < 81; ++step) {
// Calculate result
const long roundedSquareRoot = std::lround(std::sqrt(step));
const long roundedSquare = roundedSquareRoot * roundedSquareRoot;
const long distance = std::abs(roundedSquare - step) - roundedSquareRoot;
const long rsrIsOdd = (roundedSquareRoot % 2);
const long x = (distance + roundedSquare - step - rsrIsOdd) / (rsrIsOdd ? -2 : 2);
const long y = (-distance + roundedSquare - step - rsrIsOdd) / (rsrIsOdd ? -2 : 2);
// Show ouput
std::cout << "Step:" << std::setw(4) << step << std::setw(3) << x << ' ' << std::setw(3) << y << '\n';
}
}
So, you see that we really have an analytical solution. Given any number we can calculate the x and y coordinate using a formula. Cool.
Getting indices in a matrix is just adding some offset.
With that gained know how, we can now easily calculate the complete matrix. And, since there is no runtime activity needed at all, we can let the compiler do the work. We will simply use constexpr functions for everything.
Then the compiler will create this matrix at compile time. At runtime, nothing will happen.
Please see a very compact solution:
#include <iostream>
#include <iomanip>
#include <array>
constexpr size_t MatrixSize = 15u;
using MyType = long;
static_assert(MatrixSize > 0 && MatrixSize%2, "Matrix size must be odd and > 0");
constexpr MyType MatrixHalf = MatrixSize / 2;
using Matrix = std::array<std::array<MyType, MatrixSize>, MatrixSize >;
// Some constexpr simple mathematical functions ------------------------------------------------------------------------------
// No need for <cmath>
constexpr MyType myAbs(MyType v) { return v < 0 ? -v : v; }
constexpr double mySqrtRecursive(double x, double c, double p) {return c == p? c: mySqrtRecursive(x, 0.5 * (c + x / c), c); }
constexpr MyType mySqrt(MyType x) {return (MyType)(mySqrtRecursive((double)x,(double)x,0.0)+0.5); }
// Main constexpr function will fill the matrix with a spiral pattern during compile time -------------------------------------
constexpr Matrix fillMatrix() {
Matrix matrix{};
for (int i{}; i < (MatrixSize * MatrixSize); ++i) {
const MyType rsr{ mySqrt(i) }, rs{ rsr * rsr }, d{ myAbs(rs - i) - rsr }, o{ rsr % 2 };
const size_t col{ (size_t)(MatrixHalf +((d + rs - i - o) / (o ? -2 : 2)))};
const size_t row{ (size_t)(MatrixHalf -((-d + rs - i - o) / (o ? -2 : 2)))};
matrix[row][col] = i;
}
return matrix;
}
// This is a compile time constant!
constexpr Matrix matrix = fillMatrix();
// All the above has been done during compile time! -----------------------------------------
int main() {
// Nothing to do. All has beend done at compile time already!
// The matrix is already filled with a spiral pattern
// Just output
for (const auto& row : matrix) {
for (const auto& col : row) std::cout << std::setw(5) << col << ' '; std::cout << '\n';
}
}
Different coordinate systems or other spiral direction can be adapted easily.
Happy coding.

Large height map interpolation

I have a vector<vector<double>> heightmap that is dynamically loaded from a CSV file of GPS data to be around 4000x4000. However, only provides 140,799 points.
It produces a greyscale map as shown bellow:
I wish to interpolate the heights between all the points to generate a height map of the area.
The below code finds all known points will look in a 10m radius of the point to find any other known points. If another point is found then it will linearly interpolate between the 2 points. Interpolated points are defined by - height and unset values are defined as -1337.
This approach is incredibly slow I am sure there are better ways to achieve this.
bool run_interp = true;
bool interp_interp = false;
int counter = 0;
while (run_interp)
{
for (auto x = 0; x < map.size(); x++)
{
for (auto y = 0; y < map.at(x).size(); y++)
{
const auto height = map.at(x).at(y);
if (height == -1337) continue;
if (!interp_interp && height < 0) continue;
//Look in a 10m radius of a known value to see if there
//Is another known value to linearly interp between
//Set height to a negative if it has been interped
const int radius = (1 / resolution) * 10;
for (auto rxi = 0; rxi < radius * 2; rxi++)
{
//since we want to expand outwards
const int rx = x + ((rxi % 2 == 0) ? rxi / 2 : -(rxi - 1) / 2);
if (rx < 0 || rx >= map.size()) continue;
for (auto ryi = 0; ryi < radius * 2; ryi++)
{
const int ry = y + ((rxi % 2 == 0) ? rxi / 2 : -(rxi - 1) / 2);
if (ry < 0 || ry >= map.at(x).size()) continue;
const auto new_height = map.at(rx).at(ry);
if (new_height == -1337) continue;
//First go around we don't want to interp
//Interps
if (!interp_interp && new_height < 0) continue;
//We have found a known point within 10m
const auto delta = new_height - height;
const auto distance = sqrt((rx- x) * (rx - x)
+ (ry - y) * (ry - y));
const auto angle = atan2(ry - y, rx - x);
const auto ratio = delta / distance;
//Backtrack from found point until we get to know point
for (auto radi = 0; radi < distance; radi++)
{
const auto new_x = static_cast<int>(x + radi * cos(angle));
const auto new_y = static_cast<int>(y + radi * sin(angle));
if (new_x < 0 || new_x >= map.size()) continue;
if (new_y < 0 || new_y >= map.at(new_x).size()) continue;
const auto interp_height = map.at(new_x).at(new_y);
//If it is a known height don't interp it
if (interp_height > 0)
continue;
counter++;
set_height(new_x, new_y, -interp_height);
}
}
}
}
std::cout << x << " " << counter << std::endl;;
}
if (interp_interp)
run_interp = false;
interp_interp = true;
}
set_height(const int x, const int y, const double height)
{
//First time data being set
if (map.at(x).at(y) == -1337)
{
map.at(x).at(y) = height;
}
else // Data set already so average it
{
//While this isn't technically correct and weights
//Later data significantly more favourablily
//It should be fine
//TODO: fix it.
map.at(x).at(y) += height;
map.at(x).at(y) /= 2;
}
}
If you put the points into a kd-tree, it will be much faster to find the closest point (O(nlogn)).
I'm not sure that will solve all your issues, but it is a start.

Is there a better way of handling bitmap data depending on compression + bitcount?

I've been studying the Bitmap (.bmp) file format for a while now, trying to figure out how to structure the read function. The general idea is to support all the common compression types, be safe and still quick to load into a (custom) general in-memory image for further processing in a machine vision library.
With performance in mind, I figured it would be best to write an as efficient loop as possible for each bitcount + compression type constellation. Here's a snippet of how that looks for BI_RGB:
if (bmpi.biCompression == BI_RGB){
if (bmpi.biBitCount >= 24){
// Here, every byte is each channel's value (assuming order is GBR(A))
for (y = start_y; y != end_y; y += direction){
row = y * stride;
for (x = 0; x < stride; x += bpp){
index = row + x;
*(res->data++) = read_data[index + 2]; // R
*(res->data++) = read_data[index + 1]; // G
*(res->data++) = read_data[index]; // B
}
}
}
else if (bmpi.biBitCount == 16){
// Without a bitfield mask, it's assumed 555 (instead of the common 565) with the last bit discarded
for (y = start_y; y != end_y; y += direction){
row = y * stride;
for (x = 0; x < stride; x += bpp){
val = (WORD)&read_data[row + x];
*(res->data++) = map16to255[((val >> 10) & 31)]; // R
*(res->data++) = map16to255[((val >> 5) & 31)]; // G
*(res->data++) = map16to255[(val & 31)]; // B
}
}
}
else if (bmpi.biBitCount == 8){
for (y = start_y; y != end_y; y += direction){
row = y * stride;
for (x = 0; x < stride; x += bpp){
memcpy(res->data, &colors[read_data[row + x]], 3); // Copy RGB from color table
res->data += 3; // Increment
}
}
}
else if (bmpi.biBitCount == 4){
// 2 pixels per byte
for (y = start_y; y != end_y; y += direction){
row = y * stride;
for (x = 0; x < stride; x += bpp){
index = row + x;
memcpy(res->data, &colors[(read_data[index] & 15)], 3);
res->data += 3;
memcpy(res->data, &colors[(read_data[index] >> 4)], 3);
res->data += 3;
}
}
}
else{
// 8 pixels per byte
for (y = start_y; y != end_y; y += direction){
row = y * stride;
for (x = 0; x < stride; x += bpp){
index = row + x;
for (i = 0; i < 8; i++){
memcpy(res->data, &colors[((read_data[index] >> i) & 1)], 3);
res->data += 3;
}
}
}
}
}
It doesn't look pretty, to be honest.
Then I thought about assigning a function pointer and just write one loop but worry it would make it slower somehow. At least it would add a layer of obfuscation (of what is going on). I'd rather have bloated code if it clearly shows what is happening and what the intention is though
typedef void(*pxl_proc) (int index, BYTEP read_data, BYTEP in_data, PRGBQUAD colors);
//...
pxl_proc func = _24_rgb;
for (y = start_y; y != end_y; y += direction){
row = y * stride;
for (x = 0; x < stride; x += bpp){
func(row + x, read_data, res->data, colors);
}
}
I get the sense that this approach is unnecessarily specific and that there's a general way to process the bitmap while still being as quick, or quicker.
How should I be doing this? Is this "good enough" for a professional library?

C++ console TicTacToe: Checking Win Conditions

The game board is stored as a 2D char array. The Player moves his cursor around the board using the numpad, and chooses with the enter key- current position of the cursor is stored in two ints.
After each move, the board is evaluated for a win using the below method.
void checkwin()
{
//look along lines from current position
int x = cursorPosX;
int y = cursorPosY;
int c = playerTurn ? 1 : 2; //which mark to look for
for (int xAxis = 0; xAxis <= 2; xAxis++) //look along x axis
{
x = WrapValue(0, sizeof(squares[0]), x + 1);
if (CheckPos(x, y) != c) //if we don't find the same mark, must not be a horizontal line, otherwise, break out.
{
x = cursorPosX; //reset x
for (int yAxis = 0; yAxis <= 2; yAxis++) //look along y axis
{
y = WrapValue(0, sizeof(squares[0]), y + 1);
if (CheckPos(x, y) != c)
{
y = cursorPosY;
//look for diagonal
for (int i = 0; i <= 2; i++ )
{
x = WrapValue(0, sizeof(squares[0]), x + 1);
y = WrapValue(0, sizeof(squares[0]), y + 1);
if (CheckPos(x, y) != c)
{
//failed everything, return
winConditions = -1;
return;
}
}
break;
}
}
break;
}
}
//if we make it out of the loops, we have a winner.
winConditions = playerTurn ? 0 : 1;
}
I get wrong results- returning a draw or win when not appropriate. I'm almost certain x and y get wrong values at some point and start checking the wrong spots.
Visual Studio stops updating a watch on x and y after going into the yAxis loop- I'm not sure why, but it prevents me from keeping track of those values. Am I breaking a rule about scoping somewhere? This is the only place I use x and y as variable names.
Relevant wrap method below. My aim was to always be able to check the other 2 spaces by adding, no matter where I was on the board
int WrapValue(int min, int max, int value)
{
auto range = max - min;
while (value >= max)
{
value -= range;
}
while (value < min)
{
value += range;
}
return value;
}
I'd appreciate a trained eye to tell me what I did wrong here. Thanks so much for your time.
Nesting for loops was a terrible idea. I solved the problem by refactoring the code into multiple separate loops that each do 1 thing, rather than fall through each other into deeper levels of hell.
for (int xAxis = 0; xAxis <= 2; xAxis++) //look along x axis
{
x = WrapValue(0, sizeof(squares[0]), x + 1);
if (CheckPos(x, y) != c) //if we don't find the same mark, must not be a horizontal line, otherwise, break out.
{
x = cursorPosX; //reset x
break;
}
else if (xAxis == 2)
{
winConditions = playerTurn ? 0 : 1;
return;
}
}
for (int yAxis = 0; yAxis <= 2; yAxis++) //look along y axis
{
y = WrapValue(0, sizeof(squares[0]), y + 1);
if (CheckPos(x, y) != c)
{
y = cursorPosY;
break;
}
else if (yAxis == 2)
{
winConditions = playerTurn ? 0 : 1;
return;
}
}
...ect
This violates DRY, but it does work the way it's supposed to, I'm sure I can simplify it later.
While I'm not entirely sure why the previous way didn't work, I do realize that it was just bad design to start with.

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

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.