From 7b6edab3a9c2589b7af00d2c2c88584bb2e7788c Mon Sep 17 00:00:00 2001
From: Syoyo Fujita <syoyo@lighttransport.com>
Date: Thu, 13 Jul 2017 18:20:14 +0900
Subject: [PATCH] Initial support of parsing/serializing Camera.

---
 loader_example.cc |  32 ++++++
 tiny_gltf.h       | 260 +++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 276 insertions(+), 16 deletions(-)

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<Image> images;
   std::vector<Skin> skins;
   std::vector<Sampler> samplers;
+  std::vector<Camera> cameras;
   std::vector<Scene> 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<picojson::object>()) {
+      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<picojson::object>())) {
+      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<picojson::object>()) {
+      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<picojson::object>())) {
+      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<picojson::array>()) {
+    const picojson::array &root = v.get("cameras").get<picojson::array>();
+
+    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<picojson::object>())) {
+        return false;
+      }
+
+      model->cameras.push_back(camera);
+    }
+  }
   return true;
 }
 
@@ -2920,6 +3095,10 @@ static void SerializeGltfNode(Node &node, picojson::object &o) {
     SerializeNumberProperty<int>("skin", node.skin, o);
   }
 
+  if (node.camera != -1) {
+    SerializeNumberProperty<int>("camera", node.camera, o);
+  }
+
   SerializeStringProperty("name", node.name, o);
   SerializeNumberArrayProperty<int>("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<int>("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;
 }