diff --git a/examples/glview/README.md b/examples/glview/README.md index de1f25f..bb6194c 100644 --- a/examples/glview/README.md +++ b/examples/glview/README.md @@ -15,10 +15,15 @@ Simple OpenGL viewer for glTF geometry. > premake4 gmake $ make -### Windows(not tested) +### Windows(not tested well) + +Edit glew and glfw path in `premake4.lua`, then > premake5.exe vs2013 - Open .sln in Visual Studio 2013 + +Open .sln in Visual Studio 2013 + +When running .exe, glew and glfw dll must exist in the working directory. ## TODO diff --git a/examples/glview/glview.cc b/examples/glview/glview.cc index 3002815..a9abe16 100644 --- a/examples/glview/glview.cc +++ b/examples/glview/glview.cc @@ -142,7 +142,9 @@ bool LinkShader(GLuint &prog, GLuint &vertShader, GLuint &fragShader) { void reshapeFunc(GLFWwindow *window, int w, int h) { (void)window; - glViewport(0, 0, w, h); + 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); @@ -235,6 +237,7 @@ static void SetupGLState(tinygltf::Scene &scene, GLuint progId) { for (; it != itEnd; it++) { const tinygltf::BufferView &bufferView = it->second; if (bufferView.target == 0) { + std::cout << "WARN: bufferView.target is zero" << std::endl; continue; // Unsupported bufferView. } @@ -242,6 +245,7 @@ static void SetupGLState(tinygltf::Scene &scene, GLuint progId) { 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); @@ -349,17 +353,22 @@ void DrawMesh(tinygltf::Scene &scene, const tinygltf::Mesh &mesh) { count = 3; } else if (accessor.type == TINYGLTF_TYPE_VEC4) { count = 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)) { - glVertexAttribPointer( - gGLProgramState.attribs[it->first], count, accessor.componentType, - GL_FALSE, accessor.byteStride, BUFFER_OFFSET(accessor.byteOffset)); - CheckErrors("vertex attrib pointer"); - glEnableVertexAttribArray(gGLProgramState.attribs[it->first]); - CheckErrors("enable vertex attrib array"); + + if (gGLProgramState.attribs[it->first] >= 0) { + glVertexAttribPointer( + gGLProgramState.attribs[it->first], count, accessor.componentType, + GL_FALSE, accessor.byteStride, BUFFER_OFFSET(accessor.byteOffset)); + CheckErrors("vertex attrib pointer"); + glEnableVertexAttribArray(gGLProgramState.attribs[it->first]); + CheckErrors("enable vertex attrib array"); + } } } @@ -380,7 +389,9 @@ void DrawMesh(tinygltf::Scene &scene, const tinygltf::Mesh &mesh) { 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"); @@ -395,7 +406,9 @@ void DrawMesh(tinygltf::Scene &scene, const tinygltf::Mesh &mesh) { if ((it->first.compare("POSITION") == 0) || (it->first.compare("NORMAL") == 0) || (it->first.compare("TEXCOORD_0") == 0)) { - glDisableVertexAttribArray(gGLProgramState.attribs[it->first]); + if (gGLProgramState.attribs[it->first] >= 0) { + glDisableVertexAttribArray(gGLProgramState.attribs[it->first]); + } } } } @@ -489,7 +502,7 @@ int main(int argc, char **argv) { glfwSetMouseButtonCallback(window, clickFunc); glfwSetCursorPosCallback(window, motionFunc); - glewExperimental = true; + glewExperimental = true; // This may be only true for linux environment. if (glewInit() != GLEW_OK) { std::cerr << "Failed to initialize GLEW." << std::endl; return -1; @@ -515,17 +528,12 @@ int main(int argc, char **argv) { 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); } - - GLint tnLoc = glGetAttribLocation(progId, "in_normal"); - if (tnLoc < 0) { - printf("normal loc not found.\n"); - exit(-1); - } } glUseProgram(progId); @@ -534,6 +542,8 @@ int main(int argc, char **argv) { SetupGLState(scene, progId); CheckErrors("SetupGLState"); + std::cout << "# of meshes = " << scene.meshes.size() << std::endl; + while (glfwWindowShouldClose(window) == GL_FALSE) { glfwPollEvents(); glClearColor(0.1f, 0.2f, 0.3f, 1.0f); diff --git a/examples/glview/premake4.lua b/examples/glview/premake4.lua index 773a194..51b4a73 100644 --- a/examples/glview/premake4.lua +++ b/examples/glview/premake4.lua @@ -1,36 +1,41 @@ solution "glview" - -- location ( "build" ) - configurations { "Debug", "Release" } - platforms {"native", "x64", "x32"} - - project "glview" + -- location ( "build" ) + configurations { "Debug", "Release" } + platforms {"native", "x64", "x32"} + + project "glview" - kind "ConsoleApp" - language "C++" - files { "glview.cc", "trackball.cc" } - includedirs { "./" } - includedirs { "../../" } + kind "ConsoleApp" + language "C++" + files { "glview.cc", "trackball.cc" } + includedirs { "./" } + includedirs { "../../" } - configuration { "linux" } - linkoptions { "`pkg-config --libs glfw3`" } - links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } + configuration { "linux" } + linkoptions { "`pkg-config --libs glfw3`" } + links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } - configuration { "windows" } - links { "glfw3", "gdi32", "winmm", "user32", "GLEW", "glu32","opengl32", "kernel32" } - defines { "_CRT_SECURE_NO_WARNINGS" } + 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" } + 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" } + links { "glfw3", "GLEW" } + linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" } - configuration "Debug" - defines { "DEBUG" } - flags { "Symbols", "ExtraWarnings"} + configuration "Debug" + defines { "DEBUG" } + flags { "Symbols", "ExtraWarnings"} - configuration "Release" - defines { "NDEBUG" } - flags { "Optimize", "ExtraWarnings"} + configuration "Release" + defines { "NDEBUG" } + flags { "Optimize", "ExtraWarnings"} diff --git a/examples/writer/Makefile b/examples/writer/Makefile new file mode 100644 index 0000000..7f7a703 --- /dev/null +++ b/examples/writer/Makefile @@ -0,0 +1,2 @@ +all: + $(CXX) -o gltf_writer -I../../ writer.cc diff --git a/examples/writer/README.md b/examples/writer/README.md new file mode 100644 index 0000000..32f2399 --- /dev/null +++ b/examples/writer/README.md @@ -0,0 +1,11 @@ +# Simple glTF writer in C++. + +Read glTF with tinygltfloader, and write it to glTF JSON. + +## TODO + +* [ ] Asset export option(embed, external file) +* [ ] Textures +* [ ] Materials +* [ ] etc. + diff --git a/examples/writer/writer.cc b/examples/writer/writer.cc new file mode 100644 index 0000000..01d66b2 --- /dev/null +++ b/examples/writer/writer.cc @@ -0,0 +1,431 @@ +#include +#include +#include +#include + +#define TINYGLTF_LOADER_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#include "../../tiny_gltf_loader.h" + +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 ""; +} + +// ---------------------------------------------------------------- +// writer module +// @todo { move writer code to tiny_gltf_writer.h } + +static std::string EncodeType(int ty) { + if (ty == TINYGLTF_TYPE_SCALAR) { + return "SCALAR"; + } else if (ty == TINYGLTF_TYPE_VECTOR) { + return "VECTOR"; + } else if (ty == TINYGLTF_TYPE_VEC2) { + return "VEC2"; + } else if (ty == TINYGLTF_TYPE_VEC3) { + return "VEC3"; + } else if (ty == TINYGLTF_TYPE_VEC4) { + return "VEC4"; + } else if (ty == TINYGLTF_TYPE_MATRIX) { + return "MATRIX"; + } else if (ty == TINYGLTF_TYPE_MAT2) { + return "MAT2"; + } else if (ty == TINYGLTF_TYPE_MAT3) { + return "MAT3"; + } else if (ty == TINYGLTF_TYPE_MAT4) { + return "MAT4"; + } + return "**UNKNOWN**"; +} + +// http://www.adp-gmbh.ch/cpp/common/base64.html +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +std::string base64_encode(unsigned char const* bytes_to_encode, + unsigned int in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) { + for (j = i; j < 3; j++) char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; + + while ((i++ < 3)) ret += '='; + } + + return ret; +} + +bool EncodeBuffers(picojson::object* o, + const std::map& buffers) { + std::map::const_iterator it(buffers.begin()); + std::map::const_iterator itEnd(buffers.end()); + for (; it != itEnd; it++) { + // @todo { Support external file resource. } + picojson::object buf; + std::string b64_data = + base64_encode(it->second.data.data(), it->second.data.size()); + buf["type"] = picojson::value("arraybuffer"); + buf["uri"] = picojson::value( + std::string("data:application/octet-stream;base64,") + b64_data); + buf["byteLength"] = + picojson::value(static_cast(it->second.data.size())); + + (*o)[it->first] = picojson::value(buf); + } + + return true; +} + +bool EncodeBufferViews( + picojson::object* o, + const std::map& bufferViews) { + std::map::const_iterator it( + bufferViews.begin()); + std::map::const_iterator itEnd( + bufferViews.end()); + + for (; it != itEnd; it++) { + picojson::object buf; + buf["buffer"] = picojson::value(it->second.buffer); + buf["byteLength"] = + picojson::value(static_cast(it->second.byteLength)); + buf["byteOffset"] = + picojson::value(static_cast(it->second.byteOffset)); + buf["target"] = picojson::value(static_cast(it->second.target)); + + (*o)[it->first] = picojson::value(buf); + } + + return true; +} + +bool EncodeFloatArray(picojson::array* arr, const std::vector& values) { + for (size_t i = 0; i < values.size(); i++) { + arr->push_back(picojson::value(values[i])); + } + + return true; +} + +bool EncodeStringArray(picojson::array* arr, + const std::vector& values) { + for (size_t i = 0; i < values.size(); i++) { + arr->push_back(picojson::value(values[i])); + } + + return true; +} + +bool EncodeNode(picojson::object* o, const tinygltf::Node& node) { + (*o)["name"] = picojson::value(node.name); + (*o)["camera"] = picojson::value(node.camera); + + if (!node.rotation.empty()) { + picojson::array arr; + EncodeFloatArray(&arr, node.rotation); + (*o)["rotation"] = picojson::value(arr); + } + if (!node.scale.empty()) { + picojson::array arr; + EncodeFloatArray(&arr, node.scale); + (*o)["scale"] = picojson::value(arr); + } + if (!node.translation.empty()) { + picojson::array arr; + EncodeFloatArray(&arr, node.translation); + (*o)["translation"] = picojson::value(arr); + } + + if (!node.matrix.empty()) { + picojson::array arr; + EncodeFloatArray(&arr, node.matrix); + (*o)["matrix"] = picojson::value(arr); + } + + if (!node.meshes.empty()) { + picojson::array arr; + EncodeStringArray(&arr, node.meshes); + (*o)["meshes"] = picojson::value(arr); + } + + if (!node.children.empty()) { + picojson::array arr; + EncodeStringArray(&arr, node.children); + (*o)["children"] = picojson::value(arr); + } + + return true; +} + +bool EncodeNodes(picojson::object* o, + const std::map& nodes) { + std::map::const_iterator it(nodes.begin()); + std::map::const_iterator itEnd(nodes.end()); + + for (; it != itEnd; it++) { + picojson::object node; + EncodeNode(&node, it->second); + + (*o)[it->first] = picojson::value(node); + } + + return true; +} + +bool EncodeScenes( + picojson::object* o, + const std::map >& scenes) { + std::map >::const_iterator it( + scenes.begin()); + std::map >::const_iterator itEnd( + scenes.end()); + + for (; it != itEnd; it++) { + picojson::object buf; + picojson::array arr; + for (size_t i = 0; i < it->second.size(); i++) { + arr.push_back(picojson::value(it->second[i])); + } + + buf["nodes"] = picojson::value(arr); + + (*o)[it->first] = picojson::value(buf); + } + + return true; +} + +bool EncodeAccessors( + picojson::object* o, + const std::map& accessors) { + std::map::const_iterator it( + accessors.begin()); + std::map::const_iterator itEnd( + accessors.end()); + for (; it != itEnd; it++) { + picojson::object buf; + buf["bufferView"] = picojson::value(it->second.bufferView); + buf["byteOffset"] = + picojson::value(static_cast(it->second.byteOffset)); + buf["byteStride"] = + picojson::value(static_cast(it->second.byteStride)); + buf["componentType"] = + picojson::value(static_cast(it->second.componentType)); + buf["count"] = picojson::value(static_cast(it->second.count)); + buf["type"] = picojson::value(EncodeType(it->second.type)); + + if (!it->second.minValues.empty()) { + picojson::array arr; + EncodeFloatArray(&arr, it->second.minValues); + buf["min"] = picojson::value(arr); + } + if (!it->second.maxValues.empty()) { + picojson::array arr; + EncodeFloatArray(&arr, it->second.maxValues); + buf["max"] = picojson::value(arr); + } + + (*o)[it->first] = picojson::value(buf); + } + + return true; +} + +bool EncodePrimitive(picojson::object* o, + const tinygltf::Primitive& primitive) { + (*o)["material"] = picojson::value(primitive.material); + (*o)["indices"] = picojson::value(primitive.indices); + (*o)["mode"] = picojson::value(static_cast(primitive.mode)); + + std::map::const_iterator it( + primitive.attributes.begin()); + std::map::const_iterator itEnd( + primitive.attributes.end()); + + picojson::object attributes; + for (; it != itEnd; it++) { + picojson::object buf; + attributes[it->first] = picojson::value(it->second); + } + + (*o)["attributes"] = picojson::value(attributes); + + return true; +} + +bool EncodeMeshes(picojson::object* o, + const std::map& meshes) { + std::map::const_iterator it(meshes.begin()); + std::map::const_iterator itEnd(meshes.end()); + for (; it != itEnd; it++) { + picojson::object buf; + + buf["name"] = picojson::value(it->second.name); + + picojson::array arr; + for (size_t i = 0; i < it->second.primitives.size(); i++) { + picojson::object primitive; + EncodePrimitive(&primitive, it->second.primitives[i]); + arr.push_back(picojson::value(primitive)); + } + buf["primitives"] = picojson::value(arr); + + (*o)[it->first] = picojson::value(buf); + } + return true; +} + +bool SaveGLTF(const std::string& output_filename, + const tinygltf::Scene& scene) { + picojson::object root; + + { + picojson::object asset; + asset["generator"] = picojson::value("tinygltf_writer"); + asset["premultipliedAlpha"] = picojson::value(true); + asset["version"] = picojson::value(static_cast(1)); + picojson::object profile; + profile["api"] = picojson::value("WebGL"); + profile["version"] = picojson::value("1.0.2"); + asset["profile"] = picojson::value(profile); + root["assets"] = picojson::value(asset); + } + + { + picojson::object buffers; + bool ret = EncodeBuffers(&buffers, scene.buffers); + assert(ret); + root["buffers"] = picojson::value(buffers); + } + + { + picojson::object bufferViews; + bool ret = EncodeBufferViews(&bufferViews, scene.bufferViews); + assert(ret); + root["bufferViews"] = picojson::value(bufferViews); + } + + { + picojson::object accessors; + bool ret = EncodeAccessors(&accessors, scene.accessors); + assert(ret); + root["accessors"] = picojson::value(accessors); + } + + { + picojson::object meshes; + bool ret = EncodeMeshes(&meshes, scene.meshes); + assert(ret); + root["meshes"] = picojson::value(meshes); + } + + { + picojson::object nodes; + bool ret = EncodeNodes(&nodes, scene.nodes); + assert(ret); + root["nodes"] = picojson::value(nodes); + } + + root["scene"] = picojson::value(scene.defaultScene); + { + picojson::object scenes; + bool ret = EncodeScenes(&scenes, scene.scenes); + assert(ret); + root["scenes"] = picojson::value(scenes); + } + + // @todo {} + picojson::object shaders; + picojson::object programs; + picojson::object techniques; + picojson::object materials; + picojson::object skins; + root["shaders"] = picojson::value(shaders); + root["programs"] = picojson::value(programs); + root["techniques"] = picojson::value(techniques); + root["materials"] = picojson::value(materials); + root["skins"] = picojson::value(skins); + + picojson::value v = picojson::value(root); + + std::ofstream ifs(output_filename); + if (ifs.bad()) { + std::cerr << "Failed to open " << output_filename << std::endl; + return false; + } + + std::string s = v.serialize(); + ifs.write(s.data(), s.size()); + ifs.close(); + + return true; +} + +// ---------------------------------------------------------------- + +int main(int argc, char** argv) { + if (argc < 3) { + printf("Needs input.gltf output.gltf\n"); + exit(1); + } + + tinygltf::Scene scene; + tinygltf::TinyGLTFLoader loader; + std::string err; + 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(&scene, &err, input_filename.c_str()); + } else { + // assume ascii glTF. + ret = loader.LoadASCIIFromFile(&scene, &err, input_filename.c_str()); + } + + if (!err.empty()) { + printf("Err: %s\n", err.c_str()); + } + + if (!ret) { + printf("Failed to parse glTF\n"); + return -1; + } + + ret = SaveGLTF(argv[2], scene); + + return ret ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/loader_example.cc b/loader_example.cc index 15a2860..e88848c 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -29,6 +29,16 @@ static std::string PrintMode(int mode) { return "**UNKNOWN**"; } +static std::string PrintTarget(int target) { + if (target == 34962) { + return "GL_ARRAY_BUFFER"; + } else if (target == 34963) { + return "GL_ELEMENT_ARRAY_BUFFER"; + } else { + return "**UNKNOWN**"; + } +} + static std::string PrintType(int ty) { if (ty == TINYGLTF_TYPE_SCALAR) { return "SCALAR"; @@ -146,6 +156,8 @@ static void DumpNode(const tinygltf::Node &node, int indent) { static void DumpPrimitive(const tinygltf::Primitive &primitive, int indent) { std::cout << Indent(indent) << "material : " << primitive.material << std::endl; + std::cout << Indent(indent) << "indices : " << primitive.indices + << std::endl; std::cout << Indent(indent) << "mode : " << PrintMode(primitive.mode) << "(" << primitive.mode << ")" << std::endl; std::cout << Indent(indent) @@ -265,6 +277,8 @@ static void Dump(const tinygltf::Scene &scene) { << std::endl; std::cout << Indent(2) << "byteOffset : " << it->second.byteOffset << std::endl; + std::cout << Indent(2) << "target : " << PrintTarget(it->second.target) + << std::endl; } } diff --git a/tiny_gltf_loader.h b/tiny_gltf_loader.h index 867cfa9..12f89d4 100644 --- a/tiny_gltf_loader.h +++ b/tiny_gltf_loader.h @@ -1,7 +1,7 @@ // // Tiny glTF loader. // -// Copyright (c) 2015, Syoyo Fujita. +// Copyright (c) 2015-2016, Syoyo Fujita. // All rights reserved. // (Licensed under 2-clause BSD liecense) // @@ -1124,8 +1124,8 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, if (err) { std::stringstream ss; ss << "Invalid `byteLength'. Must be equal or less than binary size: " - "`byteLength' = " << byteLength - << ", binary size = " << bin_size << std::endl; + "`byteLength' = " + << byteLength << ", binary size = " << bin_size << std::endl; (*err) += ss.str(); } return false; @@ -1589,6 +1589,12 @@ bool TinyGLTFLoader::LoadFromString(Scene *scene, std::string *err, picojson::object::const_iterator it(root.begin()); picojson::object::const_iterator itEnd(root.end()); for (; it != itEnd; it++) { + if (!((it->second).is())) { + if (err) { + (*err) += "`scenes' does not contain an object."; + } + return false; + } const picojson::object &o = (it->second).get(); std::vector nodes; if (!ParseStringArrayProperty(&nodes, err, o, "nodes", false)) {