From b91a0d30ae6484b6c4d981aeafa8d4996c98effe Mon Sep 17 00:00:00 2001 From: Luke Lau Date: Wed, 11 Mar 2020 23:13:41 +0000 Subject: [PATCH] Circular cursor + dragging --- main.cpp | 121 ++++++++++++++++++++++++++++++++++++---------- shapes.hpp | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++-- skybox.cpp | 8 ++-- 3 files changed, 235 insertions(+), 32 deletions(-) diff --git a/main.cpp b/main.cpp index d1a3553..7054e7a 100644 --- a/main.cpp +++ b/main.cpp @@ -30,9 +30,10 @@ using namespace std; -GLuint lightVao; +GLuint lightVao, cursorVao; +GLuint cursorNumIndices; -Program *textureProg, *plainProg, *reflectProg, *pbrProg; +Program *textureProg, *plainProg, *reflectProg, *pbrProg, *cursorProg; ControlWindow controlWindow; @@ -48,6 +49,8 @@ float yaw = 1.57, pitch = 0; Model *targetModel; // The model that the selection is happening on Model::VertexLookup closestVertex; +// How close a vertex needs to be to the cursor before it is "over" +const float closestVertexThreshold = 0.5; std::map manipulators; VertIdx curManipulator = {-1,-1}; @@ -108,22 +111,22 @@ void setLightColorAndPos(GLuint progId, glm::vec3 lightPos, glm::vec4 lightColor glUniform3fv(viewPosLoc, 1, glm::value_ptr(camPos)); } -void drawBox(glm::mat4 trans, glm::vec3 color) { - glUseProgram(plainProg->progId); - glBindVertexArray(lightVao); - setProjectionAndViewUniforms(plainProg->progId); +void drawPlainProg(Program *p, GLuint vao, glm::mat4 trans, glm::vec3 color) { + glUseProgram(p->progId); + glBindVertexArray(vao); + setProjectionAndViewUniforms(p->progId); glm::mat4 model = glm::scale(trans, glm::vec3(0.3)); - GLuint modelLoc = glGetUniformLocation(plainProg->progId, "model"); + GLuint modelLoc = glGetUniformLocation(p->progId, "model"); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); - GLuint colorLoc = glGetUniformLocation(plainProg->progId, "color"); + GLuint colorLoc = glGetUniformLocation(p->progId, "color"); glUniform4fv(colorLoc, 1, glm::value_ptr(color)); glDrawArrays(GL_TRIANGLES, 0, 36); } void drawLight(Light &light) { - drawBox(light.trans, light.color); + drawPlainProg(plainProg, lightVao, light.trans, light.color); } int findNodeTrans(const struct aiNode *n, const struct aiString name, glm::mat4 *dest) { @@ -154,8 +157,18 @@ glm::mat4 worldSpaceToModelSpace(aiNode *node, glm::mat4 m) { return res; } -void highlightVertex() { - drawBox(glm::translate(glm::mat4(1), closestVertex.pos), {1, 1, 0.5}); +void drawCursor(glm::vec3 pos, glm::vec3 color, float scale = 1) { + glUseProgram(cursorProg->progId); + glBindVertexArray(cursorVao); + setProjectionAndViewUniforms(cursorProg->progId); + glm::mat4 model = glm::scale(glm::translate(glm::mat4(1), pos), glm::vec3(scale * 0.4)); + GLuint modelLoc = glGetUniformLocation(cursorProg->progId, "model"); + glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); + + GLuint colorLoc = glGetUniformLocation(cursorProg->progId, "color"); + glUniform4fv(colorLoc, 1, glm::value_ptr(color)); + + glDrawElements(GL_TRIANGLES, cursorNumIndices, GL_UNSIGNED_INT, 0); } void display() { @@ -214,17 +227,18 @@ void display() { sceneModel->draw(skyboxes[activeSkybox], d * 1000); if (curMode == Blendshapes) { - highlightVertex(); + if (closestVertex.distance < closestVertexThreshold) + drawCursor(closestVertex.pos, {0.5,0,1}, 0.9); for (auto v: manipulators) { glm::vec3 color = { 0.4, 1, 0 }; if (closestVertex.meshIdx == v.first.first && closestVertex.vertIdx == v.first.second) color = {1, 0, 0}; - drawBox(glm::translate(glm::mat4(1), v.second), color); + drawCursor(v.second, color); glm::vec3 origVertex = aiVector3DToVec3(bsModel.model->meshes[v.first.first].ai.mVertices[v.first.second]); - drawBox(glm::translate(glm::mat4(1), origVertex), {0,0,1}); + drawCursor(origVertex, {0,0,1}, 0.7); } bsModel.model->draw(skyboxes[activeSkybox], d * 1000); @@ -245,9 +259,8 @@ void display() { glutSwapBuffers(); } -void setupLightBuffers(GLuint progId) { - auto vertices = cube(); - GLuint verticesSize = 36 * 3 * sizeof(GLfloat); +void setupPlainBuffers(GLuint progId, std::vector vertices) { + GLuint verticesSize = vertices.size() * sizeof(glm::vec3); glGenVertexArrays(1, &lightVao); GLuint vbo; @@ -261,6 +274,31 @@ void setupLightBuffers(GLuint progId) { glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); } +GLuint setupBuffersWithIndices(GLuint progId, GLuint vao, + std::vector vertices, + std::vector indices) { + GLuint vbos[2]; + glBindVertexArray(vao); + glGenBuffers(2, vbos); + + int verticesSize = vertices.size() * sizeof(glm::vec3); + // positions + glBindBuffer(GL_ARRAY_BUFFER, vbos[0]); + glBufferData(GL_ARRAY_BUFFER, verticesSize, NULL, GL_STATIC_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, verticesSize, + glm::value_ptr(vertices[0])); + + GLuint posLoc = glGetAttribLocation(progId, "vPosition"); + glEnableVertexAttribArray(posLoc); + glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); + + // indices + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), &indices[0], GL_STATIC_DRAW); + + return indices.size(); +} + bool needToCalculateClosestVertex = false; bool needToInterpolateBlendshapes = false; @@ -297,9 +335,15 @@ void init() { plainProg = new Program("plainvertex.glsl", "plainfrag.glsl"); glUseProgram(plainProg->progId); - setupLightBuffers(plainProg->progId); + setupPlainBuffers(plainProg->progId, cube()); plainProg->validate(); + cursorProg = new Program("plainvertex.glsl", "plainfrag.glsl"); + glUseProgram(cursorProg->progId); + glGenVertexArrays(1, &cursorVao); + cursorNumIndices = setupBuffersWithIndices(cursorProg->progId, cursorVao, sphere(), sphereIndices()); + cursorProg->validate(); + skyboxes.push_back(Skybox(Image("skyboxes/loft/Newport_Loft_Ref.hdr"))); skyboxes.push_back(Skybox(Image("skyboxes/wooden_lounge_8k.hdr"))); skyboxes.push_back(Skybox(Image("skyboxes/machine_shop_02_8k.hdr"))); @@ -360,7 +404,7 @@ void init() { for (int i = 0; i < numBlends; i++) names[i] = bsModel.blendshapes[i].name; controlWindow = createControlWindow(names, &cwDelegate); - camPos = { 0, 22, 81 }; + camPos = { 0, 18, 81 }; camFront = { 0, 0, -1 }; camUp = { 0, 1, 0 }; zfar = 10000; @@ -476,12 +520,40 @@ int prevMouseX, prevMouseY; bool firstMouse = true; void motion(int x, int y) { -#ifdef ENABLE_MOVEMENT if (firstMouse) { prevMouseX = x; prevMouseY = y; firstMouse = false; } + float dx = x - prevMouseX, dy = y - prevMouseY; + prevMouseX = x; prevMouseY = y; + if (curMode == Blendshapes) { + if (closestVertex.distance > closestVertexThreshold) { + const glm::vec3 origin(0,18,0); + const float sensitivity = 0.003f; + auto camMat = glm::translate(glm::mat4(1), origin + camPos); + auto rotation = glm::rotate(glm::rotate(glm::mat4(1), -dx * sensitivity, {0, 1, 0}), + -dy * sensitivity, {1, 0, 0}); + auto rotAroundOrig = camMat * rotation * glm::translate(glm::mat4(1), origin - camPos); + camPos = rotAroundOrig * glm::vec4(camPos, 0); + camFront = origin - camPos; // face center + } + needToCalculateClosestVertex = true; + } +} + +void passiveMotion(int x, int y) { + if (firstMouse) { + prevMouseX = x; + prevMouseY = y; + firstMouse = false; + } + mouseX = x; mouseY = y; + prevMouseX = x; + prevMouseY = y; +#ifdef ENABLE_MOVEMENT + + int dx = x - prevMouseX, dy = y - prevMouseY; prevMouseX = x; @@ -503,21 +575,22 @@ void motion(int x, int y) { camUp = glm::vec3(0, 1, 0); } #endif - - mouseX = x; mouseY = y; + if (curMode == Blendshapes) needToCalculateClosestVertex = true; - } void mouse(int button, int state, int x, int y) { if (isPanelFocused(controlWindow)) return; + if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) { + if (closestVertex.distance < closestVertexThreshold) { VertIdx idx = { closestVertex.meshIdx, closestVertex.vertIdx }; if (manipulators.count(idx) <= 0) manipulators[idx] = closestVertex.pos; curManipulator = idx; } + } #ifdef ENABLE_MOVEMENT if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) @@ -547,7 +620,7 @@ int main(int argc, char** argv) { glutKeyboardUpFunc(keyboardUp); glutTimerFunc(16, timer, 0); glutMotionFunc(motion); - glutPassiveMotionFunc(motion); + glutPassiveMotionFunc(passiveMotion); glutMouseFunc(mouse); glutMainLoop(); diff --git a/shapes.hpp b/shapes.hpp index 92cc8ad..876ce3c 100644 --- a/shapes.hpp +++ b/shapes.hpp @@ -1,4 +1,6 @@ #include +#include +#include #define GLM_FORCE_PURE #include @@ -24,7 +26,70 @@ constexpr array plane() { }); } -constexpr array cube() { +inline vector cube() { + int i = 0; + vector vertices(36); + + const array front = { + glm::vec3(1, -1, -1), + glm::vec3(-1, -1, -1), + glm::vec3(-1, 1, -1), + glm::vec3(1, 1, -1) + }; + + for (auto v: quadToTriangles(front)) + vertices[i++] = v; + + const array back = { + glm::vec3(1, 1, 1), + glm::vec3(-1, 1, 1), + glm::vec3(-1, -1, 1), + glm::vec3(1, -1, 1) + }; + for (auto v: quadToTriangles(back)) + vertices[i++] = v; + + + const array top = { + glm::vec3(1, 1, -1), + glm::vec3(-1, 1, -1), + glm::vec3(-1, 1, 1), + glm::vec3(1, 1, 1) + }; + for (auto v: quadToTriangles(top)) + vertices[i++] = v; + + const array bottom = { + glm::vec3(1, -1, 1), + glm::vec3(-1, -1, 1), + glm::vec3(-1, -1, -1), + glm::vec3(1, -1, -1) + }; + for (auto v: quadToTriangles(bottom)) + vertices[i++] = v; + + const array left = { + glm::vec3(-1, 1, 1), + glm::vec3(-1, 1, -1), + glm::vec3(-1, -1, -1), + glm::vec3(-1, -1, 1) + }; + for (auto v: quadToTriangles(left)) + vertices[i++] = v; + + const array right = { + glm::vec3(1, 1, -1), + glm::vec3(1, 1, 1), + glm::vec3(1, -1, 1), + glm::vec3(1, -1, -1) + }; + for (auto v: quadToTriangles(right)) + vertices[i++] = v; + + return vertices; +} + +constexpr array cubeArray() { int i = 0; array vertices; @@ -112,7 +177,72 @@ constexpr glm::vec3 pyramid[18] = { glm::vec3(1, -1, 1) }; -// TODO: Add a sphere -/* constexpr std::vector sphere() { */ +// taken from http://www.songho.ca/opengl/gl_sphere.html +#define SECTOR_COUNT 16 +#define STACK_COUNT 16 +inline std::vector sphere(float radius = 1) { + float sectorStep = 2 * M_PI / SECTOR_COUNT; + float stackStep = M_PI / STACK_COUNT; + + std::vector vertices; + + for(int i = 0; i <= STACK_COUNT; i++) { + float stackAngle = M_PI / 2 - i * stackStep; // starting from pi/2 to -pi/2 + float xy = radius * cosf(stackAngle); // r * cos(u) + float z = radius * sinf(stackAngle); // r * sin(u) + + // add (sectorCount+1) vertices per stack + // the first and last vertices have same position and normal, but different tex coords + for(int j = 0; j <= SECTOR_COUNT; j++) + { + float sectorAngle = j * sectorStep; // starting from 0 to 2pi + + // vertex position (x, y, z) + float x = xy * cosf(sectorAngle); // r * cos(u) * cos(v) + float y = xy * sinf(sectorAngle); // r * cos(u) * sin(v) + vertices.push_back({x, y, z}); + + // normalized vertex normal (nx, ny, nz) + /* nx = x * lengthInv; */ + /* ny = y * lengthInv; */ + /* nz = z * lengthInv; */ + /* normals.push_back(nx); */ + /* normals.push_back(ny); */ + /* normals.push_back(nz); */ + + } + } + return vertices; +} + +inline std::vector sphereIndices() { + // generate CCW index list of sphere triangles + vector indices; + int k1, k2; + for(int i = 0; i < STACK_COUNT; ++i) + { + k1 = i * (SECTOR_COUNT + 1); // beginning of current stack + k2 = k1 + SECTOR_COUNT + 1; // beginning of next stack + + for(int j = 0; j < SECTOR_COUNT; ++j, ++k1, ++k2) + { + // 2 triangles per sector excluding first and last stacks + // k1 => k2 => k1+1 + if(i != 0) + { + indices.push_back(k1); + indices.push_back(k2); + indices.push_back(k1 + 1); + } -/* } */ + // k1+1 => k2 => k2+1 + if(i != (STACK_COUNT-1)) + { + indices.push_back(k1 + 1); + indices.push_back(k2); + indices.push_back(k2 + 1); + } + } + } + return indices; +} diff --git a/skybox.cpp b/skybox.cpp index 83f1308..1c99e95 100644 --- a/skybox.cpp +++ b/skybox.cpp @@ -35,7 +35,7 @@ void Skybox::generatePrefilterMap() const { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexId); - setupVertices(prefilterProg.progId, cube()); + setupVertices(prefilterProg.progId, cubeArray()); glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); constexpr GLuint MAX_MIP_LEVELS = 5; @@ -124,7 +124,7 @@ Skybox::Skybox(const Image img): program("skyboxvert.glsl", "skyboxfrag.glsl") { glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // generate vertices - setupVertices(equiProg.progId, cube()); + setupVertices(equiProg.progId, cubeArray()); // render the cube @@ -165,7 +165,7 @@ Skybox::Skybox(const Image img): program("skyboxvert.glsl", "skyboxfrag.glsl") { glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexId); // generate vertices - setupVertices(irradianceProg.progId, cube()); + setupVertices(irradianceProg.progId, cubeArray()); // render irradiance map glViewport(0, 0, 32, 32); @@ -186,7 +186,7 @@ Skybox::Skybox(const Image img): program("skyboxvert.glsl", "skyboxfrag.glsl") { glDepthFunc(GL_LEQUAL); // reverse so facing inside out - vao = setupVertices(program.progId, cube(), true); + vao = setupVertices(program.progId, cubeArray(), true); // restore default framebuffer glBindFramebuffer(GL_FRAMEBUFFER, 0); -- 2.30.2