// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
#include <iostream>
#include "Shader.h"
#include <SOIL.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

void key_callback(GLFWwindow* windows, int key, int scancode, int action, int mode);
const GLuint WIDTH = 800, HEIGHT = 600;

GLfloat yaw = -90.0f; // Yaw is initialized to -90.0 degrees since a yaw of 0.0 results in a direction vector pointing to the right (due to how Eular angles work) so we initially rotate a bit to the left.
GLfloat pitch = 0.0f;
GLfloat lastX = WIDTH / 2.0;
GLfloat lastY = HEIGHT / 2.0;
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
//顶点着色器源码:


float Offset = 0.1;
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);

bool keys[1024];
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
void do_movement()
{
// 摄像机控制
GLfloat cameraSpeed = 5.0f * deltaTime;
if (keys[GLFW_KEY_W])
cameraPos += cameraSpeed * cameraFront;
if (keys[GLFW_KEY_S])
cameraPos -= cameraSpeed * cameraFront;
if (keys[GLFW_KEY_A])
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
if (keys[GLFW_KEY_D])
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}

GLfloat fov = 45.0f;
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
if (fov >= 1.0f && fov <= 45.0f)
fov -= yoffset;
if (fov <= 1.0f)
fov = 1.0f;
if (fov >= 45.0f)
fov = 45.0f;
}
int main()
{
/*
//此部分写变换
glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
std::cout << vec.x << vec.y << vec.z << std::endl;



//然后是旋转和缩放箱子
glm::mat4 trans;
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
//因为我们把这个矩阵传递给了GLM的每个函数,GLM会自动将矩阵相乘
//返回的结果是一个包括了多个变换的变换矩阵。
//转换弧度glm::radians(90.0f)将角度转换为弧度。
*/




std::cout << "Starting GLFW context, OpenGL 3.3" << std::endl;

//这都是初始化GLFW,后面的要看文档
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);


//创建窗口,
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
if (window == nullptr)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);//通知GLFW将我们窗口的上下文设置为当前线程的主上下文



//不知道回调函数为什么要加在这里??
//glfwSetKeyCallback(window, key_callback2);回调函数只有最下面那个有作用
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);

//初始化GLEW(管理OpenGL的函数指针)
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}


glViewport(0, 0, WIDTH, HEIGHT);//前两个控制左下角的位置,后面是窗口宽度高度(像素)

//开启深度测试
glEnable(GL_DEPTH_TEST);

Shader ourShader("shader.vs", "shader.frag");


//这里省略了颜色值,省的就是位置坐标和纹理坐标
GLfloat vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,

-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,

-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,

0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,

-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,

-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};

GLuint indices[] = { // Note that we start from 0!
0, 1, 3, // First Triangle
1, 2, 3 // Second Triangle
};

glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};




GLuint VBO, VAO,EBO;
//使用glGenBuffers函数和一个缓冲ID生成一个VBO对象??
glGenBuffers(1, &VBO);
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &EBO);

//绑定VAO
glBindVertexArray(VAO);
//复制顶点数组到缓冲中,glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数
glBindBuffer(GL_ARRAY_BUFFER, VBO);//glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//这局是把定义好的顶点数据复制到缓冲的内存中。

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);


//设置顶点属性指针,和颜色属性

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);

//因为现在是两个属性,需要向右移动步长,6个
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
//由于我们添加了额外的顶点属性,我们要告诉OPenGL新的顶点格式。




//生成纹理
GLuint texture1,texture2;
glGenTextures(1, &texture1);//??
//glGenTextures(1, &texture2);
//glGenTextures函数首先需要输入生成纹理的数量,然后储存在GLuint数组中,让之前任何纹理指令都可以配置当前绑定的纹理。
//下面这一句是绑定纹理。
//glActiveTexture(GL_TEXTURE0);//多个位置时,使用前需要先激活
glBindTexture(GL_TEXTURE_2D, texture1);
//glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);

//同理设置第二个纹理位置
//glActiveTexture(GL_TEXTURE1);//多个位置时,使用前需要先激活
//glBindTexture(GL_TEXTURE_2D, texture2);
//glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);



//设置到两个轴上
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); // Set texture wrapping to GL_REPEAT (usually basic wrapping method)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
//上面两句是对S和T轴设置环绕方式,环绕方式为重复纹理图像。
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);


//下面生成纹理。2D纹理,因此纹理目标是GL_TEXTURE_2D
//下面两句设置方法和缩小的纹理过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

//使用SOIL加载图片,自此到循环前都是加载并生成纹理。
int width, height;
std::cout << SOIL_last_result() << std::endl;
unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0, SOIL_LOAD_RGB);
std::cout << SOIL_last_result() << std::endl;
if (image)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
//第一个参数纹理目标Target,会生成与当前绑定纹理对象同样纹理
//设置纹理指定多级渐远纹理级别,0,基本级别
//第三个参数,纹理储存为何种格式,RGB
//四五是设置最终的纹理的宽度和高度,就用之前的变量。
//0
//七八位源图的格式和数据类型,使用RGB加载并储存为char(byte)数组
//最后参数为图像的数据
}
else
{
std::cout << "Failed to load texture." << std::endl;
}

//当前绑定的纹理对象会被附加上纹理图像,然而只有0级别的纹理图像被加载 了,多级的话需要手动设置
//或者可以,在生成纹理之后调用glGenerateMipmap,这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。
glGenerateMipmap(GL_TEXTURE_2D);
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);





glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture2);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
// Set texture filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
image = SOIL_load_image("awesomeface.png", &width, &height, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);





/*
//摄像机
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);//x摄像机位置
//摄像机方向
glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);

glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

//上轴
glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);
*/
/*
//lookat
glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f),
glm::vec3(0.0f,0.0f,0.0f),
glm::vec3(0.0f, 1.0f, 0.0f));
*/

//防止退出的循环
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
do_movement();

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//glClearColor是一个状态设置函数,glClear是一个状态应用函数


//绘图
ourShader.Use();

glUniform1f(glGetUniformLocation(ourShader.Program, "para"), Offset);


//坐标系统


//我们将矩阵向我们要进行移动场景的反向移动。,可以把矩阵看摄像机






glActiveTexture(GL_TEXTURE0);//这句是激活。
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);

//这里因为要使用第二个纹理,改变一点渲染流程,先绑定两个纹理到对应的纹理单元,然后定义哪个uniform采样器应对哪个纹理单元。

/*
GLfloat radius = 10.0f;
GLfloat camX = sin(glfwGetTime())*radius;
GLfloat camZ = cos(glfwGetTime())*radius;
glm::mat4 view;
view = glm::lookAt(glm::vec3(camX,0.0,camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0,1.0,0.0));
//然后是透视投影矩阵。*/



glm::mat4 view;
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);




glm::mat4 projection;
projection = glm::perspective(glm::radians(fov), (GLfloat)WIDTH/ (GLfloat)HEIGHT, 0.1f, 100.f);


GLint modelLoc = glGetUniformLocation(ourShader.Program, "model");
GLint viewLoc = glGetUniformLocation(ourShader.Program, "view");
GLint projLoc = glGetUniformLocation(ourShader.Program, "projection");


glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

glBindVertexArray(VAO);
for (GLuint i = 0; i < 10; i++)
{
// Calculate the model matrix for each object and pass it to shader before drawing
glm::mat4 model;
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * i;
if (i % 3 == 0) // every 3rd iteration (including the first) we set the angle using GLFW's time function.
angle = glfwGetTime() * 25.0f;
model = glm::rotate(model, angle, glm::vec3(1.0f, 0.3f, 0.5f));
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));

glDrawArrays(GL_TRIANGLES, 0, 36);
}
glBindVertexArray(0);














GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;

glfwSwapBuffers(window);
}

//回调函数

//释放:
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);

glfwTerminate(); //释放/删除之前的分配的所有资源
return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{

std::cout << key << std::endl;
//用户按下ESC键,我们设置window窗口WindowShouldClose属性为true
//关闭应用程序
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key == GLFW_KEY_UP&&action == GLFW_PRESS)
{
Offset += 0.1;
if (Offset >= 1.0f) Offset = 1.0f;
}
if (key == GLFW_KEY_DOWN&&action == GLFW_PRESS)
{
Offset -= 0.1;
if (Offset <= 0.0f) Offset = 0.0f;
}
if (action == GLFW_PRESS)
keys[key] = true;
else if (action == GLFW_RELEASE)
keys[key] = false;

}
bool firstMouse = true;
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}

GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos; // Reversed since y-coordinates go from bottom to left
lastX = xpos;
lastY = ypos;

GLfloat sensitivity = 0.05; // Change this value to your liking
xoffset *= sensitivity;
yoffset *= sensitivity;

yaw += xoffset;
pitch += yoffset;

// Make sure that when pitch is out of bounds, screen doesn't get flipped
if (pitch > 89.0f)
pitch = 89.0f;
if (pitch < -89.0f)
pitch = -89.0f;

glm::vec3 front;
front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
front.y = sin(glm::radians(pitch));
front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
cameraFront = glm::normalize(front);
}