diff --git a/loader_example.cc b/loader_example.cc index c575c76..156ad61 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -498,6 +498,38 @@ static void Dump(const tinygltf::Model &model) { << std::endl; } } + + { + std::cout << "cameras(items=" << model.cameras.size() << ")" << std::endl; + + for (size_t i = 0; i < model.cameras.size(); i++) { + const tinygltf::Camera &camera = model.cameras[i]; + std::cout << Indent(1) << "name (id) : " << camera.name << std::endl; + std::cout << Indent(1) << "type : " << camera.type << std::endl; + + if (camera.type.compare("perspective") == 0) { + std::cout << Indent(2) + << "aspectRatio : " << camera.perspective.aspectRatio + << std::endl; + std::cout << Indent(2) << "yfov : " << camera.perspective.yfov + << std::endl; + std::cout << Indent(2) << "zfar : " << camera.perspective.zfar + << std::endl; + std::cout << Indent(2) << "znear : " << camera.perspective.znear + << std::endl; + } else if (camera.type.compare("orthographic") == 0) { + std::cout << Indent(2) << "xmag : " << camera.orthographic.xmag + << std::endl; + std::cout << Indent(2) << "ymag : " << camera.orthographic.ymag + << std::endl; + std::cout << Indent(2) << "zfar : " << camera.orthographic.zfar + << std::endl; + std::cout << Indent(2) + << "znear : " << camera.orthographic.znear + << std::endl; + } + } + } } int main(int argc, char **argv) { diff --git a/tiny_gltf.h b/tiny_gltf.h index 1cc2c9b..2d422ec 100644 --- a/tiny_gltf.h +++ b/tiny_gltf.h @@ -414,26 +414,44 @@ struct Accessor { Accessor() { bufferView = -1; } }; -class Camera { - public: - Camera() {} - ~Camera() {} +struct PerspectiveCamera { + float aspectRatio; // min > 0 + float yfov; // required. min > 0 + float zfar; // min > 0 + float znear; // required. min > 0 - std::string name; - bool isOrthographic; // false = perspective. + PerspectiveCamera() + : aspectRatio(0.0f), + yfov(0.0f), + zfar(0.0f) // 0 = use infinite projecton matrix + , + znear(0.0f) {} - // Orthographic properties - float xMag; // required - float yMag; // required - float zFar; // required - float zNear; // required + ParameterMap extensions; + Value extras; +}; - // Perspective properties - float aspectRatio; - float yfov; // required - float zfar; +struct OrthographicCamera { + float xmag; // required. must not be zero. + float ymag; // required. must not be zero. + float zfar; // required. `zfar` must be greater than `znear`. float znear; // required + OrthographicCamera() : xmag(0.0f), ymag(0.0f), zfar(0.0f), znear(0.0f) {} + + ParameterMap extensions; + Value extras; +}; + +struct Camera { + std::string type; // required. "perspective" or "orthographic" + std::string name; + + PerspectiveCamera perspective; + OrthographicCamera orthographic; + + Camera() {} + ParameterMap extensions; Value extras; }; @@ -470,7 +488,7 @@ typedef struct { class Node { public: - Node() : skin(-1), mesh(-1) {} + Node() : camera(-1), skin(-1), mesh(-1) {} ~Node() {} @@ -530,6 +548,7 @@ class Model { std::vector images; std::vector skins; std::vector samplers; + std::vector cameras; std::vector scenes; int defaultScene; @@ -2112,6 +2131,145 @@ static bool ParseSkin(Skin *skin, std::string *err, const picojson::object &o) { return true; } +static bool ParsePerspectiveCamera(PerspectiveCamera *camera, std::string *err, + const picojson::object &o) { + double yfov = 0.0; + if (!ParseNumberProperty(&yfov, err, o, "yfov", true, "OrthographicCamera")) { + return false; + } + + double znear = 0.0; + if (!ParseNumberProperty(&znear, err, o, "znear", true, + "PerspectiveCamera")) { + return false; + } + + double aspectRatio = 0.0; // = invalid + ParseNumberProperty(&aspectRatio, err, o, "aspectRatio", false, + "PerspectiveCamera"); + + double zfar = 0.0; // = invalid + ParseNumberProperty(&zfar, err, o, "zfar", false, "PerspectiveCamera"); + + camera->aspectRatio = float(aspectRatio); + camera->zfar = float(zfar); + camera->yfov = float(yfov); + camera->znear = float(znear); + + ParseExtrasProperty(&(camera->extras), o); + + // TODO(syoyo): Validate parameter values. + + return true; +} + +static bool ParseOrthographicCamera(OrthographicCamera *camera, + std::string *err, + const picojson::object &o) { + double xmag = 0.0; + if (!ParseNumberProperty(&xmag, err, o, "xmag", true, "OrthographicCamera")) { + return false; + } + + double ymag = 0.0; + if (!ParseNumberProperty(&ymag, err, o, "ymag", true, "OrthographicCamera")) { + return false; + } + + double zfar = 0.0; + if (!ParseNumberProperty(&zfar, err, o, "zfar", true, "OrthographicCamera")) { + return false; + } + + double znear = 0.0; + if (!ParseNumberProperty(&znear, err, o, "znear", true, + "OrthographicCamera")) { + return false; + } + + ParseExtrasProperty(&(camera->extras), o); + + camera->xmag = float(xmag); + camera->ymag = float(ymag); + camera->zfar = float(zfar); + camera->znear = float(znear); + + // TODO(syoyo): Validate parameter values. + + return true; +} + +static bool ParseCamera(Camera *camera, std::string *err, + const picojson::object &o) { + if (!ParseStringProperty(&camera->type, err, o, "type", true, "Camera")) { + return false; + } + + if (camera->type.compare("orthographic") == 0) { + if (o.find("orthographic") == o.end()) { + if (err) { + std::stringstream ss; + ss << "Orhographic camera description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const picojson::value &v = o.find("orthographic")->second; + if (!v.is()) { + if (err) { + std::stringstream ss; + ss << "\"orthographic\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParseOrthographicCamera(&camera->orthographic, err, + v.get())) { + return false; + } + } else if (camera->type.compare("perspective") == 0) { + if (o.find("perspective") == o.end()) { + if (err) { + std::stringstream ss; + ss << "Perspective camera description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const picojson::value &v = o.find("perspective")->second; + if (!v.is()) { + if (err) { + std::stringstream ss; + ss << "\"perspective\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParsePerspectiveCamera(&camera->perspective, err, + v.get())) { + return false; + } + } else { + if (err) { + std::stringstream ss; + ss << "Invalid camera type: \"" << camera->type + << "\". Must be \"perspective\" or \"orthographic\"" << std::endl; + (*err) += ss.str(); + } + return false; + } + + ParseStringProperty(&camera->name, err, o, "name", false); + + ParseExtrasProperty(&(camera->extras), o); + + return true; +} + bool TinyGLTF::LoadFromString(Model *model, std::string *err, const char *str, unsigned int length, const std::string &base_dir, unsigned int check_sections) { @@ -2177,6 +2335,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, const char *str, model->bufferViews.clear(); model->accessors.clear(); model->meshes.clear(); + model->cameras.clear(); model->nodes.clear(); model->extensionsUsed.clear(); model->extensionsRequired.clear(); @@ -2449,6 +2608,22 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, const char *str, model->samplers.push_back(sampler); } } + + // 14. Parse Camera + if (v.contains("cameras") && v.get("cameras").is()) { + const picojson::array &root = v.get("cameras").get(); + + picojson::array::const_iterator it(root.begin()); + picojson::array::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + Camera camera; + if (!ParseCamera(&camera, err, it->get())) { + return false; + } + + model->cameras.push_back(camera); + } + } return true; } @@ -2920,6 +3095,10 @@ static void SerializeGltfNode(Node &node, picojson::object &o) { SerializeNumberProperty("skin", node.skin, o); } + if (node.camera != -1) { + SerializeNumberProperty("camera", node.camera, o); + } + SerializeStringProperty("name", node.name, o); SerializeNumberArrayProperty("children", node.children, o); } @@ -2931,6 +3110,46 @@ static void SerializeGltfSampler(Sampler &sampler, picojson::object &o) { SerializeNumberProperty("wrapT", sampler.wrapT, o); } +static void SerializeGltfOrthographicCamera(const OrthographicCamera &camera, + picojson::object &o) { + SerializeNumberProperty("zfar", camera.zfar, o); + SerializeNumberProperty("znear", camera.znear, o); + SerializeNumberProperty("xmag", camera.xmag, o); + SerializeNumberProperty("ymag", camera.ymag, o); +} + +static void SerializeGltfPerspectiveCamera(const PerspectiveCamera &camera, + picojson::object &o) { + SerializeNumberProperty("zfar", camera.zfar, o); + SerializeNumberProperty("znear", camera.znear, o); + if (camera.aspectRatio > 0) { + SerializeNumberProperty("aspectRatio", camera.aspectRatio, o); + } + + if (camera.yfov > 0) { + SerializeNumberProperty("yfov", camera.yfov, o); + } +} + +static void SerializeGltfCamera(const Camera &camera, picojson::object &o) { + SerializeStringProperty("type", camera.type, o); + if (!camera.name.empty()) { + SerializeStringProperty("name", camera.type, o); + } + + if (camera.type.compare("orthographic") == 0) { + picojson::object orthographic; + SerializeGltfOrthographicCamera(camera.orthographic, orthographic); + o.insert(json_object_pair("orthographic", picojson::value(orthographic))); + } else if (camera.type.compare("perspective") == 0) { + picojson::object perspective; + SerializeGltfPerspectiveCamera(camera.perspective, perspective); + o.insert(json_object_pair("perspective", picojson::value(perspective))); + } else { + // ??? + } +} + static void SerializeGltfScene(Scene &scene, picojson::object &o) { SerializeNumberArrayProperty("nodes", scene.nodes, o); @@ -3117,6 +3336,15 @@ bool TinyGLTF::WriteGltfSceneToFile( } output.insert(json_object_pair("samplers", picojson::value(samplers))); + // CAMERAS + picojson::array cameras; + for (unsigned int i = 0; i < model->cameras.size(); ++i) { + picojson::object camera; + SerializeGltfCamera(model->cameras[i], camera); + cameras.push_back(picojson::value(camera)); + } + output.insert(json_object_pair("cameras", picojson::value(cameras))); + WriteGltfFile(filename, picojson::value(output).serialize()); return true; }