From 48b3422925946134a9d6c32d92a5f56e09c44160 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 28 Dec 2018 22:26:47 +0900 Subject: [PATCH] Initial addition of animation data dumper. Add an utility function to get buffer byte address. --- README.md | 1 + examples/anim-dump/Makefile | 6 + examples/anim-dump/README.md | 2 + examples/anim-dump/anim-dump.cc | 515 ++++++++++++++++++++++++++++++++ tiny_gltf.h | 51 +++- 5 files changed, 570 insertions(+), 5 deletions(-) create mode 100644 examples/anim-dump/Makefile create mode 100644 examples/anim-dump/README.md create mode 100644 examples/anim-dump/anim-dump.cc diff --git a/README.md b/README.md index d30af0c..1ed9da6 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ v2.0.0 release(22 Aug, 2018)! ## TODOs +* [ ] Sparse accesors(e.g. for efficient morph targets) * [ ] Write C++ code generator which emits C++ code from JSON schema for robust parsing. * [ ] Mesh Compression/decompression(Open3DGC, etc) * [ ] Load Draco compressed mesh diff --git a/examples/anim-dump/Makefile b/examples/anim-dump/Makefile new file mode 100644 index 0000000..cc55f47 --- /dev/null +++ b/examples/anim-dump/Makefile @@ -0,0 +1,6 @@ +# Use this for strict compilation check(will work on clang 3.8+) +#EXTRA_CXXFLAGS := -fsanitize=address -Wall -Werror -Weverything -Wno-c++11-long-long -Wno-c++98-compat + +all: + clang++ -I../../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o anim-dump anim-dump.cc + diff --git a/examples/anim-dump/README.md b/examples/anim-dump/README.md new file mode 100644 index 0000000..e916de4 --- /dev/null +++ b/examples/anim-dump/README.md @@ -0,0 +1,2 @@ +# Simple animation value dumper + diff --git a/examples/anim-dump/anim-dump.cc b/examples/anim-dump/anim-dump.cc new file mode 100644 index 0000000..40537dc --- /dev/null +++ b/examples/anim-dump/anim-dump.cc @@ -0,0 +1,515 @@ +// +// TODO(syoyo): Print extensions and extras for each glTF object. +// +#define TINYGLTF_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "tiny_gltf.h" + +#include +#include +#include + +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 ""; +} + +static std::string PrintMode(int mode) { + if (mode == TINYGLTF_MODE_POINTS) { + return "POINTS"; + } else if (mode == TINYGLTF_MODE_LINE) { + return "LINE"; + } else if (mode == TINYGLTF_MODE_LINE_LOOP) { + return "LINE_LOOP"; + } else if (mode == TINYGLTF_MODE_TRIANGLES) { + return "TRIANGLES"; + } else if (mode == TINYGLTF_MODE_TRIANGLE_FAN) { + return "TRIANGLE_FAN"; + } else if (mode == TINYGLTF_MODE_TRIANGLE_STRIP) { + return "TRIANGLE_STRIP"; + } + 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"; + } 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**"; +} + +static std::string PrintComponentType(int ty) { + if (ty == TINYGLTF_COMPONENT_TYPE_BYTE) { + return "BYTE"; + } else if (ty == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + return "UNSIGNED_BYTE"; + } else if (ty == TINYGLTF_COMPONENT_TYPE_SHORT) { + return "SHORT"; + } else if (ty == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + return "UNSIGNED_SHORT"; + } else if (ty == TINYGLTF_COMPONENT_TYPE_INT) { + return "INT"; + } else if (ty == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { + return "UNSIGNED_INT"; + } else if (ty == TINYGLTF_COMPONENT_TYPE_FLOAT) { + return "FLOAT"; + } else if (ty == TINYGLTF_COMPONENT_TYPE_DOUBLE) { + return "DOUBLE"; + } + + return "**UNKNOWN**"; +} + +#if 0 +static std::string PrintParameterType(int ty) { + if (ty == TINYGLTF_PARAMETER_TYPE_BYTE) { + return "BYTE"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE) { + return "UNSIGNED_BYTE"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_SHORT) { + return "SHORT"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT) { + return "UNSIGNED_SHORT"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_INT) { + return "INT"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT) { + return "UNSIGNED_INT"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_FLOAT) { + return "FLOAT"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_FLOAT_VEC2) { + return "FLOAT_VEC2"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_FLOAT_VEC3) { + return "FLOAT_VEC3"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_FLOAT_VEC4) { + return "FLOAT_VEC4"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_INT_VEC2) { + return "INT_VEC2"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_INT_VEC3) { + return "INT_VEC3"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_INT_VEC4) { + return "INT_VEC4"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_BOOL) { + return "BOOL"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_BOOL_VEC2) { + return "BOOL_VEC2"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_BOOL_VEC3) { + return "BOOL_VEC3"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_BOOL_VEC4) { + return "BOOL_VEC4"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_FLOAT_MAT2) { + return "FLOAT_MAT2"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_FLOAT_MAT3) { + return "FLOAT_MAT3"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_FLOAT_MAT4) { + return "FLOAT_MAT4"; + } else if (ty == TINYGLTF_PARAMETER_TYPE_SAMPLER_2D) { + return "SAMPLER_2D"; + } + + return "**UNKNOWN**"; +} +#endif + +static std::string PrintWrapMode(int mode) { + if (mode == TINYGLTF_TEXTURE_WRAP_REPEAT) { + return "REPEAT"; + } else if (mode == TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE) { + return "CLAMP_TO_EDGE"; + } else if (mode == TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT) { + return "MIRRORED_REPEAT"; + } + + return "**UNKNOWN**"; +} + +static std::string PrintFilterMode(int mode) { + if (mode == TINYGLTF_TEXTURE_FILTER_NEAREST) { + return "NEAREST"; + } else if (mode == TINYGLTF_TEXTURE_FILTER_LINEAR) { + return "LINEAR"; + } else if (mode == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST) { + return "NEAREST_MIPMAP_NEAREST"; + } else if (mode == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR) { + return "NEAREST_MIPMAP_LINEAR"; + } else if (mode == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST) { + return "LINEAR_MIPMAP_NEAREST"; + } else if (mode == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR) { + return "LINEAR_MIPMAP_LINEAR"; + } + return "**UNKNOWN**"; +} + +static std::string PrintIntArray(const std::vector &arr) { + if (arr.size() == 0) { + return ""; + } + + std::stringstream ss; + ss << "[ "; + for (size_t i = 0; i < arr.size(); i++) { + ss << arr[i] << ((i != arr.size() - 1) ? ", " : ""); + } + ss << " ]"; + + return ss.str(); +} + +static std::string PrintFloatArray(const std::vector &arr) { + if (arr.size() == 0) { + return ""; + } + + std::stringstream ss; + ss << "[ "; + for (size_t i = 0; i < arr.size(); i++) { + ss << arr[i] << ((i != arr.size() - 1) ? ", " : ""); + } + ss << " ]"; + + return ss.str(); +} + +static std::string Indent(const int indent) { + std::string s; + for (int i = 0; i < indent; i++) { + s += " "; + } + + return s; +} + +static std::string PrintParameterValue(const tinygltf::Parameter ¶m) { + if (!param.number_array.empty()) { + return PrintFloatArray(param.number_array); + } else { + return param.string_value; + } +} + +#if 0 +static std::string PrintParameterMap(const tinygltf::ParameterMap &pmap) { + std::stringstream ss; + + ss << pmap.size() << std::endl; + for (auto &kv : pmap) { + ss << kv.first << " : " << PrintParameterValue(kv.second) << std::endl; + } + + return ss.str(); +} +#endif + +static std::string PrintValue(const std::string &name, + const tinygltf::Value &value, const int indent, const bool tag = true) { + std::stringstream ss; + + if (value.IsObject()) { + const tinygltf::Value::Object &o = value.Get(); + tinygltf::Value::Object::const_iterator it(o.begin()); + tinygltf::Value::Object::const_iterator itEnd(o.end()); + for (; it != itEnd; it++) { + ss << PrintValue(it->first, it->second, indent + 1) << std::endl; + } + } else if (value.IsString()) { + if (tag) { + ss << Indent(indent) << name << " : " << value.Get(); + } else { + ss << " " << value.Get() << " "; + } + } else if (value.IsBool()) { + if (tag) { + ss << Indent(indent) << name << " : " << value.Get(); + } else { + ss << " " << value.Get() << " "; + } + } else if (value.IsNumber()) { + if (tag) { + ss << Indent(indent) << name << " : " << value.Get(); + } else { + ss << " " << value.Get() << " "; + } + } else if (value.IsInt()) { + if (tag) { + ss << Indent(indent) << name << " : " << value.Get(); + } else { + ss << " " << value.Get() << " "; + } + } else if (value.IsArray()) { + ss << Indent(indent) << name << " [ "; + for (size_t i = 0; i < value.Size(); i++) { + ss << PrintValue("", value.Get(int(i)), indent + 1, /* tag */false); + if (i != (value.ArrayLen()-1)) { + ss << ", "; + } + + } + ss << Indent(indent) << "] "; + } + + // @todo { binary } + + return ss.str(); +} + +static void DumpNode(const tinygltf::Node &node, int indent) { + std::cout << Indent(indent) << "name : " << node.name << std::endl; + std::cout << Indent(indent) << "camera : " << node.camera << std::endl; + std::cout << Indent(indent) << "mesh : " << node.mesh << std::endl; + if (!node.rotation.empty()) { + std::cout << Indent(indent) + << "rotation : " << PrintFloatArray(node.rotation) + << std::endl; + } + if (!node.scale.empty()) { + std::cout << Indent(indent) + << "scale : " << PrintFloatArray(node.scale) << std::endl; + } + if (!node.translation.empty()) { + std::cout << Indent(indent) + << "translation : " << PrintFloatArray(node.translation) + << std::endl; + } + + if (!node.matrix.empty()) { + std::cout << Indent(indent) + << "matrix : " << PrintFloatArray(node.matrix) << std::endl; + } + + std::cout << Indent(indent) + << "children : " << PrintIntArray(node.children) << std::endl; +} + +static void DumpStringIntMap(const std::map &m, int indent) { + std::map::const_iterator it(m.begin()); + std::map::const_iterator itEnd(m.end()); + for (; it != itEnd; it++) { + std::cout << Indent(indent) << it->first << ": " << it->second << std::endl; + } +} + +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) + << "attributes(items=" << primitive.attributes.size() << ")" + << std::endl; + DumpStringIntMap(primitive.attributes, indent + 1); + + std::cout << Indent(indent) << "extras :" << std::endl + << PrintValue("extras", primitive.extras, indent + 1) << std::endl; +} + +static void DumpExtensions(const tinygltf::ExtensionMap &extension, const int indent) +{ + // TODO(syoyo): pritty print Value + for (auto &e : extension) { + std::cout << Indent(indent) << e.first << std::endl; + std::cout << PrintValue("extensions", e.second, indent+1) << std::endl; + } +} + +static float DecodeScalarAnimationValue(const size_t i, const tinygltf::Accessor &accessor, const tinygltf::Model &model) +{ + const tinygltf::BufferView &bufferView = model.bufferViews[accessor.bufferView]; + const tinygltf::Buffer &buffer = model.buffers[bufferView.buffer]; + + const uint8_t *addr = GetBufferAddress(i, accessor, bufferView, buffer); + if (addr == nullptr) { + std::cerr << "Invalid glTF data?" << std::endl; + return 0.0f; + } + + float value = 0.0f; + + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { + value = tinygltf::DecodeAnimationChannelValue(*(reinterpret_cast(addr))); + } else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + value = tinygltf::DecodeAnimationChannelValue(*(reinterpret_cast(addr))); + } else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_SHORT) { + value = tinygltf::DecodeAnimationChannelValue(*(reinterpret_cast(addr))); + } else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + value = tinygltf::DecodeAnimationChannelValue(*(reinterpret_cast(addr))); + } else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { + value = *(reinterpret_cast(addr)); + } else { + std::cerr << "??? Unknown componentType : " << PrintComponentType(accessor.componentType) << std::endl; + } + + return value; +} + +static void ProcessAnimation(const tinygltf::Animation &animation, const tinygltf::Model &model) +{ +#if 0 + if (animaton_channel.target_path.compare("translation") == 0) { + } else if (animaton_channel.target_path.compare("rotation") == 0) { + } else if (animaton_channel.target_path.compare("scale") == 0) { + } else if (animaton_channel.target_path.compare("weights") == 0) { + } +#endif + + + for (size_t j = 0; j < animation.samplers.size(); j++) { + std::cout << "== samplers[" << j << "] ===============" << std::endl; + const tinygltf::AnimationSampler &sampler = animation.samplers[j]; + std::cout << Indent(1) << "interpolation = " << sampler.interpolation<< std::endl; + std::cout << Indent(1) << "input = " << sampler.input << std::endl; + std::cout << Indent(1) << "output = " << sampler.output << std::endl; + + // input accessor must have min/max property. + const tinygltf::Accessor &accessor = model.accessors[sampler.input]; + + for (size_t i = 0; i < accessor.minValues.size(); i++) { + std::cout << Indent(1) << "input min[" << i << "] = " << accessor.minValues[i] << std::endl; + } + for (size_t i = 0; i < accessor.maxValues.size(); i++) { + std::cout << Indent(1) << "input max[" << i << "] = " << accessor.maxValues[i] << std::endl; + } + + std::cout << Indent(1) << "input count = " << accessor.count << std::endl; + + for (size_t i = 0; i < accessor.count; i++) { + if (accessor.type == TINYGLTF_TYPE_SCALAR) { + float v = DecodeScalarAnimationValue(i, accessor, model); + std::cout << Indent(2) << "input value[" << i << "] = " << v << std::endl; + } + } + + + //const tinygltf::Accessor &accessor = model.accessors[sampler.output]; + //std::cout << Indent(2) << "output : " << sampler.output + // << std::endl; + } + +} + +static void DumpAnim(const tinygltf::Model &model) { + std::cout << "=== Dump glTF ===" << std::endl; + std::cout << "asset.copyright : " << model.asset.copyright + << std::endl; + std::cout << "asset.generator : " << model.asset.generator + << std::endl; + std::cout << "asset.version : " << model.asset.version + << std::endl; + std::cout << "asset.minVersion : " << model.asset.minVersion + << std::endl; + std::cout << std::endl; + + std::cout << "=== Dump scene ===" << std::endl; + std::cout << "defaultScene: " << model.defaultScene << std::endl; + + { + std::cout << "animations(items=" << model.animations.size() << ")" + << std::endl; + for (size_t i = 0; i < model.animations.size(); i++) { + const tinygltf::Animation &animation = model.animations[i]; + std::cout << Indent(1) << "name : " << animation.name + << std::endl; + + std::cout << Indent(1) << "channels : [ " << std::endl; + for (size_t j = 0; i < animation.channels.size(); i++) { + std::cout << Indent(2) + << "sampler : " << animation.channels[j].sampler + << std::endl; + std::cout << Indent(2) + << "target.id : " << animation.channels[j].target_node + << std::endl; + std::cout << Indent(2) + << "target.path : " << animation.channels[j].target_path + << std::endl; + std::cout << ((i != (animation.channels.size() - 1)) ? " , " : ""); + } + std::cout << " ]" << std::endl; + + std::cout << Indent(1) << "samplers(items=" << animation.samplers.size() + << ")" << std::endl; + for (size_t j = 0; j < animation.samplers.size(); j++) { + const tinygltf::AnimationSampler &sampler = animation.samplers[j]; + std::cout << Indent(2) << "input : " << sampler.input + << std::endl; + std::cout << Indent(2) << "interpolation : " << sampler.interpolation + << std::endl; + std::cout << Indent(2) << "output : " << sampler.output + << std::endl; + } + + ProcessAnimation(animation, model); + } + + } + +} + +int main(int argc, char **argv) { + if (argc < 2) { + printf("Needs input.gltf\n"); + exit(1); + } + + tinygltf::Model model; + tinygltf::TinyGLTF gltf_ctx; + 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) { + std::cout << "Reading binary glTF" << std::endl; + // assume binary glTF. + ret = gltf_ctx.LoadBinaryFromFile(&model, &err, &warn, input_filename.c_str()); + } else { + std::cout << "Reading ASCII glTF" << std::endl; + // assume ascii glTF. + ret = gltf_ctx.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 parse glTF\n"); + return -1; + } + + DumpAnim(model); + + return 0; +} diff --git a/tiny_gltf.h b/tiny_gltf.h index d70060b..8608501 100644 --- a/tiny_gltf.h +++ b/tiny_gltf.h @@ -56,6 +56,7 @@ namespace tinygltf { #define TINYGLTF_MODE_TRIANGLE_STRIP (5) #define TINYGLTF_MODE_TRIANGLE_FAN (6) +#define TINYGLTF_COMPONENT_TYPE_INVALID (-1) #define TINYGLTF_COMPONENT_TYPE_BYTE (5120) #define TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE (5121) #define TINYGLTF_COMPONENT_TYPE_SHORT (5122) @@ -106,6 +107,7 @@ namespace tinygltf { // End parameter types +#define TINYGLTF_TYPE_INVALID (-1) #define TINYGLTF_TYPE_VEC2 (2) #define TINYGLTF_TYPE_VEC3 (3) #define TINYGLTF_TYPE_VEC4 (4) @@ -194,6 +196,20 @@ static inline int32_t GetTypeSizeInBytes(uint32_t ty) { } } +// Utility function for decoding animation value +static inline float DecodeAnimationChannelValue(int8_t c) { + return std::max(float(c) / 127.0f, -1.0f); +} +static inline float DecodeAnimationChannelValue(uint8_t c) { + return float(c) / 255.0f; +} +static inline float DecodeAnimationChannelValue(int16_t c) { + return std::max(float(c) / 32767.0f, -1.0f); +} +static inline float DecodeAnimationChannelValue(uint16_t c) { + return float(c) / 65525.0f; +} + bool IsDataURI(const std::string &in); bool DecodeDataURI(std::vector *out, std::string &mime_type, const std::string &in, size_t reqBytes, bool checkSize); @@ -416,6 +432,7 @@ struct AnimationSampler { AnimationSampler() : input(-1), output(-1), interpolation("LINEAR") {} bool operator==(const AnimationSampler &) const; + }; struct Animation { @@ -535,11 +552,11 @@ struct Accessor { int bufferView; // optional in spec but required here since sparse accessor // are not supported std::string name; - size_t byteOffset; - bool normalized; // optinal. - int componentType; // (required) One of TINYGLTF_COMPONENT_TYPE_*** - size_t count; // required - int type; // (required) One of TINYGLTF_TYPE_*** .. + size_t byteOffset = 0; + bool normalized = false; // optinal. + int componentType = TINYGLTF_COMPONENT_TYPE_INVALID; // (required) One of TINYGLTF_COMPONENT_TYPE_*** + size_t count = 0; // required + int type = TINYGLTF_TYPE_INVALID; // (required) One of TINYGLTF_TYPE_*** .. Value extras; std::vector minValues; // optional @@ -588,6 +605,7 @@ struct Accessor { bool operator==(const tinygltf::Accessor &) const; }; + struct PerspectiveCamera { double aspectRatio; // min > 0 double yfov; // required. min > 0 @@ -775,6 +793,14 @@ class Model { Value extras; }; +/// +/// Utility function to get an address of underlying buffer for i'th element. +/// Returns nullptr for invalid parameter or invalid data. +/// Assume `buffer` parameter = model.buffers[bufferViewObject.buffer] +/// +const uint8_t *GetBufferAddress(const int i, const Accessor &accessor, const BufferView &bufferViewObject, const Buffer &buffer); + + enum SectionCheck { NO_REQUIRE = 0x00, REQUIRE_SCENE = 0x01, @@ -2008,6 +2034,21 @@ bool DecodeDataURI(std::vector *out, std::string &mime_type, return true; } +const uint8_t *GetBufferAddress(const int i, const Accessor &accessor, const BufferView &bufferViewObject, const Buffer &buffer) { + + if (i >= accessor.count) return nullptr; + + int byte_stride = accessor.ByteStride(bufferViewObject); + if (byte_stride == -1) { + return nullptr; + } + + // TODO(syoyo): Bounds check. + const uint8_t *base_addr = buffer.data.data() + bufferViewObject.byteOffset + accessor.byteOffset; + const uint8_t *addr = base_addr + i * byte_stride; + return addr; +} + static bool ParseJsonAsValue(Value *ret, const json &o) { Value val{}; switch (o.type()) {