Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: [SOLVED] need a better collision idea  (Read 8258 times)

0 Members and 1 Guest are viewing this topic.

Stauricus

  • Sr. Member
  • ****
  • Posts: 369
    • View Profile
    • A Mafia Graphic Novel
    • Email
[SOLVED] need a better collision idea
« on: July 11, 2020, 04:54:41 pm »
hello!
I'm creating this city like game, but having some trouble with collision between the cursor and the building tiles.
my first idea (which is working now) was to detect collision with only the ground area of each building. but its extremely counter intuitive. video explaining:


by 7 seconds I move the cursor over a building, but the selected tile is the ground behind it.

I need some insight on how to make a better collision detection, considering that buildings have different heights (and street tiles have almost no height at all).
observation: the tiles are made of sf::Quads (shaped as parallelograms/diamonds, NOT rectangles) in a sf::VertexArray.

original tile image:


tiles boundings before rotating the view (notice that most buildings won't use all the tile size).


I don't know if it is of any help, but the building tiles before rotating the view:


and after rotating:


grid lines are for reference.
any idea is apreciated. thanks in advance!  ;D
« Last Edit: July 25, 2020, 07:09:06 pm by Stauricus »
Visit my game site (and hopefully help funding it? )
Website | IndieDB

Paul

  • Jr. Member
  • **
  • Posts: 78
    • View Profile
Re: need a better collision idea
« Reply #1 on: July 11, 2020, 07:45:49 pm »
This is bit tricky in OpenGL, all graphics is in VRAM.

I'm not sure for which purposes you're rotating view, but one way is bitmask checking.
1) Generate bitmasks (array of bits) for your building images (*.png, *.bmp) where transparent pixels/colors will be 0
2) Check mouse cursor vs sprites on screen (point vs rectangle). If you get more than 1, select one which is closest to the player on screen (by y position for example)
3) Check whether the cursor collides with bitmask for given sprite

Overall, this is not entirely practical, generating bitmasks on the start of progam can be slow if you have a lot of sprites or sprite atlases. In case they are pre-generated you still must deal with extra data, it's too complicated.

Better is something like render sprites with colors into some buffer (where unique color means object ID), then just check color under cursor and return ID (https://www.kamremake.com/devblog/unit-picking/). I haven't impemented this yet. Maybe shaders would help.
« Last Edit: July 11, 2020, 07:50:19 pm by Paul »

Stauricus

  • Sr. Member
  • ****
  • Posts: 369
    • View Profile
    • A Mafia Graphic Novel
    • Email
Re: need a better collision idea
« Reply #2 on: July 14, 2020, 05:49:25 pm »
hi! thanks for your response
I was trying to come up with some code based on what you said before replying again, but this may take some time. so, i just wanted to thank for the help. i'm going for the second approach. when i have somethign new, i'll post here.  ;D


EDIT: well, i'll need help for that. i have never writen a shader before, nor really used one with SFML.

so, i'll be using this code based on the tilemap tutorial:
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>

int main(){
    sf::RenderWindow window(sf::VideoMode(512, 256), "Tilemap");
    sf::Vector2u tile_size(32, 32);
    sf::Vector2u map_size(32, 16);

    sf::Texture tileset;
    tileset.loadFromFile("terrain-6.png");

    sf::VertexArray vertices;
    vertices.setPrimitiveType(sf::Quads);
    vertices.resize(map_size.x*map_size.y*4);

    const int level[] ={
        0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0,
        1, 1, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3,
        0, 1, 0, 0, 2, 0, 3, 3, 3, 0, 1, 1, 1, 0, 0, 0,
        0, 1, 1, 0, 3, 3, 3, 0, 0, 0, 1, 1, 1, 2, 0, 0,
        0, 0, 1, 0, 3, 0, 2, 2, 0, 0, 1, 1, 1, 1, 2, 0,
        2, 0, 1, 0, 3, 0, 2, 2, 2, 0, 1, 1, 1, 1, 1, 1,
        0, 0, 1, 0, 3, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1,
    };

    for (unsigned int i = 0; i < map_size.x; ++i){
        for (unsigned int j = 0; j < map_size.y; ++j){
            int tileNumber = level[i+j*map_size.x];
            int tu = tileNumber % (tileset.getSize().x / tile_size.x);
            int tv = tileNumber / (tileset.getSize().x / tile_size.x);
            sf::Vertex* quad = &vertices[(i+j*map_size.x)*4];

            quad[0].position = sf::Vector2f(i*tile_size.x, (j-1)*tile_size.y);
            quad[1].position = sf::Vector2f((i+1)*tile_size.x, (j-1)*tile_size.y);
            quad[2].position = sf::Vector2f((i+1)*tile_size.x, (j+1)*tile_size.y);
            quad[3].position = sf::Vector2f(i*tile_size.x, (j+1)*tile_size.y);

            quad[0].texCoords = sf::Vector2f(tu*tile_size.x, tv*tile_size.y);
            quad[1].texCoords = sf::Vector2f((tu+1)*tile_size.x, tv*tile_size.y);
            quad[2].texCoords = sf::Vector2f((tu+1)*tile_size.x, (tv+1)*tile_size.y);
            quad[3].texCoords = sf::Vector2f(tu*tile_size.x, (tv+1)*tile_size.y);
        }
    }

    sf::Shader shader;
    shader.loadFromFile("shader.frag", sf::Shader::Fragment);
    shader.setUniform("mouse_position", window.mapPixelToCoords(sf::Mouse::getPosition(window)));
    shader.setUniform("tileset", tileset);

    while (window.isOpen()){
        sf::Event event;
        while (window.pollEvent(event)){
            if(event.type == sf::Event::Closed)
                window.close();
        }
        window.clear();
        window.draw(vertices, &tileset);
        window.display();
    }
    return 0;
}


and this not-yet shader "shader.frag" file
#version 330

uniform vec2 mouse_position;
uniform sampler2D tileset;

void main(){

}
 



what i think should be the next steps:
1- identify the current visible tiles on screen
2- for each one of these, get from the tileset the corresponding tile convert the non-transparent pixels to an unique color, respecting the draw order (left to right, up to down)
3- based on the mouse position, get the current color and convert it to a final tile id.

is it possible to return a number from a glsl shader back to the sfml program? in this case, the final tile id?
Visit my game site (and hopefully help funding it? )
Website | IndieDB

Paul

  • Jr. Member
  • **
  • Posts: 78
    • View Profile
Re: need a better collision idea
« Reply #3 on: July 14, 2020, 10:24:51 pm »
And that's where I ended up :) It seems like basic function to get pixel color from screen or texture, but whole graphics accelerated concept doesn't think so at all, you need to know API.

Shader approach is for example described here: https://stackoverflow.com/questions/53757376/how-to-retrieve-z-depth-and-color-of-a-rendered-pixel

Stauricus

  • Sr. Member
  • ****
  • Posts: 369
    • View Profile
    • A Mafia Graphic Novel
    • Email
Re: need a better collision idea
« Reply #4 on: July 15, 2020, 03:40:19 pm »
yeah, I think that its just not possible at all to do it in a simple way using vertexarrays, since each tile should take one different color.
maybe for this color buffer a sequence of sprites rendered to a RenderTexture could work, since they would be rendered only in and around the screen (you can't click something you can't see). but still theres a problem with recognizing the color at given position. copying the render texture to a sf::Image every loop doesn't seem practical  ???
Visit my game site (and hopefully help funding it? )
Website | IndieDB

Paul

  • Jr. Member
  • **
  • Posts: 78
    • View Profile
Re: need a better collision idea
« Reply #5 on: July 15, 2020, 10:57:35 pm »
In case of drawing into RenderTexture you can draw only objects near cursor or just buffer with 1/4 of screen size. So theoretically buffer can be very small, I'm not sure if it reduce GPU/CPU bottleneck.

There is still glReadPixels() mentioned in KaM arcticle, you must bind texture to frame buffer otherwise it can't be used. Again it's plain OpenGL. (https://www.khronos.org/opengl/wiki/Pixel_Transfer)


Stauricus

  • Sr. Member
  • ****
  • Posts: 369
    • View Profile
    • A Mafia Graphic Novel
    • Email
Re: need a better collision idea
« Reply #6 on: July 21, 2020, 05:55:28 pm »
updating the journey:

shader to colorize:
uniform sampler2D texture;
uniform vec4 color_id;

void main(){
    vec4 pixel = texture2D(texture, gl_TexCoord[0].xy);
    if(pixel.a != 0.0){
        gl_FragColor = color_id;
    }
    else{
        discard;
    }
}
 
aparently gl_FragColor and gl_TexCoord are deprecated in GLSL, but I wasn't able to do it using "out vec4" and alikes

an aplication that can draw it directly to the screen, so we can see it is working:
#include <string>
#include <fstream>
#include <iostream>

#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>


int main(){
    sf::RenderWindow window(sf::VideoMode(512, 256), "Tests");
    sf::Vector2u tile_size(32, 32);
    sf::Vector2u map_size(16, 8);

    sf::Texture tileset;
    tileset.loadFromFile("terrain-6.png");

    sf::VertexArray vertices;
    vertices.setPrimitiveType(sf::Quads);
    vertices.resize(map_size.x*map_size.y*4);

    const int level[] ={
        0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0,
        1, 1, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3,
        0, 1, 0, 0, 2, 0, 3, 3, 3, 0, 1, 1, 1, 0, 0, 0,
        0, 1, 1, 0, 3, 3, 3, 0, 0, 0, 1, 1, 1, 2, 0, 0,
        0, 0, 1, 0, 3, 0, 2, 2, 0, 0, 1, 1, 1, 1, 2, 0,
        2, 0, 1, 0, 3, 0, 2, 2, 2, 0, 1, 1, 1, 1, 1, 1,
        0, 0, 1, 0, 3, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1,
    };


    for (unsigned int i = 0; i < map_size.x; ++i){
        for (unsigned int j = 0; j < map_size.y; ++j){
            int tileNumber = level[i+j*map_size.x];
            int tu = tileNumber % (tileset.getSize().x / tile_size.x);
            int tv = tileNumber / (tileset.getSize().x / tile_size.x);
            sf::Vertex* quad = &vertices[(i+j*map_size.x)*4];

            quad[0].position = sf::Vector2f(i*tile_size.x, (j)*tile_size.y);
            quad[1].position = sf::Vector2f((i+1)*tile_size.x, (j)*tile_size.y);
            quad[2].position = sf::Vector2f((i+1)*tile_size.x, (j+1)*tile_size.y);
            quad[3].position = sf::Vector2f(i*tile_size.x, (j+1)*tile_size.y);

            quad[0].texCoords = sf::Vector2f(tu*tile_size.x, tv*tile_size.y);
            quad[1].texCoords = sf::Vector2f((tu+1)*tile_size.x, tv*tile_size.y);
            quad[2].texCoords = sf::Vector2f((tu+1)*tile_size.x, (tv+1)*tile_size.y);
            quad[3].texCoords = sf::Vector2f(tu*tile_size.x, (tv+1)*tile_size.y);
        }
    }

    sf::Shader shader;
    shader.loadFromFile("shaders/test.frag", sf::Shader::Fragment);
    shader.setUniform("texture", tileset);
    shader.setUniform("color_id", sf::Glsl::Vec4(255, 0, 0, 255));

    sf::RenderStates rs;
    rs.texture = &tileset;
    rs.shader = &shader;

    while (window.isOpen()){
        sf::Event event;
        while (window.pollEvent(event)){
            if(event.type == sf::Event::Closed)
                window.close();
        }

        window.clear(sf::Color::White);
        window.draw(vertices, rs);
        window.display();
        sf::sleep(sf::milliseconds(10));
    }
    return 0;
}

if we draw it without the shader:


and after enabling it:



next step would be to draw it to a buffer. problem is, as I said, the shader is applied to the whole vertexArray at once, so every tile would have the same id. so my idea was to draw sprites around the mouse (as you said, like 1/4 of the screen), each one with the shader applied with a different color value. so i tried this (my idea would be to iterate a single sprite  sized as a tile trough the screen, drawing it to a RenderTexture):

#include <string>
#include <fstream>
#include <iostream>

#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>


int main(){
    sf::RenderWindow window(sf::VideoMode(512, 256), "Tests");
    sf::Vector2u tile_size(32, 32);
    sf::Vector2u map_size(16, 8);

    sf::Texture tileset;
    tileset.loadFromFile("terrain-6.png");

    sf::VertexArray vertices;
    vertices.setPrimitiveType(sf::Quads);
    vertices.resize(map_size.x*map_size.y*4);

    const int level[] ={
        0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0,
        1, 1, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3,
        0, 1, 0, 0, 2, 0, 3, 3, 3, 0, 1, 1, 1, 0, 0, 0,
        0, 1, 1, 0, 3, 3, 3, 0, 0, 0, 1, 1, 1, 2, 0, 0,
        0, 0, 1, 0, 3, 0, 2, 2, 0, 0, 1, 1, 1, 1, 2, 0,
        2, 0, 1, 0, 3, 0, 2, 2, 2, 0, 1, 1, 1, 1, 1, 1,
        0, 0, 1, 0, 3, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1,
    };


    for (unsigned int i = 0; i < map_size.x; ++i){
        for (unsigned int j = 0; j < map_size.y; ++j){
            int tileNumber = level[i+j*map_size.x];
            int tu = tileNumber % (tileset.getSize().x / tile_size.x);
            int tv = tileNumber / (tileset.getSize().x / tile_size.x);
            sf::Vertex* quad = &vertices[(i+j*map_size.x)*4];

            quad[0].position = sf::Vector2f(i*tile_size.x, (j)*tile_size.y);
            quad[1].position = sf::Vector2f((i+1)*tile_size.x, (j)*tile_size.y);
            quad[2].position = sf::Vector2f((i+1)*tile_size.x, (j+1)*tile_size.y);
            quad[3].position = sf::Vector2f(i*tile_size.x, (j+1)*tile_size.y);

            quad[0].texCoords = sf::Vector2f(tu*tile_size.x, tv*tile_size.y);
            quad[1].texCoords = sf::Vector2f((tu+1)*tile_size.x, tv*tile_size.y);
            quad[2].texCoords = sf::Vector2f((tu+1)*tile_size.x, (tv+1)*tile_size.y);
            quad[3].texCoords = sf::Vector2f(tu*tile_size.x, (tv+1)*tile_size.y);
        }
    }

    sf::Shader shader;
    shader.loadFromFile("shaders/test.frag", sf::Shader::Fragment);
    shader.setUniform("texture", tileset);

    sf::RenderTexture buffer;
    buffer.create(window.getSize().x, window.getSize().y);
    sf::Texture buffer_tex;
    sf::Sprite buffer_spr(buffer_tex);

    sf::RenderStates rs;
    rs.texture = &tileset;
    rs.shader = &shader;

    while (window.isOpen()){
        sf::Event event;
        while (window.pollEvent(event)){
            if(event.type == sf::Event::Closed)
                window.close();
        }
        buffer.clear();
        for (size_t y=0; y<map_size.y; y++){
            for (size_t x=0; x<map_size.x; x++){
                shader.setUniform("color_id", sf::Glsl::Vec4(x, y, 0, 255));

                int tileNumber = level[x+y*map_size.x];
                int tu = tileNumber % (tileset.getSize().x / tile_size.x);
                int tv = tileNumber / (tileset.getSize().x / tile_size.x);

                buffer_spr.setTextureRect(sf::IntRect(tu*tile_size.x, tv*tile_size.y, tile_size.x, tile_size.y));
                buffer_spr.setPosition(x*tile_size.x, y*tile_size.y);
                buffer.draw(buffer_spr, rs);
            }
        }
        buffer.display();
        window.clear(sf::Color::White);
        window.draw(vertices, &tileset);
        sf::Sprite test_sprite(buffer.getTexture());
        window.draw(test_sprite);
        window.display();
        sf::sleep(sf::milliseconds(10));
    }
    return 0;
}

the test_sprite is just for testing purposes, obviously. I set it to use the render texture, so we could see what is being draw. but the results are weird:



maybe i'm not calculating the sprite and its texture positions correctly?  :P
or maybe a vertex shader would be better?


EDIT: watch out, my brain is probably melting after 10 days trying to solve this. i simply forgot to load the texture in the buffer_tex. and by the way, that was nonsense; I just had to use the original tileset texture in the buffer_spr sprite. fixing it:

#include <string>
#include <fstream>
#include <iostream>

#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>


int main(){
    sf::RenderWindow window(sf::VideoMode(512, 256), "Tests");
    sf::Vector2u tile_size(32, 32);
    sf::Vector2u map_size(16, 8);

    sf::Texture tileset;
    tileset.loadFromFile("terrain-6.png");

    sf::VertexArray vertices;
    vertices.setPrimitiveType(sf::Quads);
    vertices.resize(map_size.x*map_size.y*4);

    const int level[] ={
        0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0,
        1, 1, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3,
        0, 1, 0, 0, 2, 0, 3, 3, 3, 0, 1, 1, 1, 0, 0, 0,
        0, 1, 1, 0, 3, 3, 3, 0, 0, 0, 1, 1, 1, 2, 0, 0,
        0, 0, 1, 0, 3, 0, 2, 2, 0, 0, 1, 1, 1, 1, 2, 0,
        2, 0, 1, 0, 3, 0, 2, 2, 2, 0, 1, 1, 1, 1, 1, 1,
        0, 0, 1, 0, 3, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1,
    };


    for (unsigned int i = 0; i < map_size.x; ++i){
        for (unsigned int j = 0; j < map_size.y; ++j){
            int tileNumber = level[i+j*map_size.x];
            int tu = tileNumber % (tileset.getSize().x / tile_size.x);
            int tv = tileNumber / (tileset.getSize().x / tile_size.x);
            sf::Vertex* quad = &vertices[(i+j*map_size.x)*4];

            quad[0].position = sf::Vector2f(i*tile_size.x, (j)*tile_size.y);
            quad[1].position = sf::Vector2f((i+1)*tile_size.x, (j)*tile_size.y);
            quad[2].position = sf::Vector2f((i+1)*tile_size.x, (j+1)*tile_size.y);
            quad[3].position = sf::Vector2f(i*tile_size.x, (j+1)*tile_size.y);

            quad[0].texCoords = sf::Vector2f(tu*tile_size.x, tv*tile_size.y);
            quad[1].texCoords = sf::Vector2f((tu+1)*tile_size.x, tv*tile_size.y);
            quad[2].texCoords = sf::Vector2f((tu+1)*tile_size.x, (tv+1)*tile_size.y);
            quad[3].texCoords = sf::Vector2f(tu*tile_size.x, (tv+1)*tile_size.y);
        }
    }

    sf::Shader shader;
    shader.loadFromFile("shaders/test.frag", sf::Shader::Fragment);
    shader.setUniform("texture", tileset);

    sf::RenderTexture buffer;
    buffer.create(window.getSize().x, window.getSize().y);
    sf::Sprite buffer_spr(tileset);

    sf::RenderStates rs;
    rs.texture = &tileset;
    rs.shader = &shader;

    while (window.isOpen()){
        sf::Event event;
        while (window.pollEvent(event)){
            if(event.type == sf::Event::Closed)
                window.close();
        }
        buffer.clear();
        for (size_t y=0; y<map_size.y; y++){
            for (size_t x=0; x<map_size.x; x++){
                shader.setUniform("color_id", sf::Glsl::Vec4(x, y, 0, 255));

                int tileNumber = level[y*map_size.x+x];
                int tu = tileNumber % (tileset.getSize().x / tile_size.x);
                int tv = tileNumber / (tileset.getSize().x / tile_size.x);

                buffer_spr.setTextureRect(sf::IntRect(tu*tile_size.x, tv*tile_size.y, tile_size.x, tile_size.y));
                buffer_spr.setPosition(x*tile_size.x, y*tile_size.y);
                buffer.draw(buffer_spr, rs);
            }
        }
        buffer.display();
        window.clear(sf::Color::White);
        window.draw(vertices, &tileset);
        sf::Sprite test_sprite(buffer.getTexture());
        window.draw(test_sprite);
        window.display();
        sf::sleep(sf::milliseconds(10));
    }
    return 0;
}

works now:


not bad for a prototype. last part is identify the color under the mouse cursor and return the id.

EDIT 2: tried this right before clearing the buffer, but it does not update the color (with or without the first line)
sf::Texture::bind(&buffer.getTexture(), sf::Texture::CoordinateType::Pixels);
        sf::Color color(10, 10, 10, 255);
        glReadPixels(sf::Mouse::getPosition().x, sf::Mouse::getPosition().y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &color);
        std::cout << size_t(color.r) << " ";
« Last Edit: July 21, 2020, 11:04:38 pm by Stauricus »
Visit my game site (and hopefully help funding it? )
Website | IndieDB

Paul

  • Jr. Member
  • **
  • Posts: 78
    • View Profile
Re: need a better collision idea
« Reply #7 on: July 22, 2020, 03:54:00 pm »
Good, I thought it would be necessary to call more OpenGL functions :)

It seems like you get the mouse coordinates outside the window. With few changes it reads right RGBA values from screen. There is still one problem if you call buffer.display(), rendered texture is mirrored while framebuffer? isn't, so it doesn't match. Then some GL function is missing or maybe it would be enough to recalculate the mouse coordinates.

#include <string>
#include <fstream>
#include <iostream>
#include <SFML/OpenGL.hpp>

#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>

int main() {
        sf::RenderWindow window(sf::VideoMode(800, 600), "Tests");
        window.setVerticalSyncEnabled(true);
        sf::Clock clock;
        sf::Vector2i mouse;    

        sf::Vector2u tile_size(64, 64);
        sf::Vector2u map_size(8, 8);
        sf::Texture tileset;
        tileset.loadFromFile("texture.png");

        sf::VertexArray vertices;
        vertices.setPrimitiveType(sf::Quads);
        vertices.resize(map_size.x * map_size.y * 4);

        const int level[] = {
                   0, 1, 2, 3, 4, 5, 6, 7,
                   8, 9, 10, 11, 12, 13, 14, 15,
                   16, 17, 18, 19, 20, 21, 22, 23,
                   24, 25, 26, 27, 28, 29, 30, 31,
                   0, 1, 2, 3, 4, 5, 6, 7,
                   8, 9, 10, 11, 12, 13, 14, 15,
                   16, 17, 18, 19, 20, 21, 22, 23,
                   24, 25, 26, 27, 28, 29, 30, 31,
        };

        for (unsigned int i = 0; i < map_size.x; ++i) {
                for (unsigned int j = 0; j < map_size.y; ++j) {
                        int tileNumber = level[i + j * map_size.x];
                        int tu = tileNumber % (tileset.getSize().x / tile_size.x);
                        int tv = tileNumber / (tileset.getSize().x / tile_size.x);
                        sf::Vertex* quad = &vertices[(i + j * map_size.x) * 4];

                        quad[0].position = sf::Vector2f(i * tile_size.x, (j)*tile_size.y);
                        quad[1].position = sf::Vector2f((i + 1) * tile_size.x, (j)*tile_size.y);
                        quad[2].position = sf::Vector2f((i + 1) * tile_size.x, (j + 1) * tile_size.y);
                        quad[3].position = sf::Vector2f(i * tile_size.x, (j + 1) * tile_size.y);

                        quad[0].texCoords = sf::Vector2f(tu * tile_size.x, tv * tile_size.y);
                        quad[1].texCoords = sf::Vector2f((tu + 1) * tile_size.x, tv * tile_size.y);
                        quad[2].texCoords = sf::Vector2f((tu + 1) * tile_size.x, (tv + 1) * tile_size.y);
                        quad[3].texCoords = sf::Vector2f(tu * tile_size.x, (tv + 1) * tile_size.y);
                }
        }

        sf::Shader shader;
        shader.loadFromFile("shaders/test.frag", sf::Shader::Fragment);
        shader.setUniform("texture", tileset);

        sf::RenderTexture buffer;
        buffer.create(window.getSize().x, window.getSize().y);
        sf::Sprite buffer_spr(tileset);

        sf::RenderStates rs;
        rs.texture = &tileset;
        rs.shader = &shader;

        while (window.isOpen()) {
                sf::Event event;
                while (window.pollEvent(event)) {
                        if (event.type == sf::Event::Closed)
                                window.close();
                }

                mouse = sf::Mouse::getPosition(window);
               
                buffer.clear();
                for (size_t y = 0; y < map_size.y; y++) {
                        for (size_t x = 0; x < map_size.x; x++) {

                                // Calc ID
                                int ID = 50 + y * map_size.x + x;
                                int r = (ID & 0x000000FF) >> 0;
                                int g = (ID & 0x0000FF00) >> 8;
                                int b = (ID & 0x00FF0000) >> 16;

                                shader.setUniform("color_id", sf::Glsl::Vec4(r / 255.0f, g / 255.0f, b / 255.0f, 255));

                                int tileNumber = level[y * map_size.x + x];
                                int tu = tileNumber % (tileset.getSize().x / tile_size.x);
                                int tv = tileNumber / (tileset.getSize().x / tile_size.x);

                                buffer_spr.setTextureRect(sf::IntRect(tu * tile_size.x, tv * tile_size.y, tile_size.x, tile_size.y));
                                buffer_spr.setPosition(x * tile_size.x, y * tile_size.y);
                                buffer.draw(buffer_spr, rs);
                        }
                }
                //buffer.display(); // Mirrored        

                clock.restart();
                sf::Texture::bind(&buffer.getTexture(), sf::Texture::CoordinateType::Pixels);
                unsigned char data[4];
                glReadPixels(mouse.x, mouse.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, data);
                sf::Time benchmark = clock.getElapsedTime();
                std::cout << (float)benchmark.asMicroseconds() / 1000 << " ms \n";

                // Get ID from color
                int selected_ID = (data[0] + data[1] * 256 + data[2] * 256 * 256) - 50;

                //std::cout << "RGBA: [" << static_cast<unsigned int>(data[0]) << " " << static_cast<unsigned int>(data[1]) << " " << static_cast<unsigned int>(data[2]) << "] " << static_cast<unsigned int>(data[3]) << "\n";
                std::string title = "Mouse: [" + std::to_string(mouse.x) + "; " + std::to_string(mouse.y) + "]     " +
                        "R[" + std::to_string((unsigned char)data[0]) + "] G:[" + std::to_string((unsigned char)data[1]) +
                        "] B:[" + std::to_string((unsigned char)data[2]) + "] A:[" + std::to_string((unsigned char)data[3]) + "]";

                if (selected_ID > -1)
                        title +=  + "  ID: " + std::to_string(selected_ID);
                window.setTitle(title);

                window.clear(sf::Color::White);        
                window.draw(vertices, &tileset);
                sf::Sprite test_sprite(buffer.getTexture());
                window.draw(test_sprite);
                window.display();
                //sf::sleep(sf::milliseconds(10));
        }
        return 0;
}

« Last Edit: July 22, 2020, 11:36:46 pm by Paul »

Paul

  • Jr. Member
  • **
  • Posts: 78
    • View Profile
Re: need a better collision idea
« Reply #8 on: July 23, 2020, 12:08:54 am »
Framebuffer like everything else in OpenGL is drawn from bottom left position, inverting helped.

glReadPixels(mouse.x, window.getSize().y - mouse.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, data);

Now it works, it seems like slowest part (cca 3 ms on my PC) is rendering sprites into buffer/RenderTexture with shader (without shader it's cca 0.3 ms). I have no experience with them, hard to say. I rewrote it a bit to make more sense (use texture from post above). There are no extra optimalizations, it render all sprites to buffer etc. Anyway, this is probably the slowest way to do it. With some complex scene it can be pretty slow.

#include <string>
#include <iostream>

#include <SFML/OpenGL.hpp>
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>

class Entity
{
public:
        size_t objectID;
        sf::Sprite sprite;
        unsigned int frame;
        sf::Vector2f pos;
        sf::Vector2f speed;

        Entity(const sf::Texture& texture, const unsigned int& frame, const size_t& objectID, const sf::Vector2f& pos, const sf::Vector2u& texture_size, const sf::Vector2u& frame_size) : objectID(objectID), frame(frame), pos(pos)
        {
                speed = { rand() % 500 / 10 + 50.f, rand() % 500 / 10 + 50.f };

                sf::IntRect frameRect;
                frameRect.left = (frame % texture_size.x) * frame_size.x;
                frameRect.top = (frame / texture_size.x) * frame_size.y;
                frameRect.width = frame_size.x;
                frameRect.height = frame_size.y;

                sprite.setTexture(texture);
                sprite.setTextureRect(frameRect);
                sprite.setPosition(pos);
        }

        void OnUpdate(const float& dt, const sf::Vector2u& posLimit)
        {
                pos.x += speed.x * dt;
                pos.y += speed.y * dt;

                if ((pos.x > posLimit.x - 64) || (pos.x < 0))
                {
                        speed.x = -speed.x;
                }

                if ((pos.y > posLimit.y - 64) || (pos.y < 0))
                {
                        speed.y = -speed.y;
                }

                sprite.setPosition(pos);
        }
};

class EntityManager
{
public:
        size_t lastID;
        bool sorting;
        std::vector<Entity> v;

public:
        EntityManager() : lastID(0), sorting(false) {};

        void Add(const int& number, const sf::Texture& texture, const sf::Vector2u& posLimit, const sf::Vector2u& texture_size, const sf::Vector2u& frame_size )
        {
                v.reserve(v.size() + number);
                for (int i = 0; i < number; i++)
                {
                        v.emplace_back(Entity(texture, rand() % 36, lastID, { rand() % posLimit.x + 50.f, rand() % posLimit.y + 50.f }, texture_size, frame_size));
                        lastID++;
                }
        };

        void OnUpdate(const float& dt, const sf::Vector2u posLimit, const bool& movingObjects)
        {
                if (movingObjects)
                {
                        for (Entity& entity : v)
                                entity.OnUpdate(dt, posLimit);

                        if (sorting)
                                Sort();
                }
        }

        void Sort()
        {
                std::sort(v.begin(), v.end(), [](const Entity& lhs, const Entity& rhs)
                        {
                                return lhs.pos.y < rhs.pos.y;
                        });
        }

        void Draw(sf::RenderWindow& window)
        {
                for (Entity& entity : v)
                {
                        window.draw(entity.sprite);
                }
        }

        void PrintOut(const size_t& objectID)
        {
                for (Entity& entity : v)
                {
                        if (entity.objectID == objectID)
                        {
                                std::cout << "-------------------- " << std::endl;
                                std::cout << "ID: " << entity.objectID << std::endl;
                                std::cout << "Pos: [" << entity.pos.x << "; " << entity.pos.y << "] " << std::endl;
                                return;
                        }
                }
                std::cout << "Entity with ID: " << objectID << " not found." << std::endl;
        }
};

int main() {
        sf::RenderWindow window(sf::VideoMode(800, 600), "Tests");
        //window.setVerticalSyncEnabled(true);
        window.setFramerateLimit(60);
        sf::Clock clock;
        sf::Vector2i mouse;

        sf::VertexArray vertices;
        vertices.setPrimitiveType(sf::Quads);

        sf::Texture texture; // 9 x 4 = 36 frames (64x64 pixels)
        texture.loadFromFile("texture.png");
        sf::Vector2u texture_size = { 9, 4 }; // Rows, cols
        sf::Vector2u frame_size = { 64, 64 }; // In pixels             

        sf::Shader shader;
        shader.loadFromFile("shaders/test.frag", sf::Shader::Fragment);
        shader.setUniform("texture", texture);

        sf::RenderTexture buffer;
        buffer.create(window.getSize().x, window.getSize().y);
        sf::Sprite rt_buffer_sprite;
        sf::Sprite buffer_spr(texture); // Temp
        buffer_spr.setTexture(texture);

        sf::RenderStates rs;
        rs.texture = &texture;
        rs.shader = &shader;

        EntityManager objectManager;
        objectManager.Add(100, texture, { window.getSize().x - 100, window.getSize().y - 100 }, texture_size, frame_size);
        objectManager.Sort();

        bool drawBuffer = true;
        bool drawObjects = false;
        bool movingObjects = false;
        bool benchmarkOutput = false;

        int selected_ID;

        while (window.isOpen())
        {
                // Process events
                sf::Event event;
                while (window.pollEvent(event))
                {
                        switch (event.type)
                        {
                        case sf::Event::Closed:
                                window.close();
                                break;

                        case sf::Event::KeyPressed:
                        {
                                if (event.key.code == sf::Keyboard::Num1)
                                        drawBuffer = !drawBuffer;

                                if (event.key.code == sf::Keyboard::Num2)
                                        drawObjects = !drawObjects;

                                if (event.key.code == sf::Keyboard::Space)
                                        movingObjects = !movingObjects;

                                if (event.key.code == sf::Keyboard::S)
                                        objectManager.sorting = !objectManager.sorting;

                                if (event.key.code == sf::Keyboard::B)
                                        benchmarkOutput = !benchmarkOutput;
                        }
                        break;

                        case sf::Event::MouseButtonPressed:
                        {
                                if (event.mouseButton.button == sf::Mouse::Left)
                                {
                                        if (selected_ID > -1)
                                        {
                                                objectManager.PrintOut(selected_ID);
                                        }
                                }
                        }
                        break;

                        default: break;
                        }
                }

                // Update
                mouse = sf::Mouse::getPosition(window);
                objectManager.OnUpdate(1 / 60.f, window.getSize(), movingObjects);

                // Buffer
                clock.restart();
                buffer.clear();
                for (int i = 0; i < objectManager.v.size(); i++)
                {
                        // Calc ID
                        size_t ID = objectManager.v[i].objectID + 50;
                        int r = (ID & 0x000000FF) >> 0;
                        int g = (ID & 0x0000FF00) >> 8;
                        int b = (ID & 0x00FF0000) >> 16;

                        shader.setUniform("color_id", sf::Glsl::Vec4(r / 255.0f, g / 255.0f, b / 255.0f, 255));

                        sf::IntRect frameRect;
                        frameRect.left = (objectManager.v[i].frame % texture_size.x) * frame_size.x;
                        frameRect.top = (objectManager.v[i].frame / texture_size.x) * frame_size.y;
                        frameRect.width = frame_size.x;
                        frameRect.height = frame_size.y;

                        buffer_spr.setTextureRect(frameRect);
                        buffer_spr.setPosition(objectManager.v[i].pos);
                        buffer.draw(buffer_spr, rs); // Bit slow ??
                        //buffer.draw(buffer_spr);
                }
                buffer.display();

                //sf::Texture::bind(&buffer.getTexture(), sf::Texture::CoordinateType::Pixels); // It's not needed?
                unsigned char data[4];
                //glPixelStorei(GL_PACK_ALIGNMENT, 1);
                glReadPixels(mouse.x, window.getSize().y - mouse.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, data);

                // Get ID from color
                selected_ID = (data[0] + data[1] * 256 + data[2] * 256 * 256) - 50;

                // Benchmark end
                sf::Time benchmark = clock.getElapsedTime();
                if (benchmarkOutput)
                        std::cout << (float)benchmark.asMicroseconds() / 1000 << " ms \n";

                //std::cout << "RGBA: [" << static_cast<unsigned int>(data[0]) << " " << static_cast<unsigned int>(data[1]) << " " << static_cast<unsigned int>(data[2]) << "] " << static_cast<unsigned int>(data[3]) << "\n";
                std::string title = "Mouse: [" + std::to_string(mouse.x) + "; " + std::to_string(mouse.y) + "]     " +
                        "R[" + std::to_string((unsigned char)data[0]) + "] G:[" + std::to_string((unsigned char)data[1]) +
                        "] B:[" + std::to_string((unsigned char)data[2]) + "] A:[" + std::to_string((unsigned char)data[3]) + "]";

                if (objectManager.sorting)
                        title += +"  SORTING";

                if (selected_ID > -1)
                        title += +"  ID: " + std::to_string(selected_ID);
                window.setTitle(title);

                // Draw
                window.clear({ 20, 20, 20 });

                if (drawBuffer)
                {
                        rt_buffer_sprite.setTexture(buffer.getTexture());
                        window.draw(rt_buffer_sprite);
                }

                if (drawObjects)
                        objectManager.Draw(window);

                window.display();
        }
        return 0;
}
« Last Edit: July 23, 2020, 12:14:14 am by Paul »

Stauricus

  • Sr. Member
  • ****
  • Posts: 369
    • View Profile
    • A Mafia Graphic Novel
    • Email
Re: need a better collision idea
« Reply #9 on: July 24, 2020, 02:58:47 pm »
great! i'll try to put it to my code
one question, tough: does the code from your last post works for you? I get a segmentation fault at line 88:
return lhs.pos.y < rhs.pos.y;

for some reason I can't debug a code that contains shader, so i don't know what could be the cause
Visit my game site (and hopefully help funding it? )
Website | IndieDB

Paul

  • Jr. Member
  • **
  • Posts: 78
    • View Profile
Re: need a better collision idea
« Reply #10 on: July 25, 2020, 02:19:19 pm »
In Visual Studio 2019, C++17 there are no errors in debug and release modes (x64).

I tried tests with randomized seed, it was still ok.
« Last Edit: July 25, 2020, 02:27:58 pm by Paul »

Stauricus

  • Sr. Member
  • ****
  • Posts: 369
    • View Profile
    • A Mafia Graphic Novel
    • Email
Re: need a better collision idea
« Reply #11 on: July 25, 2020, 07:08:31 pm »
weird, in GCC (Linux, Debian Testing) +14 and +17 it seems not to be emplacing new itens in the Add method.
anyway, its working for me now, so i consider it solved. also need optimizations, but this test works:

#include <string>
#include <fstream>
#include <iostream>

#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/OpenGL.hpp>

int main(){
    sf::RenderWindow window(sf::VideoMode(512, 256), "Tests");
    sf::Vector2u tile_size(32, 32);
    sf::Vector2u map_size(16, 8);

    sf::Texture tileset;
    tileset.loadFromFile("terrain-6.png");

    sf::VertexArray vertices;
    vertices.setPrimitiveType(sf::Quads);
    vertices.resize(map_size.x*map_size.y*4);

    const int level[] ={
        0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0,
        1, 1, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3,
        0, 1, 0, 0, 2, 0, 3, 3, 3, 0, 1, 1, 1, 0, 0, 0,
        0, 1, 1, 0, 3, 3, 3, 0, 0, 0, 1, 1, 1, 2, 0, 0,
        0, 0, 1, 0, 3, 0, 2, 2, 0, 0, 1, 1, 1, 1, 2, 0,
        2, 0, 1, 0, 3, 0, 2, 2, 2, 0, 1, 1, 1, 1, 1, 1,
        0, 0, 1, 0, 3, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1,
    };


    for (unsigned int i = 0; i < map_size.x; ++i){
        for (unsigned int j = 0; j < map_size.y; ++j){
            int tileNumber = level[i+j*map_size.x];
            int tu = tileNumber % (tileset.getSize().x / tile_size.x);
            int tv = tileNumber / (tileset.getSize().x / tile_size.x);
            sf::Vertex* quad = &vertices[(i+j*map_size.x)*4];

            quad[0].position = sf::Vector2f(i*tile_size.x, (j)*tile_size.y);
            quad[1].position = sf::Vector2f((i+1)*tile_size.x, (j)*tile_size.y);
            quad[2].position = sf::Vector2f((i+1)*tile_size.x, (j+1)*tile_size.y);
            quad[3].position = sf::Vector2f(i*tile_size.x, (j+1)*tile_size.y);

            quad[0].texCoords = sf::Vector2f(tu*tile_size.x, tv*tile_size.y);
            quad[1].texCoords = sf::Vector2f((tu+1)*tile_size.x, tv*tile_size.y);
            quad[2].texCoords = sf::Vector2f((tu+1)*tile_size.x, (tv+1)*tile_size.y);
            quad[3].texCoords = sf::Vector2f(tu*tile_size.x, (tv+1)*tile_size.y);
        }
    }

    sf::Shader shader;
    shader.loadFromFile("shaders/test.frag", sf::Shader::Fragment);
    shader.setUniform("texture", tileset);

    sf::RenderTexture buffer;
    buffer.create(window.getSize().x, window.getSize().y);
    sf::Sprite buffer_spr(tileset);

    sf::RenderStates rs;
    rs.texture = &tileset;
    rs.shader = &shader;

    while (window.isOpen()){
        buffer.clear();
        for (size_t y=0; y<map_size.y; y++){
            for (size_t x=0; x<map_size.x; x++){
                // Calc ID
                int ID = 1 + y * map_size.x + x;
                int r = (ID & 0x000000FF) >> 0;
                int g = (ID & 0x0000FF00) >> 8;
                int b = (ID & 0x00FF0000) >> 16;

                shader.setUniform("color_id", sf::Glsl::Vec4(r / 255.0f, g / 255.0f, b / 255.0f, 255));

                int tileNumber = level[y*map_size.x+x];
                int tu = tileNumber % (tileset.getSize().x / tile_size.x);
                int tv = tileNumber / (tileset.getSize().x / tile_size.x);

                buffer_spr.setTextureRect(sf::IntRect(tu*tile_size.x, tv*tile_size.y, tile_size.x, tile_size.y));
                buffer_spr.setPosition(x*tile_size.x, y*tile_size.y);
                buffer.draw(buffer_spr, rs);
            }
        }
        buffer.display();
        sf::Event event;
        while (window.pollEvent(event)){
            if(event.type == sf::Event::Closed) window.close();
            if(event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left){
                unsigned char data[4];
                glReadPixels(sf::Mouse::getPosition(window).x, window.getSize().y-sf::Mouse::getPosition(window).y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &data);
                std::cout << " id:" << (data[0] + data[1] * 256 + data[2] * 256 * 256) - 1;
            }
        }
        window.clear(sf::Color::White);
        window.draw(vertices, &tileset);
        sf::Sprite test_sprite(buffer.getTexture());
        //window.draw(test_sprite);
        window.display();
        sf::sleep(sf::milliseconds(10));
    }
    return 0;
}
 

funny fact that maybe the developers could explain: I tought that glReadPixels would get pixels that are in the screen, not in textures or other render targets; but we are drawing the colorized sprites to a RenderTexture. still, the function reads it correctly (of course, if nothing else is drawn to the screen yet in that frame, in which case the id colors would be overlaid). maybe both RenderTargets are seen as the same thing for OpenGL?

anyway, thank you for the help! i'm going to focus on improving the performance, but the issue is completely solved.


EDIT: I forgot to mention, i think the shader isn't really optimized also, so it could be a bottleneck too? I simply made something that could work, but the newer versions of GLSL doesn't even accept things like gl_FragColor and gl_TexCoord.
« Last Edit: July 25, 2020, 07:39:52 pm by Stauricus »
Visit my game site (and hopefully help funding it? )
Website | IndieDB

Paul

  • Jr. Member
  • **
  • Posts: 78
    • View Profile
Re: [SOLVED] need a better collision idea
« Reply #12 on: July 25, 2020, 11:02:24 pm »
Thats probably why sorting function gets errors. Maybe this line: v.reserve(v.size() + number); is problematic for your compiler. It can be omitted and use push_back instead of emplace. But it's not important.

I also thank you, I can get rid of bitmasks finally :)

About shader, I thought that part: if(pixel.a != 0.0){..} is slow, but with this code result was same:

uniform sampler2D texture;
uniform vec4 color_id;

void main(){
    vec4 textureColor = texture2D(texture, gl_TexCoord[0].xy).rgba;
    gl_FragColor = vec4(color_id.r, color_id.g, color_id.b, textureColor.a);    
}

Btw. tool for shaders which looks usable https://github.com/dfranx/SHADERed

Stauricus

  • Sr. Member
  • ****
  • Posts: 369
    • View Profile
    • A Mafia Graphic Novel
    • Email
Re: [SOLVED] need a better collision idea
« Reply #13 on: August 02, 2020, 07:01:44 pm »
hi, I just wanted to show the results here. not optimized yet, but its beautiful


Quote
n is the current tile where the mouse is
id is color id of that tile (always n + 1)
r, g and b are the corresponding colors of that id
x and y are the tile position on the map
m_stamp_sprite are the positions of the sprite's vertices (x and y)
identified building is... well, the building identified by the color
also, i got rid of the rendertexture.i simply draw the colorized tiles to screen, process what is needed, clear the screen and draw what will be visible to the user.
Visit my game site (and hopefully help funding it? )
Website | IndieDB