diff --git a/examples/skinning/README.md b/examples/skinning/README.md new file mode 100644 index 0000000..cd14579 --- /dev/null +++ b/examples/skinning/README.md @@ -0,0 +1,17 @@ +# Simple glTF skinning sample with CPU skinning implementation. + +Example use CPU implementation of skinning for the explanation of how to process skin property in glTF format. + +OpenGL is used to display transformed vertex. + +## Build on Linux and macOS + +``` +$ premake5 gmake +$ make +$ ./bin/native/Debug/skinning simple-skin.gltf +``` + +## Note on asset + +`simple-skin.gltf` is grabbed from gltfTutorial https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_019_SimpleSkin.md diff --git a/examples/skinning/main.cc b/examples/skinning/main.cc new file mode 100644 index 0000000..41b8d22 --- /dev/null +++ b/examples/skinning/main.cc @@ -0,0 +1,883 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define GLFW_INCLUDE_GLU +#include + +#include "trackball.h" + +#define TINYGLTF_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "tiny_gltf.h" + +//#define BUFFER_OFFSET(i) ((char *)NULL + (i)) +#define BUFFER_OFFSET(i) (reinterpret_cast(i)) + +#define CheckGLErrors(desc) \ + { \ + GLenum e = glGetError(); \ + if (e != GL_NO_ERROR) { \ + printf("OpenGL error in \"%s\": %d (%d) %s:%d\n", desc, e, e, __FILE__, \ + __LINE__); \ + exit(20); \ + } \ + } + +#define CAM_Z (3.0f) +int width = 768; +int height = 768; + +double prevMouseX, prevMouseY; +bool mouseLeftPressed; +bool mouseMiddlePressed; +bool mouseRightPressed; +float curr_quat[4]; +float prev_quat[4]; +float eye[3], lookat[3], up[3]; + +GLFWwindow *window; + +typedef struct { GLuint vb; } GLBufferState; + +typedef struct { + std::vector diffuseTex; // for each primitive in mesh +} GLMeshState; + +typedef struct { + std::map attribs; + std::map uniforms; +} GLProgramState; + +typedef struct { + GLuint vb; // vertex buffer + size_t count; // byte count +} GLCurvesState; + +std::map gBufferState; +std::map gMeshState; +std::map gCurvesMesh; +GLProgramState gGLProgramState; + +void CheckErrors(std::string desc) { + GLenum e = glGetError(); + if (e != GL_NO_ERROR) { + fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e); + exit(20); + } +} + +static std::string GetFilePathExtension(const std::string &FileName) { + if (FileName.find_last_of(".") != std::string::npos) + return FileName.substr(FileName.find_last_of(".") + 1); + return ""; +} + +bool LoadShader(GLenum shaderType, // GL_VERTEX_SHADER or GL_FRAGMENT_SHADER(or + // maybe GL_COMPUTE_SHADER) + GLuint &shader, const char *shaderSourceFilename) { + GLint val = 0; + + // free old shader/program + if (shader != 0) { + glDeleteShader(shader); + } + + std::vector srcbuf; + FILE *fp = fopen(shaderSourceFilename, "rb"); + if (!fp) { + fprintf(stderr, "failed to load shader: %s\n", shaderSourceFilename); + return false; + } + fseek(fp, 0, SEEK_END); + size_t len = ftell(fp); + rewind(fp); + srcbuf.resize(len + 1); + len = fread(&srcbuf.at(0), 1, len, fp); + srcbuf[len] = 0; + fclose(fp); + + const GLchar *srcs[1]; + srcs[0] = &srcbuf.at(0); + + shader = glCreateShader(shaderType); + glShaderSource(shader, 1, srcs, NULL); + glCompileShader(shader); + glGetShaderiv(shader, GL_COMPILE_STATUS, &val); + if (val != GL_TRUE) { + char log[4096]; + GLsizei msglen; + glGetShaderInfoLog(shader, 4096, &msglen, log); + printf("%s\n", log); + // assert(val == GL_TRUE && "failed to compile shader"); + printf("ERR: Failed to load or compile shader [ %s ]\n", + shaderSourceFilename); + return false; + } + + printf("Load shader [ %s ] OK\n", shaderSourceFilename); + return true; +} + +bool LinkShader(GLuint &prog, GLuint &vertShader, GLuint &fragShader) { + GLint val = 0; + + if (prog != 0) { + glDeleteProgram(prog); + } + + prog = glCreateProgram(); + + glAttachShader(prog, vertShader); + glAttachShader(prog, fragShader); + glLinkProgram(prog); + + glGetProgramiv(prog, GL_LINK_STATUS, &val); + assert(val == GL_TRUE && "failed to link shader"); + + printf("Link shader OK\n"); + + return true; +} + +void reshapeFunc(GLFWwindow *window, int w, int h) { + (void)window; + int fb_w, fb_h; + glfwGetFramebufferSize(window, &fb_w, &fb_h); + glViewport(0, 0, fb_w, fb_h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(45.0, (float)w / (float)h, 0.1f, 1000.0f); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + width = w; + height = h; +} + +void keyboardFunc(GLFWwindow *window, int key, int scancode, int action, + int mods) { + (void)scancode; + (void)mods; + if (action == GLFW_PRESS || action == GLFW_REPEAT) { + // Close window + if (key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) { + glfwSetWindowShouldClose(window, GL_TRUE); + } + } +} + +void clickFunc(GLFWwindow *window, int button, int action, int mods) { + double x, y; + glfwGetCursorPos(window, &x, &y); + + bool shiftPressed = (mods & GLFW_MOD_SHIFT); + bool ctrlPressed = (mods & GLFW_MOD_CONTROL); + + if ((button == GLFW_MOUSE_BUTTON_LEFT) && (!shiftPressed) && (!ctrlPressed)) { + mouseLeftPressed = true; + mouseMiddlePressed = false; + mouseRightPressed = false; + if (action == GLFW_PRESS) { + int id = -1; + // int id = ui.Proc(x, y); + if (id < 0) { // outside of UI + trackball(prev_quat, 0.0, 0.0, 0.0, 0.0); + } + } else if (action == GLFW_RELEASE) { + mouseLeftPressed = false; + } + } + if ((button == GLFW_MOUSE_BUTTON_RIGHT) || + ((button == GLFW_MOUSE_BUTTON_LEFT) && ctrlPressed)) { + if (action == GLFW_PRESS) { + mouseRightPressed = true; + mouseLeftPressed = false; + mouseMiddlePressed = false; + } else if (action == GLFW_RELEASE) { + mouseRightPressed = false; + } + } + if ((button == GLFW_MOUSE_BUTTON_MIDDLE) || + ((button == GLFW_MOUSE_BUTTON_LEFT) && shiftPressed)) { + if (action == GLFW_PRESS) { + mouseMiddlePressed = true; + mouseLeftPressed = false; + mouseRightPressed = false; + } else if (action == GLFW_RELEASE) { + mouseMiddlePressed = false; + } + } +} + +void motionFunc(GLFWwindow *window, double mouse_x, double mouse_y) { + (void)window; + float rotScale = 1.0f; + float transScale = 2.0f; + + if (mouseLeftPressed) { + trackball(prev_quat, rotScale * (2.0f * prevMouseX - width) / (float)width, + rotScale * (height - 2.0f * prevMouseY) / (float)height, + rotScale * (2.0f * mouse_x - width) / (float)width, + rotScale * (height - 2.0f * mouse_y) / (float)height); + + add_quats(prev_quat, curr_quat, curr_quat); + } else if (mouseMiddlePressed) { + eye[0] += -transScale * (mouse_x - prevMouseX) / (float)width; + lookat[0] += -transScale * (mouse_x - prevMouseX) / (float)width; + eye[1] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[1] += transScale * (mouse_y - prevMouseY) / (float)height; + } else if (mouseRightPressed) { + eye[2] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[2] += transScale * (mouse_y - prevMouseY) / (float)height; + } + + // Update mouse point + prevMouseX = mouse_x; + prevMouseY = mouse_y; +} + +static void SetupMeshState(tinygltf::Model &model, GLuint progId) { + // Buffer + { + for (size_t i = 0; i < model.bufferViews.size(); i++) { + const tinygltf::BufferView &bufferView = model.bufferViews[i]; + if (bufferView.target == 0) { + std::cout << "WARN: bufferView.target is zero" << std::endl; + continue; // Unsupported bufferView. + } + + const tinygltf::Buffer &buffer = model.buffers[bufferView.buffer]; + GLBufferState state; + glGenBuffers(1, &state.vb); + glBindBuffer(bufferView.target, state.vb); + std::cout << "buffer.size= " << buffer.data.size() + << ", byteOffset = " << bufferView.byteOffset << std::endl; + glBufferData(bufferView.target, bufferView.byteLength, + &buffer.data.at(0) + bufferView.byteOffset, GL_STATIC_DRAW); + glBindBuffer(bufferView.target, 0); + + gBufferState[i] = state; + } + } + +#if 0 // TODO(syoyo): Implement + // Texture + { + for (size_t i = 0; i < model.meshes.size(); i++) { + const tinygltf::Mesh &mesh = model.meshes[i]; + + gMeshState[mesh.name].diffuseTex.resize(mesh.primitives.size()); + for (size_t primId = 0; primId < mesh.primitives.size(); primId++) { + const tinygltf::Primitive &primitive = mesh.primitives[primId]; + + gMeshState[mesh.name].diffuseTex[primId] = 0; + + if (primitive.material < 0) { + continue; + } + tinygltf::Material &mat = model.materials[primitive.material]; + // printf("material.name = %s\n", mat.name.c_str()); + if (mat.values.find("diffuse") != mat.values.end()) { + std::string diffuseTexName = mat.values["diffuse"].string_value; + if (model.textures.find(diffuseTexName) != model.textures.end()) { + tinygltf::Texture &tex = model.textures[diffuseTexName]; + if (scene.images.find(tex.source) != model.images.end()) { + tinygltf::Image &image = model.images[tex.source]; + GLuint texId; + glGenTextures(1, &texId); + glBindTexture(tex.target, texId); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameterf(tex.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(tex.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Ignore Texture.fomat. + GLenum format = GL_RGBA; + if (image.component == 3) { + format = GL_RGB; + } + glTexImage2D(tex.target, 0, tex.internalFormat, image.width, + image.height, 0, format, tex.type, + &image.image.at(0)); + + CheckErrors("texImage2D"); + glBindTexture(tex.target, 0); + + printf("TexId = %d\n", texId); + gMeshState[mesh.name].diffuseTex[primId] = texId; + } + } + } + } + } + } +#endif + + glUseProgram(progId); + GLint vtloc = glGetAttribLocation(progId, "in_vertex"); + GLint nrmloc = glGetAttribLocation(progId, "in_normal"); + GLint uvloc = glGetAttribLocation(progId, "in_texcoord"); + + // GLint diffuseTexLoc = glGetUniformLocation(progId, "diffuseTex"); + GLint isCurvesLoc = glGetUniformLocation(progId, "uIsCurves"); + + gGLProgramState.attribs["POSITION"] = vtloc; + gGLProgramState.attribs["NORMAL"] = nrmloc; + gGLProgramState.attribs["TEXCOORD_0"] = uvloc; + // gGLProgramState.uniforms["diffuseTex"] = diffuseTexLoc; + gGLProgramState.uniforms["isCurvesLoc"] = isCurvesLoc; +}; + +#if 0 // TODO(syoyo): Implement +// Setup curves geometry extension +static void SetupCurvesState(tinygltf::Scene &scene, GLuint progId) { + // Find curves primitive. + { + std::map::const_iterator it( + scene.meshes.begin()); + std::map::const_iterator itEnd( + scene.meshes.end()); + + for (; it != itEnd; it++) { + const tinygltf::Mesh &mesh = it->second; + + // Currently we only support one primitive per mesh. + if (mesh.primitives.size() > 1) { + continue; + } + + for (size_t primId = 0; primId < mesh.primitives.size(); primId++) { + const tinygltf::Primitive &primitive = mesh.primitives[primId]; + + gMeshState[mesh.name].diffuseTex[primId] = 0; + + if (primitive.material.empty()) { + continue; + } + + bool has_curves = false; + if (primitive.extras.IsObject()) { + if (primitive.extras.Has("ext_mode")) { + const tinygltf::Value::Object &o = + primitive.extras.Get(); + const tinygltf::Value &ext_mode = o.find("ext_mode")->second; + + if (ext_mode.IsString()) { + const std::string &str = ext_mode.Get(); + if (str.compare("curves") == 0) { + has_curves = true; + } + } + } + } + + if (!has_curves) { + continue; + } + + // Construct curves buffer + const tinygltf::Accessor &vtx_accessor = + scene.accessors[primitive.attributes.find("POSITION")->second]; + const tinygltf::Accessor &nverts_accessor = + scene.accessors[primitive.attributes.find("NVERTS")->second]; + const tinygltf::BufferView &vtx_bufferView = + scene.bufferViews[vtx_accessor.bufferView]; + const tinygltf::BufferView &nverts_bufferView = + scene.bufferViews[nverts_accessor.bufferView]; + const tinygltf::Buffer &vtx_buffer = + scene.buffers[vtx_bufferView.buffer]; + const tinygltf::Buffer &nverts_buffer = + scene.buffers[nverts_bufferView.buffer]; + + // std::cout << "vtx_bufferView = " << vtx_accessor.bufferView << + // std::endl; + // std::cout << "nverts_bufferView = " << nverts_accessor.bufferView << + // std::endl; + // std::cout << "vtx_buffer.size = " << vtx_buffer.data.size() << + // std::endl; + // std::cout << "nverts_buffer.size = " << nverts_buffer.data.size() << + // std::endl; + + const int *nverts = + reinterpret_cast(nverts_buffer.data.data()); + const float *vtx = + reinterpret_cast(vtx_buffer.data.data()); + + // Convert to GL_LINES data. + std::vector line_pts; + size_t vtx_offset = 0; + for (int k = 0; k < static_cast(nverts_accessor.count); k++) { + for (int n = 0; n < nverts[k] - 1; n++) { + + line_pts.push_back(vtx[3 * (vtx_offset + n) + 0]); + line_pts.push_back(vtx[3 * (vtx_offset + n) + 1]); + line_pts.push_back(vtx[3 * (vtx_offset + n) + 2]); + + line_pts.push_back(vtx[3 * (vtx_offset + n + 1) + 0]); + line_pts.push_back(vtx[3 * (vtx_offset + n + 1) + 1]); + line_pts.push_back(vtx[3 * (vtx_offset + n + 1) + 2]); + + // std::cout << "p0 " << vtx[3 * (vtx_offset + n) + 0] << ", " + // << vtx[3 * (vtx_offset + n) + 1] << ", " + // << vtx[3 * (vtx_offset + n) + 2] << std::endl; + + // std::cout << "p1 " << vtx[3 * (vtx_offset + n+1) + 0] << ", " + // << vtx[3 * (vtx_offset + n+1) + 1] << ", " + // << vtx[3 * (vtx_offset + n+1) + 2] << std::endl; + } + + vtx_offset += nverts[k]; + } + + GLCurvesState state; + glGenBuffers(1, &state.vb); + glBindBuffer(GL_ARRAY_BUFFER, state.vb); + glBufferData(GL_ARRAY_BUFFER, line_pts.size() * sizeof(float), + line_pts.data(), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + state.count = line_pts.size() / 3; + gCurvesMesh[mesh.name] = state; + + // Material + tinygltf::Material &mat = scene.materials[primitive.material]; + // printf("material.name = %s\n", mat.name.c_str()); + if (mat.values.find("diffuse") != mat.values.end()) { + std::string diffuseTexName = mat.values["diffuse"].string_value; + if (scene.textures.find(diffuseTexName) != scene.textures.end()) { + tinygltf::Texture &tex = scene.textures[diffuseTexName]; + if (scene.images.find(tex.source) != scene.images.end()) { + tinygltf::Image &image = scene.images[tex.source]; + GLuint texId; + glGenTextures(1, &texId); + glBindTexture(tex.target, texId); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameterf(tex.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(tex.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Ignore Texture.fomat. + GLenum format = GL_RGBA; + if (image.component == 3) { + format = GL_RGB; + } + glTexImage2D(tex.target, 0, tex.internalFormat, image.width, + image.height, 0, format, tex.type, + &image.image.at(0)); + + CheckErrors("texImage2D"); + glBindTexture(tex.target, 0); + + printf("TexId = %d\n", texId); + gMeshState[mesh.name].diffuseTex[primId] = texId; + } + } + } + } + } + } + + glUseProgram(progId); + GLint vtloc = glGetAttribLocation(progId, "in_vertex"); + GLint nrmloc = glGetAttribLocation(progId, "in_normal"); + GLint uvloc = glGetAttribLocation(progId, "in_texcoord"); + + GLint diffuseTexLoc = glGetUniformLocation(progId, "diffuseTex"); + GLint isCurvesLoc = glGetUniformLocation(progId, "uIsCurves"); + + gGLProgramState.attribs["POSITION"] = vtloc; + gGLProgramState.attribs["NORMAL"] = nrmloc; + gGLProgramState.attribs["TEXCOORD_0"] = uvloc; + gGLProgramState.uniforms["diffuseTex"] = diffuseTexLoc; + gGLProgramState.uniforms["uIsCurves"] = isCurvesLoc; +}; +#endif + +static void DrawMesh(tinygltf::Model &model, const tinygltf::Mesh &mesh) { + //// Skip curves primitive. + // if (gCurvesMesh.find(mesh.name) != gCurvesMesh.end()) { + // return; + //} + + // if (gGLProgramState.uniforms["diffuseTex"] >= 0) { + // glUniform1i(gGLProgramState.uniforms["diffuseTex"], 0); // TEXTURE0 + //} + + if (gGLProgramState.uniforms["isCurvesLoc"] >= 0) { + glUniform1i(gGLProgramState.uniforms["isCurvesLoc"], 0); + } + + for (size_t i = 0; i < mesh.primitives.size(); i++) { + const tinygltf::Primitive &primitive = mesh.primitives[i]; + + if (primitive.indices < 0) return; + + // Assume TEXTURE_2D target for the texture object. + // glBindTexture(GL_TEXTURE_2D, gMeshState[mesh.name].diffuseTex[i]); + + std::map::const_iterator it(primitive.attributes.begin()); + std::map::const_iterator itEnd( + primitive.attributes.end()); + + for (; it != itEnd; it++) { + assert(it->second >= 0); + const tinygltf::Accessor &accessor = model.accessors[it->second]; + glBindBuffer(GL_ARRAY_BUFFER, gBufferState[accessor.bufferView].vb); + CheckErrors("bind buffer"); + int size = 1; + if (accessor.type == TINYGLTF_TYPE_SCALAR) { + size = 1; + } else if (accessor.type == TINYGLTF_TYPE_VEC2) { + size = 2; + } else if (accessor.type == TINYGLTF_TYPE_VEC3) { + size = 3; + } else if (accessor.type == TINYGLTF_TYPE_VEC4) { + size = 4; + } else { + assert(0); + } + // it->first would be "POSITION", "NORMAL", "TEXCOORD_0", ... + if ((it->first.compare("POSITION") == 0) || + (it->first.compare("NORMAL") == 0) || + (it->first.compare("TEXCOORD_0") == 0)) { + if (gGLProgramState.attribs[it->first] >= 0) { + // Compute byteStride from Accessor + BufferView combination. + int byteStride = accessor.ByteStride(model.bufferViews[accessor.bufferView]); + assert(byteStride != -1); + glVertexAttribPointer(gGLProgramState.attribs[it->first], size, + accessor.componentType, accessor.normalized ? GL_TRUE : GL_FALSE, + byteStride, + BUFFER_OFFSET(accessor.byteOffset)); + CheckErrors("vertex attrib pointer"); + glEnableVertexAttribArray(gGLProgramState.attribs[it->first]); + CheckErrors("enable vertex attrib array"); + } + } + } + + const tinygltf::Accessor &indexAccessor = + model.accessors[primitive.indices]; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, + gBufferState[indexAccessor.bufferView].vb); + CheckErrors("bind buffer"); + int mode = -1; + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + mode = GL_TRIANGLES; + } else if (primitive.mode == TINYGLTF_MODE_TRIANGLE_STRIP) { + mode = GL_TRIANGLE_STRIP; + } else if (primitive.mode == TINYGLTF_MODE_TRIANGLE_FAN) { + mode = GL_TRIANGLE_FAN; + } else if (primitive.mode == TINYGLTF_MODE_POINTS) { + mode = GL_POINTS; + } else if (primitive.mode == TINYGLTF_MODE_LINE) { + mode = GL_LINES; + } else if (primitive.mode == TINYGLTF_MODE_LINE_LOOP) { + mode = GL_LINE_LOOP; + } else { + assert(0); + } + glDrawElements(mode, indexAccessor.count, indexAccessor.componentType, + BUFFER_OFFSET(indexAccessor.byteOffset)); + CheckErrors("draw elements"); + + { + std::map::const_iterator it( + primitive.attributes.begin()); + std::map::const_iterator itEnd( + primitive.attributes.end()); + + for (; it != itEnd; it++) { + if ((it->first.compare("POSITION") == 0) || + (it->first.compare("NORMAL") == 0) || + (it->first.compare("TEXCOORD_0") == 0)) { + if (gGLProgramState.attribs[it->first] >= 0) { + glDisableVertexAttribArray(gGLProgramState.attribs[it->first]); + } + } + } + } + } +} + +#if 0 // TODO(syoyo): Implement +static void DrawCurves(tinygltf::Scene &scene, const tinygltf::Mesh &mesh) { + (void)scene; + + if (gCurvesMesh.find(mesh.name) == gCurvesMesh.end()) { + return; + } + + if (gGLProgramState.uniforms["isCurvesLoc"] >= 0) { + glUniform1i(gGLProgramState.uniforms["isCurvesLoc"], 1); + } + + GLCurvesState &state = gCurvesMesh[mesh.name]; + + if (gGLProgramState.attribs["POSITION"] >= 0) { + glBindBuffer(GL_ARRAY_BUFFER, state.vb); + glVertexAttribPointer(gGLProgramState.attribs["POSITION"], 3, GL_FLOAT, + GL_FALSE, /* stride */ 0, BUFFER_OFFSET(0)); + CheckErrors("curve: vertex attrib pointer"); + glEnableVertexAttribArray(gGLProgramState.attribs["POSITION"]); + CheckErrors("curve: enable vertex attrib array"); + } + + glDrawArrays(GL_LINES, 0, state.count); + + if (gGLProgramState.attribs["POSITION"] >= 0) { + glDisableVertexAttribArray(gGLProgramState.attribs["POSITION"]); + } +} +#endif + +// Hierarchically draw nodes +static void DrawNode(tinygltf::Model &model, const tinygltf::Node &node) { + // Apply xform + + glPushMatrix(); + if (node.matrix.size() == 16) { + // Use `matrix' attribute + glMultMatrixd(node.matrix.data()); + } else { + // Assume Trans x Rotate x Scale order + if (node.scale.size() == 3) { + glScaled(node.scale[0], node.scale[1], node.scale[2]); + } + + if (node.rotation.size() == 4) { + glRotated(node.rotation[0], node.rotation[1], node.rotation[2], + node.rotation[3]); + } + + if (node.translation.size() == 3) { + glTranslated(node.translation[0], node.translation[1], + node.translation[2]); + } + } + + // std::cout << "node " << node.name << ", Meshes " << node.meshes.size() << + // std::endl; + + // std::cout << it->first << std::endl; + // FIXME(syoyo): Refactor. + // DrawCurves(scene, it->second); + DrawMesh(model, model.meshes[node.mesh]); + + // Draw child nodes. + for (size_t i = 0; i < node.children.size(); i++) { + DrawNode(model, model.nodes[node.children[i]]); + } + + glPopMatrix(); +} + +static void DrawModel(tinygltf::Model &model, int scene_idx) { +#if 0 + std::map::const_iterator it(scene.meshes.begin()); + std::map::const_iterator itEnd(scene.meshes.end()); + + for (; it != itEnd; it++) { + DrawMesh(scene, it->second); + DrawCurves(scene, it->second); + } +#else + + const tinygltf::Scene &scene = model.scenes[scene_idx]; + for (size_t i = 0; i < scene.nodes.size(); i++) { + DrawNode(model, model.nodes[scene.nodes[i]]); + } +#endif +} + +static void Init() { + trackball(curr_quat, 0, 0, 0, 0); + + eye[0] = 0.0f; + eye[1] = 0.0f; + eye[2] = CAM_Z; + + lookat[0] = 0.0f; + lookat[1] = 0.0f; + lookat[2] = 0.0f; + + up[0] = 0.0f; + up[1] = 1.0f; + up[2] = 0.0f; +} + +static void PrintNodes(const tinygltf::Scene &scene) { + for (size_t i = 0; i < scene.nodes.size(); i++) { + std::cout << "node.name : " << scene.nodes[i] << std::endl; + } +} + +int main(int argc, char **argv) { + if (argc < 2) { + std::cout << "glview input.gltf \n" << std::endl; + return 0; + } + + float scale = 1.0f; + if (argc > 2) { + scale = atof(argv[2]); + } + + tinygltf::Model model; + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + std::string input_filename(argv[1]); + std::string ext = GetFilePathExtension(input_filename); + + bool ret = false; + if (ext.compare("glb") == 0) { + // assume binary glTF. + ret = loader.LoadBinaryFromFile(&model, &err, &warn, input_filename.c_str()); + } else { + // assume ascii glTF. + ret = loader.LoadASCIIFromFile(&model, &err, &warn, input_filename.c_str()); + } + + if (!warn.empty()) { + printf("Warn: %s\n", warn.c_str()); + } + + if (!err.empty()) { + printf("ERR: %s\n", err.c_str()); + } + if (!ret) { + printf("Failed to load .glTF : %s\n", argv[1]); + exit(-1); + } + + Init(); + + if (model.scenes.empty()) { + std::cerr << "Scene is empty" << std::endl; + return EXIT_FAILURE; + } + + std::cout << "defaultScene = " << model.defaultScene << std::endl; + if (model.defaultScene >= int(model.scenes.size())) { + std::cerr << "Invalid defualtScene value : " << model.defaultScene << std::endl; + return EXIT_FAILURE; + } + + int scene_idx = model.defaultScene; + if (scene_idx == -1) { + // Use the first scene. + scene_idx = 0; + } + + // DBG + PrintNodes(model.scenes[scene_idx]); + + if (!glfwInit()) { + std::cerr << "Failed to initialize GLFW." << std::endl; + return -1; + } + + char title[1024]; + sprintf(title, "Simple glTF viewer: %s", input_filename.c_str()); + + window = glfwCreateWindow(width, height, title, NULL, NULL); + if (window == NULL) { + std::cerr << "Failed to open GLFW window. " << std::endl; + glfwTerminate(); + return 1; + } + + glfwGetWindowSize(window, &width, &height); + + glfwMakeContextCurrent(window); + + // Callback + glfwSetWindowSizeCallback(window, reshapeFunc); + glfwSetKeyCallback(window, keyboardFunc); + glfwSetMouseButtonCallback(window, clickFunc); + glfwSetCursorPosCallback(window, motionFunc); + + glewExperimental = true; // This may be only true for linux environment. + if (glewInit() != GLEW_OK) { + std::cerr << "Failed to initialize GLEW." << std::endl; + return -1; + } + + reshapeFunc(window, width, height); + + GLuint vertId = 0, fragId = 0, progId = 0; + if (false == LoadShader(GL_VERTEX_SHADER, vertId, "shader.vert")) { + return -1; + } + CheckErrors("load vert shader"); + + if (false == LoadShader(GL_FRAGMENT_SHADER, fragId, "shader.frag")) { + return -1; + } + CheckErrors("load frag shader"); + + if (false == LinkShader(progId, vertId, fragId)) { + return -1; + } + + CheckErrors("link"); + + { + // At least `in_vertex` should be used in the shader. + GLint vtxLoc = glGetAttribLocation(progId, "in_vertex"); + if (vtxLoc < 0) { + printf("vertex loc not found.\n"); + exit(-1); + } + } + + glUseProgram(progId); + CheckErrors("useProgram"); + + SetupMeshState(model, progId); + // SetupCurvesState(model, progId); + CheckErrors("SetupGLState"); + + std::cout << "# of meshes = " << model.meshes.size() << std::endl; + + while (glfwWindowShouldClose(window) == GL_FALSE) { + glfwPollEvents(); + glClearColor(0.1f, 0.2f, 0.3f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glEnable(GL_DEPTH_TEST); + + GLfloat mat[4][4]; + build_rotmatrix(mat, curr_quat); + + // camera(define it in projection matrix) + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], + up[1], up[2]); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glMultMatrixf(&mat[0][0]); + + glScalef(scale, scale, scale); + + DrawModel(model, scene_idx); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + + glFlush(); + + glfwSwapBuffers(window); + } + + glfwTerminate(); +} diff --git a/examples/skinning/premake5.lua b/examples/skinning/premake5.lua new file mode 100644 index 0000000..4233f29 --- /dev/null +++ b/examples/skinning/premake5.lua @@ -0,0 +1,59 @@ +newoption { + trigger = "asan", + description = "Enable Address Sanitizer(gcc5+ ang clang only)" +} + +solution "skinning" + -- location ( "build" ) + configurations { "Debug", "Release" } + platforms {"native", "x64", "x32"} + + project "skinning" + + -- Use clang for better asan expericen + if _OPTIONS["asan"] then + toolset "clang" + end + + kind "ConsoleApp" + language "C++" + cppdialect "C++11" + files { "main.cc", "skinning.cc", "trackball.cc" } + includedirs { "./" } + includedirs { "../../" } + + configuration { "linux" } + + if _OPTIONS["asan"] then + buildoptions { "-fsanitize=address" } + linkoptions { "-fsanitize=address" } + end + + linkoptions { "`pkg-config --libs glfw3`" } + links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } + + configuration { "windows" } + -- Edit path to glew and GLFW3 fit to your environment. + includedirs { "../../../../local/glew-1.13.0/include/" } + includedirs { "../../../../local/glfw-3.2.bin.WIN32/include/" } + libdirs { "../../../../local/glew-1.13.0/lib/Release/Win32/" } + libdirs { "../../../../local/glfw-3.2.bin.WIN32/lib-vc2013/" } + links { "glfw3", "gdi32", "winmm", "user32", "glew32", "glu32","opengl32", "kernel32" } + defines { "_CRT_SECURE_NO_WARNINGS" } + + configuration { "macosx" } + includedirs { "/usr/local/include" } + buildoptions { "-Wno-deprecated-declarations" } + libdirs { "/usr/local/lib" } + links { "glfw3", "GLEW" } + linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" } + + configuration "Debug" + defines { "DEBUG" } + symbols "On" + warnings "Extra" + + configuration "Release" + defines { "NDEBUG" } + optimize "On" + warnings "Extra" diff --git a/examples/skinning/shader.frag b/examples/skinning/shader.frag new file mode 100644 index 0000000..05d28ce --- /dev/null +++ b/examples/skinning/shader.frag @@ -0,0 +1,16 @@ +uniform sampler2D diffuseTex; +uniform int uIsCurve; + +varying vec3 normal; +varying vec2 texcoord; + +void main(void) +{ + //gl_FragColor = vec4(0.5 * normalize(normal) + 0.5, 1.0); + //gl_FragColor = vec4(texcoord, 0.0, 1.0); + if (uIsCurve > 0) { + gl_FragColor = texture2D(diffuseTex, texcoord); + } else { + gl_FragColor = vec4(0.5 * normalize(normal) + 0.5, 1.0); + } +} diff --git a/examples/skinning/shader.vert b/examples/skinning/shader.vert new file mode 100644 index 0000000..e21752d --- /dev/null +++ b/examples/skinning/shader.vert @@ -0,0 +1,16 @@ +attribute vec3 in_vertex; +attribute vec3 in_normal; +attribute vec2 in_texcoord; + +varying vec3 normal; +varying vec2 texcoord; + +void main(void) +{ + vec4 p = gl_ModelViewProjectionMatrix * vec4(in_vertex, 1); + gl_Position = p; + vec4 nn = gl_ModelViewMatrixInverseTranspose * vec4(normalize(in_normal), 0); + normal = nn.xyz; + + texcoord = in_texcoord; +} diff --git a/examples/skinning/simple-skin.gltf b/examples/skinning/simple-skin.gltf new file mode 100644 index 0000000..2da6ddc --- /dev/null +++ b/examples/skinning/simple-skin.gltf @@ -0,0 +1,148 @@ +{ + "scenes" : [ { + "nodes" : [ 0 ] + } ], + + "nodes" : [ { + "skin" : 0, + "mesh" : 0, + "children" : [ 1 ] + }, { + "children" : [ 2 ], + "translation" : [ 0.0, 1.0, 0.0 ] + }, { + "rotation" : [ 0.0, 0.0, 0.0, 1.0 ] + } ], + + "meshes" : [ { + "primitives" : [ { + "attributes" : { + "POSITION" : 1, + "JOINTS_0" : 2, + "WEIGHTS_0" : 3 + }, + "indices" : 0 + } ] + } ], + + "skins" : [ { + "inverseBindMatrices" : 4, + "joints" : [ 1, 2 ] + } ], + + "animations" : [ { + "channels" : [ { + "sampler" : 0, + "target" : { + "node" : 2, + "path" : "rotation" + } + } ], + "samplers" : [ { + "input" : 5, + "interpolation" : "LINEAR", + "output" : 6 + } ] + } ], + + "buffers" : [ { + "uri" : "data:application/gltf-buffer;base64,AAABAAMAAAADAAIAAgADAAUAAgAFAAQABAAFAAcABAAHAAYABgAHAAkABgAJAAgAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAD8AAAAAAACAPwAAAD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAwD8AAAAAAACAPwAAwD8AAAAAAAAAAAAAAEAAAAAAAACAPwAAAEAAAAAA", + "byteLength" : 168 + }, { + "uri" : "data:application/gltf-buffer;base64,AAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAQD8AAIA+AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAAAAgD4AAEA/AAAAAAAAAAAAAIA+AABAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAA=", + "byteLength" : 320 + }, { + "uri" : "data:application/gltf-buffer;base64,AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAvwAAgL8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAL8AAIC/AAAAAAAAgD8=", + "byteLength" : 128 + }, { + "uri" : "data:application/gltf-buffer;base64,AAAAAAAAAD8AAIA/AADAPwAAAEAAACBAAABAQAAAYEAAAIBAAACQQAAAoEAAALBAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAPT9ND/0/TQ/AAAAAAAAAAD0/TQ/9P00PwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAPT9NL/0/TQ/AAAAAAAAAAD0/TS/9P00PwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAAAAAAAAAIA/", + "byteLength" : 240 + } ], + + "bufferViews" : [ { + "buffer" : 0, + "byteOffset" : 0, + "byteLength" : 48, + "target" : 34963 + }, { + "buffer" : 0, + "byteOffset" : 48, + "byteLength" : 120, + "target" : 34962 + }, { + "buffer" : 1, + "byteOffset" : 0, + "byteLength" : 320, + "byteStride" : 16 + }, { + "buffer" : 2, + "byteOffset" : 0, + "byteLength" : 128 + }, { + "buffer" : 3, + "byteOffset" : 0, + "byteLength" : 240 + } ], + + "accessors" : [ { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 24, + "type" : "SCALAR", + "max" : [ 9 ], + "min" : [ 0 ] + }, { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 10, + "type" : "VEC3", + "max" : [ 1.0, 2.0, 0.0 ], + "min" : [ 0.0, 0.0, 0.0 ] + }, { + "bufferView" : 2, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 10, + "type" : "VEC4", + "max" : [ 0, 1, 0, 0 ], + "min" : [ 0, 1, 0, 0 ] + }, { + "bufferView" : 2, + "byteOffset" : 160, + "componentType" : 5126, + "count" : 10, + "type" : "VEC4", + "max" : [ 1.0, 1.0, 0.0, 0.0 ], + "min" : [ 0.0, 0.0, 0.0, 0.0 ] + }, { + "bufferView" : 3, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 2, + "type" : "MAT4", + "max" : [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -0.5, -1.0, 0.0, 1.0 ], + "min" : [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -0.5, -1.0, 0.0, 1.0 ] + }, { + "bufferView" : 4, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 12, + "type" : "SCALAR", + "max" : [ 5.5 ], + "min" : [ 0.0 ] + }, { + "bufferView" : 4, + "byteOffset" : 48, + "componentType" : 5126, + "count" : 12, + "type" : "VEC4", + "max" : [ 0.0, 0.0, 0.707, 1.0 ], + "min" : [ 0.0, 0.0, -0.707, 0.707 ] + } ], + + "asset" : { + "version" : "2.0" + } +} diff --git a/examples/skinning/skinning.cc b/examples/skinning/skinning.cc new file mode 100644 index 0000000..e5d5a5f --- /dev/null +++ b/examples/skinning/skinning.cc @@ -0,0 +1,9 @@ + +#if 0 + +jointMatrix(j) = + globalTransform^-1 * // The "^-1" here means the inverse of this transform + globalJointTransform * + inverseBindMatrix(j); + +#endif diff --git a/examples/skinning/trackball.cc b/examples/skinning/trackball.cc new file mode 100644 index 0000000..86ff3b3 --- /dev/null +++ b/examples/skinning/trackball.cc @@ -0,0 +1,292 @@ +/* + * (c) Copyright 1993, 1994, Silicon Graphics, Inc. + * ALL RIGHTS RESERVED + * Permission to use, copy, modify, and distribute this software for + * any purpose and without fee is hereby granted, provided that the above + * copyright notice appear in all copies and that both the copyright notice + * and this permission notice appear in supporting documentation, and that + * the name of Silicon Graphics, Inc. not be used in advertising + * or publicity pertaining to distribution of the software without specific, + * written prior permission. + * + * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" + * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON + * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, + * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY + * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, + * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF + * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE + * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. + * + * US Government Users Restricted Rights + * Use, duplication, or disclosure by the Government is subject to + * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph + * (c)(1)(ii) of the Rights in Technical Data and Computer Software + * clause at DFARS 252.227-7013 and/or in similar or successor + * clauses in the FAR or the DOD or NASA FAR Supplement. + * Unpublished-- rights reserved under the copyright laws of the + * United States. Contractor/manufacturer is Silicon Graphics, + * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + * + * OpenGL(TM) is a trademark of Silicon Graphics, Inc. + */ +/* + * Trackball code: + * + * Implementation of a virtual trackball. + * Implemented by Gavin Bell, lots of ideas from Thant Tessman and + * the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129. + * + * Vector manip code: + * + * Original code from: + * David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli + * + * Much mucking with by: + * Gavin Bell + */ +#include +#include "trackball.h" + +/* + * This size should really be based on the distance from the center of + * rotation to the point on the object underneath the mouse. That + * point would then track the mouse as closely as possible. This is a + * simple example, though, so that is left as an Exercise for the + * Programmer. + */ +#define TRACKBALLSIZE (0.8) + +/* + * Local function prototypes (not defined in trackball.h) + */ +static float tb_project_to_sphere(float, float, float); +static void normalize_quat(float[4]); + +static void vzero(float *v) { + v[0] = 0.0; + v[1] = 0.0; + v[2] = 0.0; +} + +static void vset(float *v, float x, float y, float z) { + v[0] = x; + v[1] = y; + v[2] = z; +} + +static void vsub(const float *src1, const float *src2, float *dst) { + dst[0] = src1[0] - src2[0]; + dst[1] = src1[1] - src2[1]; + dst[2] = src1[2] - src2[2]; +} + +static void vcopy(const float *v1, float *v2) { + register int i; + for (i = 0; i < 3; i++) + v2[i] = v1[i]; +} + +static void vcross(const float *v1, const float *v2, float *cross) { + float temp[3]; + + temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]); + temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]); + temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]); + vcopy(temp, cross); +} + +static float vlength(const float *v) { + return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); +} + +static void vscale(float *v, float div) { + v[0] *= div; + v[1] *= div; + v[2] *= div; +} + +static void vnormal(float *v) { vscale(v, 1.0 / vlength(v)); } + +static float vdot(const float *v1, const float *v2) { + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; +} + +static void vadd(const float *src1, const float *src2, float *dst) { + dst[0] = src1[0] + src2[0]; + dst[1] = src1[1] + src2[1]; + dst[2] = src1[2] + src2[2]; +} + +/* + * Ok, simulate a track-ball. Project the points onto the virtual + * trackball, then figure out the axis of rotation, which is the cross + * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0) + * Note: This is a deformed trackball-- is a trackball in the center, + * but is deformed into a hyperbolic sheet of rotation away from the + * center. This particular function was chosen after trying out + * several variations. + * + * It is assumed that the arguments to this routine are in the range + * (-1.0 ... 1.0) + */ +void trackball(float q[4], float p1x, float p1y, float p2x, float p2y) { + float a[3]; /* Axis of rotation */ + float phi; /* how much to rotate about axis */ + float p1[3], p2[3], d[3]; + float t; + + if (p1x == p2x && p1y == p2y) { + /* Zero rotation */ + vzero(q); + q[3] = 1.0; + return; + } + + /* + * First, figure out z-coordinates for projection of P1 and P2 to + * deformed sphere + */ + vset(p1, p1x, p1y, tb_project_to_sphere(TRACKBALLSIZE, p1x, p1y)); + vset(p2, p2x, p2y, tb_project_to_sphere(TRACKBALLSIZE, p2x, p2y)); + + /* + * Now, we want the cross product of P1 and P2 + */ + vcross(p2, p1, a); + + /* + * Figure out how much to rotate around that axis. + */ + vsub(p1, p2, d); + t = vlength(d) / (2.0 * TRACKBALLSIZE); + + /* + * Avoid problems with out-of-control values... + */ + if (t > 1.0) + t = 1.0; + if (t < -1.0) + t = -1.0; + phi = 2.0 * asin(t); + + axis_to_quat(a, phi, q); +} + +/* + * Given an axis and angle, compute quaternion. + */ +void axis_to_quat(float a[3], float phi, float q[4]) { + vnormal(a); + vcopy(a, q); + vscale(q, sin(phi / 2.0)); + q[3] = cos(phi / 2.0); +} + +/* + * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet + * if we are away from the center of the sphere. + */ +static float tb_project_to_sphere(float r, float x, float y) { + float d, t, z; + + d = sqrt(x * x + y * y); + if (d < r * 0.70710678118654752440) { /* Inside sphere */ + z = sqrt(r * r - d * d); + } else { /* On hyperbola */ + t = r / 1.41421356237309504880; + z = t * t / d; + } + return z; +} + +/* + * Given two rotations, e1 and e2, expressed as quaternion rotations, + * figure out the equivalent single rotation and stuff it into dest. + * + * This routine also normalizes the result every RENORMCOUNT times it is + * called, to keep error from creeping in. + * + * NOTE: This routine is written so that q1 or q2 may be the same + * as dest (or each other). + */ + +#define RENORMCOUNT 97 + +void add_quats(float q1[4], float q2[4], float dest[4]) { + static int count = 0; + float t1[4], t2[4], t3[4]; + float tf[4]; + + vcopy(q1, t1); + vscale(t1, q2[3]); + + vcopy(q2, t2); + vscale(t2, q1[3]); + + vcross(q2, q1, t3); + vadd(t1, t2, tf); + vadd(t3, tf, tf); + tf[3] = q1[3] * q2[3] - vdot(q1, q2); + + dest[0] = tf[0]; + dest[1] = tf[1]; + dest[2] = tf[2]; + dest[3] = tf[3]; + + if (++count > RENORMCOUNT) { + count = 0; + normalize_quat(dest); + } +} + +/* + * Quaternions always obey: a^2 + b^2 + c^2 + d^2 = 1.0 + * If they don't add up to 1.0, dividing by their magnitued will + * renormalize them. + * + * Note: See the following for more information on quaternions: + * + * - Shoemake, K., Animating rotation with quaternion curves, Computer + * Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985. + * - Pletinckx, D., Quaternion calculus as a basic tool in computer + * graphics, The Visual Computer 5, 2-13, 1989. + */ +static void normalize_quat(float q[4]) { + int i; + float mag; + + mag = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]); + for (i = 0; i < 4; i++) + q[i] /= mag; +} + +/* + * Build a rotation matrix, given a quaternion rotation. + * + */ +void build_rotmatrix(float m[4][4], const float q[4]) { + m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]); + m[0][1] = 2.0 * (q[0] * q[1] - q[2] * q[3]); + m[0][2] = 2.0 * (q[2] * q[0] + q[1] * q[3]); + m[0][3] = 0.0; + + m[1][0] = 2.0 * (q[0] * q[1] + q[2] * q[3]); + m[1][1] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]); + m[1][2] = 2.0 * (q[1] * q[2] - q[0] * q[3]); + m[1][3] = 0.0; + + m[2][0] = 2.0 * (q[2] * q[0] - q[1] * q[3]); + m[2][1] = 2.0 * (q[1] * q[2] + q[0] * q[3]); + m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]); + m[2][3] = 0.0; + + m[3][0] = 0.0; + m[3][1] = 0.0; + m[3][2] = 0.0; + m[3][3] = 1.0; +} diff --git a/examples/skinning/trackball.h b/examples/skinning/trackball.h new file mode 100644 index 0000000..b1f9437 --- /dev/null +++ b/examples/skinning/trackball.h @@ -0,0 +1,75 @@ +/* + * (c) Copyright 1993, 1994, Silicon Graphics, Inc. + * ALL RIGHTS RESERVED + * Permission to use, copy, modify, and distribute this software for + * any purpose and without fee is hereby granted, provided that the above + * copyright notice appear in all copies and that both the copyright notice + * and this permission notice appear in supporting documentation, and that + * the name of Silicon Graphics, Inc. not be used in advertising + * or publicity pertaining to distribution of the software without specific, + * written prior permission. + * + * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" + * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON + * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, + * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY + * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, + * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF + * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE + * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. + * + * US Government Users Restricted Rights + * Use, duplication, or disclosure by the Government is subject to + * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph + * (c)(1)(ii) of the Rights in Technical Data and Computer Software + * clause at DFARS 252.227-7013 and/or in similar or successor + * clauses in the FAR or the DOD or NASA FAR Supplement. + * Unpublished-- rights reserved under the copyright laws of the + * United States. Contractor/manufacturer is Silicon Graphics, + * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + * + * OpenGL(TM) is a trademark of Silicon Graphics, Inc. + */ +/* + * trackball.h + * A virtual trackball implementation + * Written by Gavin Bell for Silicon Graphics, November 1988. + */ + +/* + * Pass the x and y coordinates of the last and current positions of + * the mouse, scaled so they are from (-1.0 ... 1.0). + * + * The resulting rotation is returned as a quaternion rotation in the + * first paramater. + */ +void trackball(float q[4], float p1x, float p1y, float p2x, float p2y); + +void negate_quat(float *q, float *qn); + +/* + * Given two quaternions, add them together to get a third quaternion. + * Adding quaternions to get a compound rotation is analagous to adding + * translations to get a compound translation. When incrementally + * adding rotations, the first argument here should be the new + * rotation, the second and third the total rotation (which will be + * over-written with the resulting new total rotation). + */ +void add_quats(float *q1, float *q2, float *dest); + +/* + * A useful function, builds a rotation matrix in Matrix based on + * given quaternion. + */ +void build_rotmatrix(float m[4][4], const float q[4]); + +/* + * This function computes a quaternion based on an axis (defined by + * the given vector) and an angle about which to rotate. The angle is + * expressed in radians. The result is put into the third argument. + */ +void axis_to_quat(float a[3], float phi, float q[4]);