// #define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ #include #include #include #include #include #include #include #if defined(__ANDROID__) #define APP_MAIN SDL_main #define SDL_WINDOW_FLAG SDL_WINDOW_FULLSCREEN #else #define APP_MAIN main #define SDL_WINDOW_FLAG 0 #endif #define SCREEN_WIDTH 1600.f #define SCREEN_HEIGHT 720.f #define BALL_SIZE 50.f #define PLATE_WIDTH 50.f #define PLATE_HEIGHT 150.f #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) #define clamp(x, minV, maxV) max((minV), min((maxV), (x))) static SDL_Window *window = NULL; static bool running = true; static Uint8 color = 0x18; static Uint64 elapsed = 0; static float ballVelX = -0.5f; static float ballVelY = 0.5f; static float ballSpeed = 600.f; static float ballAccel = 25.f; static float opponentSpeed = 450.f; static float opponentVelY = 0.f; const char* vShader = "#version 300 es\n" "layout (location = 0) in vec2 aPos;\n" "uniform vec4 uColor;\n" "out vec4 color;\n" "void main()\n" "{\n" " gl_Position = vec4(aPos, 0.0, 1.0);\n" " color = uColor;\n" "}"; const char* fShader = "#version 300 es\n" "precision mediump float;\n" "out vec4 fragColor;\n" "in vec4 color;\n" "void main()\n" "{\n" " fragColor = color;\n" "}"; void computeLetterbox(int windowW, int windowH, int logicalW, int logicalH, int* x, int* y, int* w, int* h) { float scaleX = (float)windowW / logicalW; float scaleY = (float)windowH / logicalH; float scale = (scaleX < scaleY) ? scaleX : scaleY; *w = (int)(logicalW * scale); *h = (int)(logicalH * scale); *x = (windowW - *w) / 2; *y = (windowH - *h) / 2; } struct Rect { public: float x; float y; float w; float h; private: float vertices[12]; GLuint vao, vbo; public: Rect(float x, float y, float w, float h) { this->x = x; this->y = y; this->w = w; this->h = h; } void Init() { glGenVertexArrays(1, &vao); glGenBuffers(1, &vbo); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } void Draw() { float xProj = (x / SCREEN_WIDTH) * 2.0f - 1.0f; float yProj = 1.0f - (y / SCREEN_HEIGHT) * 2.0f; float wProj = (w / SCREEN_WIDTH) * 2.0f; float hProj = (h / SCREEN_HEIGHT) * 2.0f; vertices[0] = xProj; vertices[1] = yProj; vertices[2] = xProj + wProj; vertices[3] = yProj; vertices[4] = xProj + wProj; vertices[5] = yProj - hProj; vertices[6] = xProj; vertices[7] = yProj; vertices[8] = xProj; vertices[9] = yProj - hProj; vertices[10] = xProj + wProj; vertices[11] = yProj - hProj; glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); glBindVertexArray(vao); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); } bool CollidesWith(const Rect& b) { return (x < b.x + b.w && x + w > b.x && y < b.y + b.h && y + h > b.y); } }; static Rect ball( SCREEN_WIDTH / 2 - BALL_SIZE / 2, SCREEN_HEIGHT / 2 - BALL_SIZE / 2, BALL_SIZE, BALL_SIZE ); static Rect player( 50.f, SCREEN_HEIGHT / 2 - PLATE_HEIGHT / 2, PLATE_WIDTH, PLATE_HEIGHT ); static Rect opponent( SCREEN_WIDTH - PLATE_WIDTH - 50.f, SCREEN_HEIGHT / 2 - PLATE_HEIGHT / 2, PLATE_WIDTH, PLATE_HEIGHT ); bool paused = true; extern "C" int APP_MAIN(int argc, char **argv) { SDL_SetAppMetadata("Example Renderer Primitives", "1.0", "com.example.renderer-primitives"); SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight"); if (!SDL_Init(SDL_INIT_VIDEO)) { SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); return SDL_APP_FAILURE; } SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); window = SDL_CreateWindow("examples/renderer/primitives", SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_FLAG | SDL_WINDOW_OPENGL); if (!window) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } SDL_GLContext glContext = SDL_GL_CreateContext(window); int success; char infoLog[512]; GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vShader, NULL); glCompileShader(vertexShader); // print compile errors if any glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); printf("ERROR::SHADER::VERTEX::COMPILATION_FAILED\n%s\n", infoLog); }; GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fShader, NULL); glCompileShader(fragmentShader); // print compile errors if any glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); printf("ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n%s\n", infoLog); }; GLuint program = glCreateProgram(); glAttachShader(program, vertexShader); glAttachShader(program, fragmentShader); glLinkProgram(program); // print linking errors if any glGetProgramiv(program, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(program, 512, NULL, infoLog); printf("ERROR::SHADER::PROGRAM::LINKING_FAILED\n%s\n", infoLog); } glDeleteShader(vertexShader); glDeleteShader(fragmentShader); ball.Init(); player.Init(); opponent.Init(); int drawableW, drawableH; SDL_GetWindowSizeInPixels(window, &drawableW, &drawableH); #if defined(__ANDROID__) int vx, vy, vw, vh; computeLetterbox(drawableW, drawableH, SCREEN_WIDTH, SCREEN_HEIGHT, &vx, &vy, &vw, &vh); glViewport(vx, vy, vw, vh); #else glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); #endif glEnable(GL_DEPTH_TEST); while (running) { SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_EVENT_QUIT) { running = false; } if (!paused) { if (event.type == SDL_EVENT_MOUSE_MOTION) { player.y = clamp(event.motion.y - PLATE_HEIGHT / 2, 0, SCREEN_HEIGHT - PLATE_HEIGHT); } else if (event.type == SDL_EVENT_FINGER_MOTION) { player.y = clamp(player.y + (event.motion.y * SCREEN_HEIGHT) - PLATE_HEIGHT / 2, 0, SCREEN_HEIGHT - PLATE_HEIGHT); } } if (paused && event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_SPACE) { paused = false; } else if (paused && event.type == SDL_EVENT_FINGER_UP) { paused = false; } } auto now = SDL_GetTicks(); float dt = now - elapsed; elapsed = now; dt /= 1000.f; if (!paused) { ball.x += ballSpeed * ballVelX * dt; ball.y += ballSpeed * ballVelY * dt; ballSpeed += ballAccel * dt; if (ball.x + BALL_SIZE >= SCREEN_WIDTH || ball.x <= 0) ballVelX *= -1.f; if (ball.x <= 0) { ball.x = 1.f; paused = true; } if (ball.x + BALL_SIZE >= SCREEN_WIDTH) { ball.x = SCREEN_WIDTH - BALL_SIZE - 1.f; paused = true; } if (ball.y + BALL_SIZE >= SCREEN_HEIGHT || ball.y <= 0) ballVelY *= -1.f; if (ball.y + BALL_SIZE >= SCREEN_HEIGHT) ball.y = SCREEN_HEIGHT - BALL_SIZE - 1.f; if (ball.y <= 0) ball.y = 1.f; if (ball.CollidesWith(player)) { ball.x = player.x + PLATE_WIDTH + 1.f; ballVelX *= -1; } if (ball.CollidesWith(opponent)) { ball.x = opponent.x - BALL_SIZE - 1.f; ballVelX *= -1; } opponentVelY = clamp(ball.y - (opponent.y + PLATE_HEIGHT / 2 - BALL_SIZE / 2), -1.f, 1.f); opponent.y = clamp(opponent.y + opponentSpeed * opponentVelY * dt, 0, SCREEN_HEIGHT - PLATE_HEIGHT); } glClearColor(0x18/255.f, 0x18/255.f, 0x18/255.f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Draw your 3D stuff here glUseProgram(program); auto colorLoc = glGetUniformLocation(program, "uColor"); float color[4] = {0.8f, 0.8f, 0.8f, 1.0f}; glUniform4fv(colorLoc, 1, color); ball.Draw(); player.Draw(); color[0] = 0.8f; color[1] = 0.2f; color[2] = 0.25f; color[3] = 1.0f; glUniform4fv(colorLoc, 1, color); opponent.Draw(); SDL_GL_SwapWindow(window); } return 0; }