#include <map>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>

#pragma warning(disable:4996)



#include <stdlib.h>
#include <stdint.h>
#include <vector>


#define FPNG_NO_STDIO

#ifndef FPNG_TRAIN_HUFFMAN_TABLES
// Set to 1 when using the -t (training) option in fpng_test to generate new opaque/alpha Huffman tables for the single pass encoder.
#define FPNG_TRAIN_HUFFMAN_TABLES (0)
#endif

namespace Png
{
  void init();

  // Fast CRC-32 SSE4.1+pclmul or a scalar fallback (slice by 4)
  const uint32_t FPNG_CRC32_INIT = 0;
  //uint32_t fpng_crc32(const void* pData, size_t size, uint32_t prev_crc32 = FPNG_CRC32_INIT);

  // Fast Adler32 SSE4.1 Adler-32 with a scalar fallback.
  const uint32_t FPNG_ADLER32_INIT = 1;
  //uint32_t fpng_adler32(const void* pData, size_t size, uint32_t adler = FPNG_ADLER32_INIT);

  // ---- Compression
  enum
  {
    // Enables computing custom Huffman tables for each file, instead of using the custom global tables. 
    // Results in roughly 6% smaller files on average, but compression is around 40% slower.
    FPNG_ENCODE_SLOWER = 1,

    // Only use raw Deflate blocks (no compression at all). Intended for testing.
    FPNG_FORCE_UNCOMPRESSED = 2,
  };

  // Fast PNG encoding. The resulting file can be decoded either using a standard PNG decoder or by the fpng_decode_memory() function below.
  // pImage: pointer to RGB or RGBA image pixels, R first in memory, B/A last.
  // w/h - image dimensions. Image's row pitch in bytes must is w*num_chans.
  // num_chans must be 3 or 4. 
  bool fpng_encode_image_to_memory(const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, std::vector<uint8_t>& out_buf, uint32_t flags = 0);

  // ---- Decompression

  enum
  {
    FPNG_DECODE_SUCCESS = 0,				// file is a valid PNG file and written by FPNG and the decode succeeded

    FPNG_DECODE_NOT_FPNG,					// file is a valid PNG file, but it wasn't written by FPNG so you should try decoding it with a general purpose PNG decoder

    FPNG_DECODE_INVALID_ARG,				// invalid function parameter

    FPNG_DECODE_FAILED_NOT_PNG,				// file cannot be a PNG file
    FPNG_DECODE_FAILED_HEADER_CRC32,		// a chunk CRC32 check failed, file is likely corrupted or not PNG
    FPNG_DECODE_FAILED_INVALID_DIMENSIONS,  // invalid image dimensions in IHDR chunk (0 or too large)
    FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE, // decoding the file fully into memory would likely require too much memory (only on 32bpp builds)
    FPNG_DECODE_FAILED_CHUNK_PARSING,		// failed while parsing the chunk headers, or file is corrupted
    FPNG_DECODE_FAILED_INVALID_IDAT,		// IDAT data length is too small and cannot be valid, file is either corrupted or it's a bug

    // fpng_decode_file() specific errors
    FPNG_DECODE_FILE_OPEN_FAILED,
    FPNG_DECODE_FILE_TOO_LARGE,
    FPNG_DECODE_FILE_READ_FAILED,
    FPNG_DECODE_FILE_SEEK_FAILED
  };

  // fpng_decode_memory() decompresses 24/32bpp PNG files ONLY encoded by this module.
  // If the image was written by FPNG, it will decompress the image data, otherwise it will return FPNG_DECODE_NOT_FPNG in which case you should fall back to a general purpose PNG decoder (lodepng, stb_image, libpng, etc.)
  //
  // pImage, image_size: Pointer to PNG image data and its size
  // out: Output 24/32bpp image buffer
  // width, height: output image's dimensions
  // channels_in_file: will be 3 or 4
  // desired_channels: must be 3 or 4 
  // 
  // If the image is 24bpp and 32bpp is requested, the alpha values will be set to 0xFF. 
  // If the image is 32bpp and 24bpp is requested, the alpha values will be discarded.
  // 
  // Returns FPNG_DECODE_SUCCESS on success, otherwise one of the failure codes above.
  // If FPNG_DECODE_NOT_FPNG is returned, you must decompress the file with a general purpose PNG decoder.
  // If another error occurs, the file is likely corrupted or invalid, but you can still try to decompress the file with another decoder (which will likely fail).
  int fpng_decode_memory(const void* pImage, uint32_t image_size, std::vector<uint8_t>& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels);

  // ---- Internal API used for Huffman table training purposes

#if FPNG_TRAIN_HUFFMAN_TABLES
  const uint32_t HUFF_COUNTS_SIZE = 288;
  extern uint64_t g_huff_counts[HUFF_COUNTS_SIZE];
  bool create_dynamic_block_prefix(uint64_t* pFreq, uint32_t num_chans, std::vector<uint8_t>& prefix, uint64_t& bit_buf, int& bit_buf_size, uint32_t* pCodes, uint8_t* pCodesizes);
#endif


  //int decode(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32);
}


// fpng.cpp 1.0.6

#include <assert.h>
#include <string.h>

#ifdef _MSC_VER
#pragma warning (disable:4127) // conditional expression is constant
#endif

// Set FPNG_NO_SSE to 1 to completely disable SSE usage.
#ifndef FPNG_NO_SSE
#define FPNG_NO_SSE (0)
#endif

// Detect if we're compiling on x86/x64
#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__)
#define FPNG_X86_OR_X64_CPU (1)
#else
#define FPNG_X86_OR_X64_CPU (0)
#endif

#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE
#ifdef _MSC_VER
#include <intrin.h>
#endif
#include <xmmintrin.h>		// SSE
#include <emmintrin.h>		// SSE2
#include <smmintrin.h>		// SSE4.1
#include <wmmintrin.h>		// pclmul
#endif

// Allow the disabling of the chunk data CRC32 checks, for fuzz testing of the decoder
#ifndef FPNG_DISABLE_DECODE_CRC32_CHECKS
#define FPNG_DISABLE_DECODE_CRC32_CHECKS (0)
#endif

// Using unaligned loads and stores causes errors when using UBSan. Jam it off.
#if defined(__has_feature)
#if __has_feature(undefined_behavior_sanitizer)
#undef FPNG_USE_UNALIGNED_LOADS
#define FPNG_USE_UNALIGNED_LOADS (0)
#endif
#endif

// Set to 0 if your platform doesn't support unaligned 32-bit/64-bit reads/writes. 
#ifndef FPNG_USE_UNALIGNED_LOADS
#if FPNG_X86_OR_X64_CPU
  // On x86/x64 we default to enabled, for a noticeable perf gain.
#define FPNG_USE_UNALIGNED_LOADS (1)
#else
#define FPNG_USE_UNALIGNED_LOADS (0)
#endif
#endif

#if defined(_MSC_VER) || defined(__MINGW32__) || FPNG_X86_OR_X64_CPU
#ifndef __LITTLE_ENDIAN
#define __LITTLE_ENDIAN 1234
#endif
#ifndef __BIG_ENDIAN
#define __BIG_ENDIAN 4321
#endif

// Assume little endian on Windows/x86/x64.
#define __BYTE_ORDER __LITTLE_ENDIAN
#elif defined(__APPLE__)
#define __BYTE_ORDER __BYTE_ORDER__
#define __LITTLE_ENDIAN __LITTLE_ENDIAN__
#define __BIG_ENDIAN __BIG_ENDIAN__
#else
  // for __BYTE_ORDER (__LITTLE_ENDIAN or __BIG_ENDIAN)
#include <sys/param.h>

#ifndef __LITTLE_ENDIAN
#define __LITTLE_ENDIAN 1234
#endif
#ifndef __BIG_ENDIAN
#define __BIG_ENDIAN 4321
#endif
#endif

#if !defined(__BYTE_ORDER)
#error __BYTE_ORDER undefined. Compile with -D__BYTE_ORDER=1234 for little endian or -D__BYTE_ORDER=4321 for big endian.
#endif

namespace Png
{
  static const int FPNG_FALSE = 0;
  static const uint8_t FPNG_FDEC_VERSION = 0;
  static const uint32_t FPNG_MAX_SUPPORTED_DIM = 1 << 24;

  template <typename S> static inline S maximum(S a, S b) { return (a > b) ? a : b; }
  template <typename S> static inline S minimum(S a, S b) { return (a < b) ? a : b; }

  static inline uint32_t simple_swap32(uint32_t x) { return (x >> 24) | ((x >> 8) & 0x0000FF00) | ((x << 8) & 0x00FF0000) | (x << 24); }
  static inline uint64_t simple_swap64(uint64_t x) { return (((uint64_t)simple_swap32((uint32_t)x)) << 32U) | simple_swap32((uint32_t)(x >> 32U)); }

  static inline uint32_t swap32(uint32_t x)
  {
#if defined(__GNUC__) || defined(__clang__)
    return __builtin_bswap32(x);
#else
    return simple_swap32(x);
#endif
  }

  static inline uint64_t swap64(uint64_t x)
  {
#if defined(__GNUC__) || defined(__clang__)
    return __builtin_bswap64(x);
#else
    return simple_swap64(x);
#endif
  }

#if FPNG_USE_UNALIGNED_LOADS
#if __BYTE_ORDER == __BIG_ENDIAN
#define READ_LE32(p) swap32(*reinterpret_cast<const uint32_t *>(p))
#define WRITE_LE32(p, v) *reinterpret_cast<uint32_t *>(p) = swap32((uint32_t)(v))
#define WRITE_LE64(p, v) *reinterpret_cast<uint64_t *>(p) = swap64((uint64_t)(v))

#define READ_BE32(p) *reinterpret_cast<const uint32_t *>(p)
#else
#define READ_LE32(p) (*reinterpret_cast<const uint32_t *>(p))
#define WRITE_LE32(p, v) *reinterpret_cast<uint32_t *>(p) = (uint32_t)(v)
#define WRITE_LE64(p, v) *reinterpret_cast<uint64_t *>(p) = (uint64_t)(v)

#define READ_BE32(p) swap32(*reinterpret_cast<const uint32_t *>(p))
#endif
#else
  // A good compiler should be able to optimize these routines - hopefully. They are crucial for performance.
  static inline uint32_t READ_LE32(const void* p)
  {
    const uint8_t* pBytes = (const uint8_t*)p;
    return ((uint32_t)pBytes[0]) | (((uint32_t)pBytes[1]) << 8U) | (((uint32_t)pBytes[2]) << 16U) | (((uint32_t)pBytes[3]) << 24U);
  }

  static inline uint32_t READ_BE32(const void* p)
  {
    const uint8_t* pBytes = (const uint8_t*)p;
    return ((uint32_t)pBytes[3]) | (((uint32_t)pBytes[2]) << 8U) | (((uint32_t)pBytes[1]) << 16U) | (((uint32_t)pBytes[0]) << 24U);
  }

  static inline void WRITE_LE32(const void* p, uint32_t v)
  {
    uint8_t* pBytes = (uint8_t*)p;
    pBytes[0] = (uint8_t)(v);
    pBytes[1] = (uint8_t)(v >> 8);
    pBytes[2] = (uint8_t)(v >> 16);
    pBytes[3] = (uint8_t)(v >> 24);
  }

  static inline void WRITE_LE64(const void* p, uint64_t v)
  {
    uint8_t* pBytes = (uint8_t*)p;
    pBytes[0] = (uint8_t)(v);
    pBytes[1] = (uint8_t)(v >> 8);
    pBytes[2] = (uint8_t)(v >> 16);
    pBytes[3] = (uint8_t)(v >> 24);
    pBytes[4] = (uint8_t)(v >> 32);
    pBytes[5] = (uint8_t)(v >> 40);
    pBytes[6] = (uint8_t)(v >> 48);
    pBytes[7] = (uint8_t)(v >> 56);
  }
#endif

  // Customized the very common case of reading a 24bpp pixel from memory
  static inline uint32_t READ_RGB_PIXEL(const void* p)
  {
#if FPNG_USE_UNALIGNED_LOADS 
    return READ_LE32(p) & 0xFFFFFF;
#else
    const uint8_t* pBytes = (const uint8_t*)p;
    return ((uint32_t)pBytes[0]) | (((uint32_t)pBytes[1]) << 8U) | (((uint32_t)pBytes[2]) << 16U);
#endif
  }

  // See "Slicing by 4" CRC-32 algorithm here: 
  // https://create.stephan-brumme.com/crc32/

  // Precomputed 4KB of CRC-32 tables
  static const uint32_t g_crc32_4[4][256] = {
  {00, 016701630226, 035603460454, 023102250672, 0733342031, 016032572217, 035130722465, 023631112643, 01666704062, 017167134244, 034065364436, 022764554610, 01155446053, 017654276275, 034756026407, 022057616621, 03555610144, 015254020362, 036356270510, 020457440736, 03266552175, 015567362353, 036465132521, 020364702707, 02333114126, 014432724300, 037530574572, 021231344754, 02400256117, 014301466331, 037203636543, 021502006765,
  07333420310, 011432210136, 032530040744, 024231670562, 07400762321, 011301152107, 032203302775, 024502532553, 06555324372, 010254514154, 033356744726, 025457174500, 06266066343, 010567656165, 033465406717, 025364236531, 04666230254, 012167400072, 031065650600, 027764060426, 04155172265, 012654742043, 031756512631, 027057322417, 05000534236, 013701304010, 030603154662, 026102764444, 05733676207, 013032046021, 030130216653, 026631426475,
  016667040620, 0166670406, 023064420274, 035765210052, 016154302611, 0655532437, 023757762245, 035056152063, 017001744642, 01700174464, 022602324216, 034103514030, 017732406673, 01033236455, 022131066227, 034630656001, 015332650764, 03433060542, 020531230330, 036230400116, 015401512755, 03300322573, 020202172301, 036503742127, 014554154706, 02255764520, 021357534352, 037456304174, 014267216737, 02566426511, 021464676363, 037365046145,
  011554460530, 07255250716, 024357000164, 032456630342, 011267722501, 07566112727, 024464342155, 032365572373, 010332364552, 06433554774, 025531704106, 033230134320, 010401026563, 06300616745, 025202446137, 033503276311, 012001270474, 04700440652, 027602610020, 031103020206, 012732132445, 04033702663, 027131552011, 031630362237, 013667574416, 05166344630, 026064114042, 030765724264, 013154636427, 05655006601, 026757256073, 030056466255,
  035556101440, 023257731666, 0355561014, 016454351232, 035265243471, 023564473657, 0466623025, 016367013203, 034330605422, 022431035604, 01533265076, 017232455250, 034403547413, 022302377635, 01200127047, 017501717261, 036003711504, 020702121722, 03600371150, 015101541376, 036730453535, 020031263713, 03133033161, 015632603347, 037665015566, 021164625740, 02066475132, 014767245314, 037156357557, 021657567771, 02755737103, 014054107325,
  032665521750, 024164311576, 07066141304, 011767771122, 032156663761, 024657053547, 07755203335, 011054433113, 033003225732, 025702415514, 06600645366, 010101075140, 033730167703, 025031757525, 06133507357, 010632337171, 031330331614, 027431501432, 04533751240, 012232161066, 031403073625, 027302643403, 04200413271, 012501223057, 030556435676, 026257205450, 05355055222, 013454665004, 030265777647, 026564147461, 05466317213, 013367527035,
  023331141260, 035430771046, 016532521634, 0233311412, 023402203251, 035303433077, 016201663605, 0500053423, 022557645202, 034256075024, 017354225656, 01455415470, 022264507233, 034565337015, 017467167667, 01366757441, 020664751324, 036165161102, 015067331770, 03766501556, 020157413315, 036656223133, 015754073741, 03055643567, 021002055346, 037703665160, 014601435712, 02100205534, 021731317377, 037030527151, 014132777723, 02633147505,
  024002561170, 032703351356, 011601101524, 07100731702, 024731623141, 032030013367, 011132243515, 07633473733, 025664265112, 033165455334, 010067605546, 06766035760, 025157127123, 033656717305, 010754547577, 06055377751, 027557371034, 031256541212, 012354711460, 04455121646, 027264033005, 031565603223, 012467453451, 04366263677, 026331475056, 030430245270, 013532015402, 05233625624, 026402737067, 030303107241, 013201357433, 05500567615,
  }, { 00,03106630501,06215461202,05313251703,014433142404,017535772105,012626523606,011720313307,031066305010,032160535511,037273764212,034375154713,025455247414,026553477115,023640626616,020746016317,011260411121,012366221420,017075070323,014173640622,05653553525,06755363024,03446132727,0540702226,020206714131,023300124430,026013375333,025115545632,034635656535,037733066034,032420237737,031526407236,
  022541022242,021447612743,024754443040,027652273541,036172160646,035074750347,030367501444,033261331145,013527327252,010421517753,015732746050,016634176551,07114265656,04012455357,01301604454,02207034155,033721433363,030627203662,035534052161,036432662460,027312571767,024214341266,021107110565,022001720064,02747736373,01641106672,04552357171,07454567470,016374674777,015272044276,010161215575,013067425074,
  036036247405,035130477104,030223626607,033325016306,022405305001,021503535500,024610764203,027716154702,07050142415,04156772114,01245523617,02343313316,013463000011,010565630510,015676461213,016770251712,027256656524,024350066025,021043237726,022145407227,033665714120,030763124421,035470375322,036576545623,016230553534,015336363035,010025132736,013123702237,02603411130,01705221431,04416070332,07510640633,
  014577265647,017471455346,012762604445,011664034144,0144327243,03042517742,06351746041,05257176540,025511160657,026417750356,023704501455,020602331154,031122022253,032024612752,037337443051,034231273550,05717674766,06611044267,03502215564,0404425065,011324736362,012222106663,017131357160,014037567461,034771571776,037677341277,032564110574,031462720075,020342433372,023244203673,026157052170,025051662471,
  07340714113,04246124412,01155375311,02053545610,013773656517,010675066016,015566237715,016460407214,036326411103,035220221402,030133070301,033035640600,022715553507,021613363006,024500132705,027406702204,016120305032,015026535533,010335764230,013233154731,02513247436,01415477137,04706626634,07600016335,027146000022,024040630523,021353461220,022255251721,033575142426,030473772127,035760523624,036666313325,
  025601736351,026707106650,023414357153,020512567452,031232674755,032334044254,037027215557,034121425056,014667433341,017761203640,012472052143,011574662442,0254571745,03352341244,06041110547,05147720046,034461327270,037567517771,032674746072,031772176573,020052265674,023154455375,026247604476,025341034177,05407022260,06501612761,03612443062,0714273563,011034160664,012132750365,017221501466,014327331167,
  031376553516,032270363017,037163132714,034065702215,025745411112,026643221413,023550070310,020456640611,0310656506,03216066007,06105237704,05003407205,014723714102,017625124403,012536375300,011430545601,020116142437,023010772136,026303523635,025205313334,034525000033,037423630532,032730461231,031636251730,011170247427,012076477126,017365626625,014263016324,05543305023,06445535522,03756764221,0650154720,
  013637571754,010731341255,015422110556,016524720057,07204433350,04302203651,01011052152,02117662453,022651674744,021757044245,024444215546,027542425047,036262736340,035364106641,030077357142,033171567443,02457160675,01551750374,04642501477,07744331176,016064022271,015162612770,010271443073,013377273572,033431265665,030537455364,035624604467,036722034166,027002327261,024104517760,021217746063,022311176562,
  }, { 00,0160465067,0341152156,0221537131,0702324334,0662741353,0443276262,0523613205,01604650670,01764235617,01545702726,01425367741,01106574544,01066111523,01247426412,01327043475,03411521560,03571144507,03750473436,03630016451,03313605654,03273260633,03052757702,03132332765,02215371310,02375714377,02154223246,02034646221,02517055024,02477430043,02656107172,02736562115,
  07023243340,07143626327,07362311216,07202774271,07721167074,07641502013,07460035122,07500450145,06627413530,06747076557,06566541466,06406124401,06125737604,06045352663,06264665752,06304200735,04432762620,04552307647,04773630776,04613255711,04330446514,04250023573,04071514442,04111171425,05236132050,05356557037,05177060106,05017405161,05534216364,05454673303,05675344232,05715721255,
  016046506700,016126163767,016307454656,016267031631,016744622434,016624247453,016405770562,016565315505,017642356170,017722733117,017503204026,017463661041,017140072244,017020417223,017201120312,017361545375,015457027260,015537442207,015716175336,015676510351,015355303154,015235766133,015014251002,015174634065,014253677410,014333212477,014112725546,014072340521,014551553724,014431136743,014610401672,014770064615,
  011065745440,011105320427,011324617516,011244272571,011767461774,011607004713,011426533622,011546156645,010661115230,010701570257,010520047366,010440422301,010163231104,010003654163,010222363052,010342706035,012474264120,012514601147,012735336076,012655753011,012376140214,012216525273,012037012342,012157477325,013270434750,013310051737,013131566606,013051103661,013572710464,013412375403,013633642532,013753227555,
  034115215600,034075670667,034254347756,034334722731,034617131534,034777554553,034556063462,034436406405,035711445070,035671020017,035450517126,035530172141,035013761344,035173304323,035352633212,035232256275,037504734360,037464351307,037645666236,037725203251,037206410054,037366075033,037147542102,037027127165,036300164510,036260501577,036041036446,036121453421,036402240624,036562625643,036743312772,036623777715,
  033136056540,033056433527,033277104416,033317561471,033634372674,033754717613,033575220722,033415645745,032732606330,032652263357,032473754266,032513331201,032030522004,032150147063,032371470152,032211015135,030527577020,030447112047,030666425176,030706040111,030225653314,030345236373,030164701242,030004364225,031323327650,031243742637,031062275706,031102610761,031421003564,031541466503,031760151432,031600534455,
  022153713100,022033376167,022212641056,022372224031,022651437234,022731052253,022510565362,022470100305,023757143770,023637526717,023416011626,023576474641,023055267444,023135602423,023314335512,023274750575,021542232460,021422657407,021603360536,021763705551,021240116754,021320573733,021101044602,021061421665,020346462210,020226007277,020007530346,020167155321,020444746124,020524323143,020705614072,020665271015,
  025170550240,025010135227,025231402316,025351067371,025672674174,025712211113,025533726022,025453343045,024774300430,024614765457,024435252566,024555637501,024076024704,024116441763,024337176652,024257513635,026561071720,026401414747,026620123676,026740546611,026263355414,026303730473,026122207542,026042662525,027365621150,027205244137,027024773006,027144316061,027467505264,027507160203,027726457332,027646032355,
  }, { 00,027057063545,025202344213,02255327756,021730513527,06767570062,04532657734,023565634271,030555024357,017502047612,015757360144,032700303401,011265537670,036232554335,034067673463,013030610126,012006253637,035051230372,037204117424,010253174161,033736740310,014761723655,016534404103,031563467446,022553277560,05504214025,07751133773,020706150236,03263764047,024234707502,026061420254,01036443711,
  024014527476,03043544133,01216663665,026241600320,05724034151,022773057414,020526370342,07571313607,014541503721,033516560264,031743647532,016714624077,035271010206,012226073743,010073354015,037024337550,036012774241,011045717704,013210430052,034247453517,017722267766,030775204223,032520123575,015577140030,06547750116,021510733453,023745414305,04712477640,027277243431,0220220174,02075107622,025022164367,
  023305054075,04352037530,06107310266,021150373723,02435547552,025462524017,027637603741,0660660204,013650070322,034607013667,036452334131,011405357474,032160563605,015137500340,017362627416,030335644153,031303207642,016354264307,014101143451,033156120114,010433714365,037464777620,035631450176,012666433433,01656223515,026601240050,024454167706,03403104243,020166730032,07131753577,05364474221,022333417764,
  07311573403,020346510146,022113637610,05144654355,026421060124,01476003461,03623324337,024674347672,037644557754,010613534211,012446613547,035411670002,016174044273,031123027736,033376300060,014321363525,015317720234,032340743771,030115464027,017142407562,034427233713,013470250256,011625177500,036672114045,025642704163,02615767426,0440440370,027417423635,04172217444,023125274101,021370153657,06327130312,
  035526333073,012571350536,010724077260,037773014725,014216620554,033241643011,031014564747,016043507202,05073317324,022024374661,020271053137,07226030472,024743604603,03714667346,01541540410,026516523155,027520160644,0577103301,02722224457,025775247112,06210473363,021247410626,023012737170,04045754435,017075144513,030022127056,032277200700,015220263245,036745457034,011712434571,013547713227,034510770762,
  011532614405,036565677140,034730550616,013767533353,030202307122,017255364467,015000043331,032057020674,021067630752,06030653217,04265574541,023232517004,0757323275,027700340730,025555067066,02502004523,03534447232,024563424777,026736703021,01761760564,022204154715,05253137250,07006210506,020051273043,033061463165,014036400420,016263727376,031234744633,012751170442,035706113107,037553234651,010504257314,
  016623367006,031674304543,033421023215,014476040750,037113674521,010144617064,012311530732,035346553277,026376343351,01321320614,03174007142,024123064407,07446650676,020411633333,022644514465,05613577120,04625134631,023672157374,021427270422,06470213167,025115427316,02142444653,0317763105,027340700440,034370110566,013327173023,011172254775,036125237230,015440403041,032417460504,030642747252,017615724717,
  032637640470,015660623135,017435504663,030462567326,013107353157,034150330412,036305017344,011352074601,02362664727,025335607262,027160520534,0137543071,023452377200,04405314745,06650033013,021607050556,020631413247,07666470702,05433757054,022464734511,01101100760,026156163225,024303244573,03354227036,010364437110,037333454455,035166773303,012131710646,031454124437,016403147172,014656260624,033601203361,
  } };

  static uint32_t crc32_slice_by_4(const void* pData, size_t data_len, uint32_t cur_crc32 = 0)
  {
    uint32_t crc = ~cur_crc32;
    const uint32_t* pData32 = static_cast<const uint32_t*>(pData);

    for (; data_len >= sizeof(uint32_t); ++pData32, data_len -= 4)
    {
      uint32_t v = READ_LE32(pData32) ^ crc;
      crc = g_crc32_4[0][v >> 24] ^ g_crc32_4[1][(v >> 16) & 0xFF] ^ g_crc32_4[2][(v >> 8) & 0xFF] ^ g_crc32_4[3][v & 0xFF];
    }

    for (const uint8_t* pData8 = reinterpret_cast<const uint8_t*>(pData32); data_len; --data_len)
      crc = (crc >> 8) ^ g_crc32_4[0][(crc & 0xFF) ^ *pData8++];

    return ~crc;
  }

#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE 
  // See Fast CRC Computation for Generic Polynomials Using PCLMULQDQ Instruction":
  // https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
  // Requires PCLMUL and SSE 4.1. This function skips Step 1 (fold by 4) for simplicity/less code.
  static uint32_t crc32_pclmul(const uint8_t* p, size_t size, uint32_t crc)
  {
    assert(size >= 16);

    // See page 22 (bit reflected constants for gzip)
#ifdef _MSC_VER
    static const uint64_t __declspec(align(16))
#else
    static const uint64_t __attribute__((aligned(16)))
#endif
      s_u[2] = { 0x1DB710641, 0x1F7011641 }, s_k5k0[2] = { 0x163CD6124, 0 }, s_k3k4[2] = { 0x1751997D0, 0xCCAA009E };

    // Load first 16 bytes, apply initial CRC32
    __m128i b = _mm_xor_si128(_mm_cvtsi32_si128(~crc), _mm_loadu_si128(reinterpret_cast<const __m128i*>(p)));

    // We're skipping directly to Step 2 page 12 - iteratively folding by 1 (by 4 is overkill for our needs)
    const __m128i k3k4 = _mm_load_si128(reinterpret_cast<const __m128i*>(s_k3k4));

    for (size -= 16, p += 16; size >= 16; size -= 16, p += 16)
      b = _mm_xor_si128(_mm_xor_si128(_mm_clmulepi64_si128(b, k3k4, 17), _mm_loadu_si128(reinterpret_cast<const __m128i*>(p))), _mm_clmulepi64_si128(b, k3k4, 0));

    // Final stages: fold to 64-bits, 32-bit Barrett reduction
    const __m128i z = _mm_set_epi32(0, ~0, 0, ~0), u = _mm_load_si128(reinterpret_cast<const __m128i*>(s_u));
    b = _mm_xor_si128(_mm_srli_si128(b, 8), _mm_clmulepi64_si128(b, k3k4, 16));
    b = _mm_xor_si128(_mm_clmulepi64_si128(_mm_and_si128(b, z), _mm_loadl_epi64(reinterpret_cast<const __m128i*>(s_k5k0)), 0), _mm_srli_si128(b, 4));
    return ~_mm_extract_epi32(_mm_xor_si128(b, _mm_clmulepi64_si128(_mm_and_si128(_mm_clmulepi64_si128(_mm_and_si128(b, z), u, 16), z), u, 0)), 1);
  }

  static uint32_t crc32_sse41_simd(const unsigned char* buf, size_t len, uint32_t prev_crc32)
  {
    if (len < 16)
      return crc32_slice_by_4(buf, len, prev_crc32);

    uint32_t simd_len = len & ~15;
    uint32_t c = crc32_pclmul(buf, simd_len, prev_crc32);
    return crc32_slice_by_4(buf + simd_len, len - simd_len, c);
  }
#endif

#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE 

#ifndef _MSC_VER
  static void do_cpuid(uint32_t eax, uint32_t ecx, uint32_t* regs)
  {
    uint32_t ebx = 0, edx = 0;

#if defined(__PIC__) && defined(__i386__)
    __asm__("movl %%ebx, %%edi;"
      "cpuid;"
      "xchgl %%ebx, %%edi;"
      : "=D"(ebx), "+a"(eax), "+c"(ecx), "=d"(edx));
#else
    __asm__("cpuid;" : "+b"(ebx), "+a"(eax), "+c"(ecx), "=d"(edx));
#endif

    regs[0] = eax; regs[1] = ebx; regs[2] = ecx; regs[3] = edx;
  }
#endif

  struct cpu_info
  {
    cpu_info() { memset(this, 0, sizeof(*this)); }

    bool m_initialized, m_has_fpu, m_has_mmx, m_has_sse, m_has_sse2, m_has_sse3, m_has_ssse3, m_has_sse41, m_has_sse42, m_has_avx, m_has_avx2, m_has_pclmulqdq;

    void init()
    {
      if (m_initialized)
        return;

      int regs[4];

#ifdef _MSC_VER
      __cpuid(regs, 0);
#else
      do_cpuid(0, 0, (uint32_t*)regs);
#endif

      const uint32_t max_eax = regs[0];
      if (max_eax >= 1U)
      {
#ifdef _MSC_VER
        __cpuid(regs, 1);
#else
        do_cpuid(1, 0, (uint32_t*)regs);
#endif
        extract_x86_flags(regs[2], regs[3]);
      }

      if (max_eax >= 7U)
      {
#ifdef _MSC_VER
        __cpuidex(regs, 7, 0);
#else
        do_cpuid(7, 0, (uint32_t*)regs);
#endif
        extract_x86_extended_flags(regs[1]);
      }

      m_initialized = true;
    }

    bool can_use_sse41() const { return m_has_sse && m_has_sse2 && m_has_sse3 && m_has_ssse3 && m_has_sse41; }
    bool can_use_pclmul() const { return m_has_pclmulqdq && can_use_sse41(); }

  private:
    void extract_x86_flags(uint32_t ecx, uint32_t edx)
    {
      m_has_fpu = (edx & (1 << 0)) != 0;	m_has_mmx = (edx & (1 << 23)) != 0;	m_has_sse = (edx & (1 << 25)) != 0; m_has_sse2 = (edx & (1 << 26)) != 0;
      m_has_sse3 = (ecx & (1 << 0)) != 0; m_has_ssse3 = (ecx & (1 << 9)) != 0; m_has_sse41 = (ecx & (1 << 19)) != 0; m_has_sse42 = (ecx & (1 << 20)) != 0;
      m_has_pclmulqdq = (ecx & (1 << 1)) != 0; m_has_avx = (ecx & (1 << 28)) != 0;
    }

    void extract_x86_extended_flags(uint32_t ebx) { m_has_avx2 = (ebx & (1 << 5)) != 0; }
  };

  cpu_info g_cpu_info;

  void fpng_init()
  {
    g_cpu_info.init();
  }
#else
  void fpng_init()
  {
  }
#endif

  bool fpng_cpu_supports_sse41()
  {
#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE 
    assert(g_cpu_info.m_initialized);
    return g_cpu_info.can_use_sse41();
#else
    return false;
#endif
  }

  uint32_t fpng_crc32(const void* pData, size_t size, uint32_t prev_crc32)
  {
#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE 
    if (g_cpu_info.can_use_pclmul())
      return crc32_sse41_simd(static_cast<const uint8_t*>(pData), size, prev_crc32);
#endif

    return crc32_slice_by_4(pData, size, prev_crc32);
  }

#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE 
  // See "Fast Computation of Adler32 Checksums":
  // https://www.intel.com/content/www/us/en/developer/articles/technical/fast-computation-of-adler32-checksums.html
  // SSE 4.1, 16 bytes per iteration
  static uint32_t adler32_sse_16(const uint8_t* p, size_t len, uint32_t initial)
  {
    uint32_t s1 = initial & 0xFFFF, s2 = initial >> 16;
    const uint32_t K = 65521;

    while (len >= 16)
    {
      __m128i a = _mm_setr_epi32(s1, 0, 0, 0), b = _mm_setzero_si128(), c = _mm_setzero_si128(), d = _mm_setzero_si128(),
        e = _mm_setzero_si128(), f = _mm_setzero_si128(), g = _mm_setzero_si128(), h = _mm_setzero_si128();

      const size_t n = minimum<size_t>(len >> 4, 5552);

      for (size_t i = 0; i < n; i++)
      {
        const __m128i v = _mm_loadu_si128((const __m128i*)(p + i * 16));
        a = _mm_add_epi32(a, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(0, 0, 0, 0)))); b = _mm_add_epi32(b, a);
        c = _mm_add_epi32(c, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(1, 1, 1, 1)))); d = _mm_add_epi32(d, c);
        e = _mm_add_epi32(e, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(2, 2, 2, 2)))); f = _mm_add_epi32(f, e);
        g = _mm_add_epi32(g, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(3, 3, 3, 3)))); h = _mm_add_epi32(h, g);
      }

      uint32_t sa[16], sb[16];
      _mm_storeu_si128((__m128i*)sa, a); _mm_storeu_si128((__m128i*)(sa + 4), c);
      _mm_storeu_si128((__m128i*)sb, b); _mm_storeu_si128((__m128i*)(sb + 4), d);
      _mm_storeu_si128((__m128i*)(sa + 8), e); _mm_storeu_si128((__m128i*)(sa + 12), g);
      _mm_storeu_si128((__m128i*)(sb + 8), f); _mm_storeu_si128((__m128i*)(sb + 12), h);

      // This could be vectorized, but it's only executed every 5552*16 iterations.
      uint64_t vs1 = 0;
      for (uint32_t i = 0; i < 16; i++)
        vs1 += sa[i];

      uint64_t vs2_a = 0;
      for (uint32_t i = 0; i < 16; i++)
        vs2_a += sa[i] * (uint64_t)i;
      uint64_t vs2_b = 0;
      for (uint32_t i = 0; i < 16; i++)
        vs2_b += sb[i];
      vs2_b *= 16U;
      uint64_t vs2 = vs2_b - vs2_a + s2;

      s1 = (uint32_t)(vs1 % K);
      s2 = (uint32_t)(vs2 % K);

      p += n * 16;
      len -= n * 16;
    }

    for (; len; len--)
    {
      s1 += *p++;
      s2 += s1;
    }

    return (s1 % K) | ((s2 % K) << 16);
  }
#endif

  static uint32_t fpng_adler32_scalar(const uint8_t* ptr, size_t buf_len, uint32_t adler)
  {
    uint32_t i, s1 = (uint32_t)(adler & 0xffff), s2 = (uint32_t)(adler >> 16); uint32_t block_len = (uint32_t)(buf_len % 5552);
    if (!ptr) return FPNG_ADLER32_INIT;
    while (buf_len) {
      for (i = 0; i + 7 < block_len; i += 8, ptr += 8) {
        s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1;
        s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1;
      }
      for (; i < block_len; ++i) s1 += *ptr++, s2 += s1;
      s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552;
    }
    return (s2 << 16) + s1;
  }

  uint32_t fpng_adler32(const void* pData, size_t size, uint32_t adler)
  {
#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE 
    if (g_cpu_info.can_use_sse41())
      return adler32_sse_16((const uint8_t*)pData, size, adler);
#endif
    return fpng_adler32_scalar((const uint8_t*)pData, size, adler);
  }

  // Ensure we've been configured for endianness correctly.
  static inline bool endian_check()
  {
    uint32_t endian_check = 0;
    WRITE_LE32(&endian_check, 0x1234ABCD);
    const uint32_t first_byte = reinterpret_cast<const uint8_t*>(&endian_check)[0];
    return first_byte == 0xCD;
  }

  static const uint16_t g_defl_len_sym[256] = {
    257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272,
    273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276,
    277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,
    279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,
    281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,
    282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,
    283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,
    284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 };

  static const uint8_t g_defl_len_extra[256] = {
    0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
    4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
    5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
    5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 };

  static const uint8_t g_defl_small_dist_sym[512] = {
    0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11,
    11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,
    13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14,
    14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
    14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
    15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16,
    16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
    16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
    16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
    17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
    17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
    17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 };

  static const uint32_t g_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };

  // Huffman tables generated by fpng_test -t @filelist.txt. Total alpha files : 1440, Total opaque files : 5627.
  // Feel free to retrain the encoder on your opaque/alpha PNG files by setting FPNG_TRAIN_HUFFMAN_TABLES and running fpng_test with the -t option.
  static const uint8_t g_dyn_huff_3[] = {
  120, 1, 237, 195, 3, 176, 110, 89, 122, 128, 225, 247, 251, 214, 218, 248, 113, 124, 173, 190, 109, 12, 50, 201, 196, 182, 109, 219, 182, 109, 219, 182,
  109, 219, 201, 36, 147, 153, 105, 235, 246, 53, 142, 207, 143, 141, 181, 214, 151, 93, 117, 170, 78, 117, 117, 58, 206, 77, 210, 217, 169, 122 };
  const uint32_t DYN_HUFF_3_BITBUF = 30, DYN_HUFF_3_BITBUF_SIZE = 7;
  static const struct { uint8_t m_code_size; uint16_t m_code; } g_dyn_huff_3_codes[288] = {
  {2,0},{4,2},{4,10},{5,14},{5,30},{6,25},{6,57},{6,5},{6,37},{7,3},{7,67},{7,35},{7,99},{8,11},{8,139},{8,75},{8,203},{8,43},{8,171},{8,107},{9,135},{9,391},{9,71},{9,327},{9,199},{9,455},{9,39},{9,295},{9,167},{9,423},{9,103},{10,183},
  {9,359},{10,695},{10,439},{10,951},{10,119},{10,631},{10,375},{10,887},{10,247},{10,759},{10,503},{11,975},{11,1999},{11,47},{11,1071},{12,1199},{11,559},{12,3247},{12,687},{11,1583},{12,2735},{12,1711},{12,3759},{12,431},{12,2479},{12,1455},{12,3503},{12,943},{12,2991},{12,1967},{12,4015},{12,111},
  {12,2159},{12,1135},{12,3183},{12,623},{12,2671},{12,1647},{12,3695},{12,367},{12,2415},{12,1391},{12,3439},{12,879},{12,2927},{12,1903},{12,3951},{12,239},{12,2287},{12,1263},{12,3311},{12,751},{12,2799},{12,1775},{12,3823},{12,495},{12,2543},{12,1519},{12,3567},{12,1007},{12,3055},{12,2031},{12,4079},{12,31},
  {12,2079},{12,1055},{12,3103},{12,543},{12,2591},{12,1567},{12,3615},{12,287},{12,2335},{12,1311},{12,3359},{12,799},{12,2847},{12,1823},{12,3871},{12,159},{12,2207},{12,1183},{12,3231},{12,671},{12,2719},{12,1695},{12,3743},{12,415},{12,2463},{12,1439},{12,3487},{12,927},{12,2975},{12,1951},{12,3999},{12,95},
  {12,2143},{12,1119},{12,3167},{12,607},{12,2655},{12,1631},{12,3679},{12,351},{12,2399},{12,1375},{12,3423},{12,863},{12,2911},{12,1887},{12,3935},{12,223},{12,2271},{12,1247},{12,3295},{12,735},{12,2783},{12,1759},{12,3807},{12,479},{12,2527},{12,1503},{12,3551},{12,991},{12,3039},{12,2015},{12,4063},{12,63},
  {12,2111},{12,1087},{12,3135},{12,575},{12,2623},{12,1599},{12,3647},{12,319},{12,2367},{12,1343},{12,3391},{12,831},{12,2879},{12,1855},{12,3903},{12,191},{12,2239},{12,1215},{12,3263},{12,703},{12,2751},{12,1727},{12,3775},{12,447},{12,2495},{12,1471},{12,3519},{12,959},{12,3007},{12,1983},{12,4031},{12,127},
  {12,2175},{12,1151},{12,3199},{12,639},{12,2687},{12,1663},{12,3711},{12,383},{12,2431},{12,1407},{12,3455},{12,895},{12,2943},{11,303},{12,1919},{12,3967},{11,1327},{12,255},{11,815},{11,1839},{11,175},{10,1015},{10,15},{10,527},{10,271},{10,783},{10,143},{10,655},{10,399},{10,911},{10,79},{10,591},
  {9,231},{10,335},{9,487},{9,23},{9,279},{9,151},{9,407},{9,87},{9,343},{9,215},{9,471},{9,55},{8,235},{8,27},{8,155},{8,91},{8,219},{8,59},{8,187},{8,123},{7,19},{7,83},{7,51},{7,115},{6,21},{6,53},{6,13},{6,45},{5,1},{5,17},{5,9},{4,6},
  {12,2303},{6,29},{0,0},{0,0},{8,251},{0,0},{0,0},{8,7},{0,0},{10,847},{0,0},{10,207},{12,1279},{10,719},{12,3327},{12,767},{12,2815},{12,1791},{12,3839},{12,511},{12,2559},{12,1535},{9,311},{12,3583},{12,1023},{12,3071},{10,463},{12,2047},{6,61},{12,4095},{0,0},{0,0}
  };

  static const uint8_t g_dyn_huff_4[] = {
  120, 1, 229, 196, 99, 180, 37, 103, 218, 128, 225, 251, 121, 171, 106, 243, 216, 231, 180, 109, 196, 182, 51, 51, 73, 6, 201, 216, 182, 109, 219, 182,
  17, 140, 98, 219, 102, 219, 60, 125, 172, 205, 170, 122, 159, 111, 213, 143, 179, 214, 94, 189, 58, 153, 104, 166, 103, 190, 247, 199, 117 };
  const uint32_t DYN_HUFF_4_BITBUF = 1, DYN_HUFF_4_BITBUF_SIZE = 2;
  static const struct { uint8_t m_code_size; uint16_t m_code; } g_dyn_huff_4_codes[288] = {
  {2,0},{4,2},{5,6},{6,30},{6,62},{6,1},{7,41},{7,105},{7,25},{7,89},{7,57},{7,121},{8,117},{8,245},{8,13},{8,141},{8,77},{8,205},{8,45},{8,173},{8,109},{8,237},{8,29},{8,157},{8,93},{8,221},{8,61},{9,83},{9,339},{9,211},{9,467},{9,51},
  {9,307},{9,179},{9,435},{9,115},{9,371},{9,243},{9,499},{9,11},{9,267},{9,139},{9,395},{9,75},{9,331},{9,203},{9,459},{9,43},{9,299},{10,7},{10,519},{10,263},{10,775},{10,135},{10,647},{10,391},{10,903},{10,71},{10,583},{10,327},{10,839},{10,199},{10,711},{10,455},
  {10,967},{10,39},{10,551},{10,295},{10,807},{10,167},{10,679},{10,423},{10,935},{10,103},{10,615},{11,463},{11,1487},{11,975},{10,359},{10,871},{10,231},{11,1999},{11,47},{11,1071},{11,559},{10,743},{10,487},{11,1583},{11,303},{11,1327},{11,815},{11,1839},{11,175},{11,1199},{11,687},{11,1711},
  {11,431},{11,1455},{11,943},{11,1967},{11,111},{11,1135},{11,623},{11,1647},{11,367},{11,1391},{11,879},{11,1903},{11,239},{11,1263},{11,751},{11,1775},{11,495},{11,1519},{11,1007},{11,2031},{11,31},{11,1055},{11,543},{11,1567},{11,287},{11,1311},{11,799},{11,1823},{11,159},{11,1183},{11,671},{11,1695},
  {11,415},{11,1439},{11,927},{11,1951},{11,95},{11,1119},{11,607},{11,1631},{11,351},{11,1375},{11,863},{11,1887},{11,223},{11,1247},{11,735},{11,1759},{11,479},{11,1503},{11,991},{11,2015},{11,63},{11,1087},{11,575},{11,1599},{11,319},{11,1343},{11,831},{11,1855},{11,191},{11,1215},{11,703},{11,1727},
  {11,447},{11,1471},{11,959},{11,1983},{11,127},{11,1151},{11,639},{11,1663},{11,383},{10,999},{10,23},{10,535},{10,279},{11,1407},{11,895},{11,1919},{11,255},{11,1279},{10,791},{10,151},{10,663},{10,407},{10,919},{10,87},{10,599},{10,343},{10,855},{10,215},{10,727},{10,471},{10,983},{10,55},
  {10,567},{10,311},{10,823},{10,183},{10,695},{10,439},{10,951},{10,119},{10,631},{10,375},{10,887},{10,247},{10,759},{10,503},{10,1015},{10,15},{10,527},{10,271},{10,783},{10,143},{10,655},{10,399},{9,171},{9,427},{9,107},{9,363},{9,235},{9,491},{9,27},{9,283},{9,155},{9,411},
  {9,91},{9,347},{9,219},{9,475},{9,59},{9,315},{9,187},{9,443},{8,189},{9,123},{8,125},{8,253},{8,3},{8,131},{8,67},{8,195},{8,35},{8,163},{8,99},{8,227},{8,19},{7,5},{7,69},{7,37},{7,101},{7,21},{7,85},{6,33},{6,17},{6,49},{5,22},{4,10},
  {12,2047},{0,0},{6,9},{0,0},{0,0},{0,0},{8,147},{0,0},{0,0},{7,53},{0,0},{9,379},{0,0},{9,251},{10,911},{10,79},{11,767},{10,591},{10,335},{10,847},{10,207},{10,719},{11,1791},{11,511},{9,507},{11,1535},{11,1023},{12,4095},{5,14},{0,0},{0,0},{0,0}
  };

#define PUT_BITS(bb, ll) do { uint32_t b = bb, l = ll; assert((l) >= 0 && (l) <= 16); assert((b) < (1ULL << (l))); bit_buf |= (((uint64_t)(b)) << bit_buf_size); bit_buf_size += (l); assert(bit_buf_size <= 64); } while(0)
#define PUT_BITS_CZ(bb, ll) do { uint32_t b = bb, l = ll; assert((l) >= 1 && (l) <= 16); assert((b) < (1ULL << (l))); bit_buf |= (((uint64_t)(b)) << bit_buf_size); bit_buf_size += (l); assert(bit_buf_size <= 64); } while(0)

#define PUT_BITS_FLUSH do { \
  if ((dst_ofs + 8) > dst_buf_size) \
    return 0; \
  WRITE_LE64(pDst + dst_ofs, bit_buf); \
  uint32_t bits_to_shift = bit_buf_size & ~7; \
  dst_ofs += (bits_to_shift >> 3); \
  assert(bits_to_shift < 64); \
  bit_buf = bit_buf >> bits_to_shift; \
  bit_buf_size -= bits_to_shift; \
} while(0)

#define PUT_BITS_FORCE_FLUSH do { \
  while (bit_buf_size > 0) \
  { \
    if ((dst_ofs + 1) > dst_buf_size) \
      return 0; \
    *(uint8_t*)(pDst + dst_ofs) = (uint8_t)bit_buf; \
    dst_ofs++; \
    bit_buf >>= 8; \
    bit_buf_size -= 8; \
  } \
} while(0)

  enum
  {
    DEFL_MAX_HUFF_TABLES = 3,
    DEFL_MAX_HUFF_SYMBOLS = 288,
    DEFL_MAX_HUFF_SYMBOLS_0 = 288,
    DEFL_MAX_HUFF_SYMBOLS_1 = 32,
    DEFL_MAX_HUFF_SYMBOLS_2 = 19,
    DEFL_LZ_DICT_SIZE = 32768,
    DEFL_LZ_DICT_SIZE_MASK = DEFL_LZ_DICT_SIZE - 1,
    DEFL_MIN_MATCH_LEN = 3,
    DEFL_MAX_MATCH_LEN = 258
  };

#if FPNG_TRAIN_HUFFMAN_TABLES
  uint64_t g_huff_counts[HUFF_COUNTS_SIZE];
#endif

  struct defl_huff
  {
    uint16_t m_huff_count[DEFL_MAX_HUFF_TABLES][DEFL_MAX_HUFF_SYMBOLS];
    uint16_t m_huff_codes[DEFL_MAX_HUFF_TABLES][DEFL_MAX_HUFF_SYMBOLS];
    uint8_t m_huff_code_sizes[DEFL_MAX_HUFF_TABLES][DEFL_MAX_HUFF_SYMBOLS];
  };

  struct defl_sym_freq
  {
    uint16_t m_key;
    uint16_t m_sym_index;
  };

#define DEFL_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj))

  static defl_sym_freq* defl_radix_sort_syms(uint32_t num_syms, defl_sym_freq* pSyms0, defl_sym_freq* pSyms1)
  {
    uint32_t total_passes = 2, pass_shift, pass, i, hist[256 * 2]; defl_sym_freq* pCur_syms = pSyms0, * pNew_syms = pSyms1; DEFL_CLEAR_OBJ(hist);
    for (i = 0; i < num_syms; i++) { uint32_t freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; }
    while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--;
    for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8)
    {
      const uint32_t* pHist = &hist[pass << 8];
      uint32_t offsets[256], cur_ofs = 0;
      for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; }
      for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i];
      { defl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; }
    }
    return pCur_syms;
  }

  // defl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996.
  static void defl_calculate_minimum_redundancy(defl_sym_freq* A, int n)
  {
    int root, leaf, next, avbl, used, dpth;
    if (n == 0) return; else if (n == 1) { A[0].m_key = 1; return; }
    A[0].m_key += A[1].m_key; root = 0; leaf = 2;
    for (next = 1; next < n - 1; next++)
    {
      if (leaf >= n || A[root].m_key < A[leaf].m_key) { A[next].m_key = A[root].m_key; A[root++].m_key = (uint16_t)next; }
      else A[next].m_key = A[leaf++].m_key;
      if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { A[next].m_key = (uint16_t)(A[next].m_key + A[root].m_key); A[root++].m_key = (uint16_t)next; }
      else A[next].m_key = (uint16_t)(A[next].m_key + A[leaf++].m_key);
    }
    A[n - 2].m_key = 0; for (next = n - 3; next >= 0; next--) A[next].m_key = A[A[next].m_key].m_key + 1;
    avbl = 1; used = dpth = 0; root = n - 2; next = n - 1;
    while (avbl > 0)
    {
      while (root >= 0 && (int)A[root].m_key == dpth) { used++; root--; }
      while (avbl > used) { A[next--].m_key = (uint16_t)(dpth); avbl--; }
      avbl = 2 * used; dpth++; used = 0;
    }
  }

  // Limits canonical Huffman code table's max code size.
  enum { DEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 };
  static void defl_huffman_enforce_max_code_size(int* pNum_codes, int code_list_len, int max_code_size)
  {
    int i; uint32_t total = 0; if (code_list_len <= 1) return;
    for (i = max_code_size + 1; i <= DEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i];
    for (i = max_code_size; i > 0; i--) total += (((uint32_t)pNum_codes[i]) << (max_code_size - i));
    while (total != (1UL << max_code_size))
    {
      pNum_codes[max_code_size]--;
      for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; }
      total--;
    }
  }

  static void defl_optimize_huffman_table(defl_huff* d, int table_num, int table_len, int code_size_limit, int static_table)
  {
    int i, j, l, num_codes[1 + DEFL_MAX_SUPPORTED_HUFF_CODESIZE]; uint32_t next_code[DEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; DEFL_CLEAR_OBJ(num_codes);
    if (static_table)
    {
      for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++;
    }
    else
    {
      defl_sym_freq syms0[DEFL_MAX_HUFF_SYMBOLS], syms1[DEFL_MAX_HUFF_SYMBOLS], * pSyms;
      int num_used_syms = 0;
      const uint16_t* pSym_count = &d->m_huff_count[table_num][0];
      for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (uint16_t)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (uint16_t)i; }

      pSyms = defl_radix_sort_syms(num_used_syms, syms0, syms1); defl_calculate_minimum_redundancy(pSyms, num_used_syms);

      for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++;

      defl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit);

      DEFL_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); DEFL_CLEAR_OBJ(d->m_huff_codes[table_num]);
      for (i = 1, j = num_used_syms; i <= code_size_limit; i++)
        for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (uint8_t)(i);
    }

    next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1);

    for (i = 0; i < table_len; i++)
    {
      uint32_t rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue;
      code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1);
      d->m_huff_codes[table_num][i] = (uint16_t)rev_code;
    }
  }

#define DEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \
  if (rle_repeat_count < 3) { \
    d->m_huff_count[2][prev_code_size] = (uint16_t)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \
    while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \
  } else { \
    d->m_huff_count[2][16] = (uint16_t)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (uint8_t)(rle_repeat_count - 3); \
} rle_repeat_count = 0; } }

#define DEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \
  if (rle_z_count < 3) { \
    d->m_huff_count[2][0] = (uint16_t)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \
  } else if (rle_z_count <= 10) { \
    d->m_huff_count[2][17] = (uint16_t)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (uint8_t)(rle_z_count - 3); \
  } else { \
    d->m_huff_count[2][18] = (uint16_t)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (uint8_t)(rle_z_count - 11); \
} rle_z_count = 0; } }

  static uint8_t g_defl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };

#define DEFL_DYN_PUT_BITS(bb, ll) \
do { \
  uint32_t b = (bb), l = (ll); \
  assert((l) >= 1 && (l) <= 16); assert((b) < (1ULL << (l))); \
  bit_buf |= (((uint64_t)(b)) << bit_buf_size); bit_buf_size += (l); assert(bit_buf_size <= 64); \
  while (bit_buf_size >= 8) \
  { \
    if ((dst_ofs + 1) > dst_buf_size) \
      return false; \
    *(uint8_t*)(pDst + dst_ofs) = (uint8_t)bit_buf; \
    dst_ofs++; \
    bit_buf >>= 8; \
    bit_buf_size -= 8; \
  } \
} while(0)

  static bool defl_start_dynamic_block(defl_huff* d, uint8_t* pDst, uint32_t& dst_ofs, uint32_t dst_buf_size, uint64_t& bit_buf, int& bit_buf_size)
  {
    int num_lit_codes, num_dist_codes, num_bit_lengths; uint32_t i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index;
    uint8_t code_sizes_to_pack[DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF;

#if FPNG_TRAIN_HUFFMAN_TABLES
    assert(HUFF_COUNTS_SIZE == DEFL_MAX_HUFF_SYMBOLS_0);
    for (uint32_t i = 0; i < DEFL_MAX_HUFF_SYMBOLS_0; i++)
      g_huff_counts[i] += d->m_huff_count[0][i];
#endif

    d->m_huff_count[0][256] = 1;

    defl_optimize_huffman_table(d, 0, DEFL_MAX_HUFF_SYMBOLS_0, 12, FPNG_FALSE);
    defl_optimize_huffman_table(d, 1, DEFL_MAX_HUFF_SYMBOLS_1, 12, FPNG_FALSE);

    for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break;
    for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break;

    memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes);
    memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes);
    total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0;

    memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * DEFL_MAX_HUFF_SYMBOLS_2);
    for (i = 0; i < total_code_sizes_to_pack; i++)
    {
      uint8_t code_size = code_sizes_to_pack[i];
      if (!code_size)
      {
        DEFL_RLE_PREV_CODE_SIZE();
        if (++rle_z_count == 138) { DEFL_RLE_ZERO_CODE_SIZE(); }
      }
      else
      {
        DEFL_RLE_ZERO_CODE_SIZE();
        if (code_size != prev_code_size)
        {
          DEFL_RLE_PREV_CODE_SIZE();
          d->m_huff_count[2][code_size] = (uint16_t)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size;
        }
        else if (++rle_repeat_count == 6)
        {
          DEFL_RLE_PREV_CODE_SIZE();
        }
      }
      prev_code_size = code_size;
    }
    if (rle_repeat_count) { DEFL_RLE_PREV_CODE_SIZE(); }
    else { DEFL_RLE_ZERO_CODE_SIZE(); }

    defl_optimize_huffman_table(d, 2, DEFL_MAX_HUFF_SYMBOLS_2, 7, FPNG_FALSE);

    // max of 2+5+5+4+18*3+(288+32)*7=2310 bits
    DEFL_DYN_PUT_BITS(2, 2);

    DEFL_DYN_PUT_BITS(num_lit_codes - 257, 5);
    DEFL_DYN_PUT_BITS(num_dist_codes - 1, 5);

    for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][g_defl_packed_code_size_syms_swizzle[num_bit_lengths]]) break;
    num_bit_lengths = maximum<int>(4, (num_bit_lengths + 1)); DEFL_DYN_PUT_BITS(num_bit_lengths - 4, 4);
    for (i = 0; (int)i < num_bit_lengths; i++) DEFL_DYN_PUT_BITS(d->m_huff_code_sizes[2][g_defl_packed_code_size_syms_swizzle[i]], 3);

    for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; )
    {
      uint32_t code = packed_code_sizes[packed_code_sizes_index++]; assert(code < DEFL_MAX_HUFF_SYMBOLS_2);
      DEFL_DYN_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]);
      if (code >= 16) DEFL_DYN_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]);
    }

    return true;
  }

  static uint32_t write_raw_block(const uint8_t* pSrc, uint32_t src_len, uint8_t* pDst, uint32_t dst_buf_size)
  {
    if (dst_buf_size < 2)
      return 0;

    pDst[0] = 0x78;
    pDst[1] = 0x01;

    uint32_t dst_ofs = 2;

    uint32_t src_ofs = 0;
    while (src_ofs < src_len)
    {
      const uint32_t src_remaining = src_len - src_ofs;
      const uint32_t block_size = minimum<uint32_t>(UINT16_MAX, src_remaining);
      const bool final_block = (block_size == src_remaining);

      if ((dst_ofs + 5 + block_size) > dst_buf_size)
        return 0;

      pDst[dst_ofs + 0] = final_block ? 1 : 0;

      pDst[dst_ofs + 1] = block_size & 0xFF;
      pDst[dst_ofs + 2] = (block_size >> 8) & 0xFF;

      pDst[dst_ofs + 3] = (~block_size) & 0xFF;
      pDst[dst_ofs + 4] = ((~block_size) >> 8) & 0xFF;

      memcpy(pDst + dst_ofs + 5, pSrc + src_ofs, block_size);

      src_ofs += block_size;
      dst_ofs += 5 + block_size;
    }

    uint32_t src_adler32 = fpng_adler32(pSrc, src_len, FPNG_ADLER32_INIT);

    for (uint32_t i = 0; i < 4; i++)
    {
      if (dst_ofs + 1 > dst_buf_size)
        return 0;

      pDst[dst_ofs] = (uint8_t)(src_adler32 >> 24);
      dst_ofs++;

      src_adler32 <<= 8;
    }

    return dst_ofs;
  }

  static void adjust_freq32(uint32_t num_freq, uint32_t* pFreq, uint16_t* pFreq16)
  {
    uint32_t total_freq = 0;
    for (uint32_t i = 0; i < num_freq; i++)
      total_freq += pFreq[i];

    if (!total_freq)
    {
      memset(pFreq16, 0, num_freq * sizeof(uint16_t));
      return;
    }

    uint32_t total_freq16 = 0;
    for (uint32_t i = 0; i < num_freq; i++)
    {
      uint64_t f = pFreq[i];
      if (!f)
      {
        pFreq16[i] = 0;
        continue;
      }

      pFreq16[i] = (uint16_t)maximum<uint32_t>(1, (uint32_t)((f * UINT16_MAX) / total_freq));

      total_freq16 += pFreq16[i];
    }

    while (total_freq16 > UINT16_MAX)
    {
      total_freq16 = 0;
      for (uint32_t i = 0; i < num_freq; i++)
      {
        if (pFreq[i])
        {
          pFreq[i] = maximum<uint32_t>(1, pFreq[i] >> 1);
          total_freq16 += pFreq[i];
        }
      }
    }
  }

#if FPNG_TRAIN_HUFFMAN_TABLES
  bool create_dynamic_block_prefix(uint64_t* pFreq, uint32_t num_chans, std::vector<uint8_t>& prefix, uint64_t& bit_buf, int& bit_buf_size, uint32_t* pCodes, uint8_t* pCodesizes)
  {
    assert((num_chans == 3) || (num_chans == 4));
    assert(HUFF_COUNTS_SIZE == DEFL_MAX_HUFF_SYMBOLS_0); // must be equal

    defl_huff dh;
    memset(&dh, 0, sizeof(dh));

    uint32_t lit_freq[DEFL_MAX_HUFF_SYMBOLS_0];

    uint32_t shift_len = 0;
    for (; ; )
    {
      uint32_t i;
      for (i = 0; i < DEFL_MAX_HUFF_SYMBOLS_0; i++)
      {
        uint64_t f = pFreq[i];
        if (f)
          f = maximum<uint64_t>(1U, f >> shift_len);

        if (f > UINT32_MAX)
          break;

        lit_freq[i] = (uint32_t)pFreq[i];
      }

      if (i == DEFL_MAX_HUFF_SYMBOLS_0)
        break;

      shift_len++;
    }

    // Ensure all valid Deflate literal/EOB/length syms are non-zero, so anything can be coded.
    for (uint32_t i = 0; i <= 256; i++)
    {
      if (!lit_freq[i])
        lit_freq[i] = 1;
    }

    for (uint32_t len = num_chans; len <= DEFL_MAX_MATCH_LEN; len += num_chans)
    {
      uint32_t sym = g_defl_len_sym[len - 3];
      if (!lit_freq[sym])
        lit_freq[sym] = 1;
    }

    adjust_freq32(DEFL_MAX_HUFF_SYMBOLS_0, lit_freq, &dh.m_huff_count[0][0]);

    const uint32_t dist_sym = g_defl_small_dist_sym[num_chans - 1];
    dh.m_huff_count[1][dist_sym] = 1;
    dh.m_huff_count[1][dist_sym + 1] = 1; // to workaround a bug in wuffs decoder

    prefix.resize(4096);
    uint8_t* pDst = prefix.data();
    uint32_t dst_buf_size = (uint32_t)prefix.size();

    uint32_t dst_ofs = 0;

    // zlib header
    PUT_BITS(0x78, 8);
    PUT_BITS(0x01, 8);

    // write BFINAL bit
    PUT_BITS(1, 1);

    if (!defl_start_dynamic_block(&dh, pDst, dst_ofs, dst_buf_size, bit_buf, bit_buf_size))
      return false;

    prefix.resize(dst_ofs);

    for (uint32_t i = 0; i < DEFL_MAX_HUFF_SYMBOLS_0; i++)
    {
      pCodes[i] = dh.m_huff_codes[0][i];
      pCodesizes[i] = dh.m_huff_code_sizes[0][i];
    }

    return true;
  }
#endif

  static uint32_t pixel_deflate_dyn_3_rle(
    const uint8_t* pImg, uint32_t w, uint32_t h,
    uint8_t* pDst, uint32_t dst_buf_size)
  {
    const uint32_t bpl = 1 + w * 3;

    uint64_t bit_buf = 0;
    int bit_buf_size = 0;

    uint32_t dst_ofs = 0;

    // zlib header
    PUT_BITS(0x78, 8);
    PUT_BITS(0x01, 8);

    // write BFINAL bit
    PUT_BITS(1, 1);

    std::vector<uint32_t> codes((w + 1) * h);
    uint32_t* pDst_codes = codes.data();

    uint32_t lit_freq[DEFL_MAX_HUFF_SYMBOLS_0];
    memset(lit_freq, 0, sizeof(lit_freq));

    const uint8_t* pSrc = pImg;
    uint32_t src_ofs = 0;

    uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT);

    const uint32_t dist_sym = g_defl_small_dist_sym[3 - 1];

    for (uint32_t y = 0; y < h; y++)
    {
      const uint32_t end_src_ofs = src_ofs + bpl;

      const uint32_t filter_lit = pSrc[src_ofs++];
      *pDst_codes++ = 1 | (filter_lit << 8);
      lit_freq[filter_lit]++;

      uint32_t prev_lits;

      {
        uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs);

        *pDst_codes++ = lits << 8;

        lit_freq[lits & 0xFF]++;
        lit_freq[(lits >> 8) & 0xFF]++;
        lit_freq[lits >> 16]++;

        src_ofs += 3;

        prev_lits = lits;
      }

      while (src_ofs < end_src_ofs)
      {
        uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs);

        if (lits == prev_lits)
        {
          uint32_t match_len = 3;
          uint32_t max_match_len = minimum<int>(255, (int)(end_src_ofs - src_ofs));

          while (match_len < max_match_len)
          {
            if (READ_RGB_PIXEL(pSrc + src_ofs + match_len) != lits)
              break;
            match_len += 3;
          }

          *pDst_codes++ = match_len - 1;

          uint32_t adj_match_len = match_len - 3;

          lit_freq[g_defl_len_sym[adj_match_len]]++;

          src_ofs += match_len;
        }
        else
        {
          *pDst_codes++ = lits << 8;

          lit_freq[lits & 0xFF]++;
          lit_freq[(lits >> 8) & 0xFF]++;
          lit_freq[lits >> 16]++;

          prev_lits = lits;

          src_ofs += 3;
        }

      } // while (src_ofs < end_src_ofs)

    } // y

    assert(src_ofs == h * bpl);
    const uint32_t total_codes = (uint32_t)(pDst_codes - codes.data());
    assert(total_codes <= codes.size());

    defl_huff dh;

    lit_freq[256] = 1;

    adjust_freq32(DEFL_MAX_HUFF_SYMBOLS_0, lit_freq, &dh.m_huff_count[0][0]);

    memset(&dh.m_huff_count[1][0], 0, sizeof(dh.m_huff_count[1][0]) * DEFL_MAX_HUFF_SYMBOLS_1);
    dh.m_huff_count[1][dist_sym] = 1;
    dh.m_huff_count[1][dist_sym + 1] = 1; // to workaround a bug in wuffs decoder

    if (!defl_start_dynamic_block(&dh, pDst, dst_ofs, dst_buf_size, bit_buf, bit_buf_size))
      return 0;

    assert(bit_buf_size <= 7);
    assert(dh.m_huff_codes[1][dist_sym] == 0 && dh.m_huff_code_sizes[1][dist_sym] == 1);

    for (uint32_t i = 0; i < total_codes; i++)
    {
      uint32_t c = codes[i];

      uint32_t c_type = c & 0xFF;
      if (c_type == 0)
      {
        uint32_t lits = c >> 8;

        PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]);
        lits >>= 8;

        PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]);
        lits >>= 8;

        PUT_BITS_CZ(dh.m_huff_codes[0][lits], dh.m_huff_code_sizes[0][lits]);
      }
      else if (c_type == 1)
      {
        uint32_t lit = c >> 8;
        PUT_BITS_CZ(dh.m_huff_codes[0][lit], dh.m_huff_code_sizes[0][lit]);
      }
      else
      {
        uint32_t match_len = c_type + 1;

        uint32_t adj_match_len = match_len - 3;

        PUT_BITS_CZ(dh.m_huff_codes[0][g_defl_len_sym[adj_match_len]], dh.m_huff_code_sizes[0][g_defl_len_sym[adj_match_len]]);
        PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], g_defl_len_extra[adj_match_len] + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0

        // no need to write the distance code, it's always 0
        //PUT_BITS_CZ(dh.m_huff_codes[1][dist_sym], dh.m_huff_code_sizes[1][dist_sym]);
      }

      // up to 55 bits
      PUT_BITS_FLUSH;
    }

    PUT_BITS_CZ(dh.m_huff_codes[0][256], dh.m_huff_code_sizes[0][256]);

    PUT_BITS_FORCE_FLUSH;

    // Write zlib adler32
    for (uint32_t i = 0; i < 4; i++)
    {
      if ((dst_ofs + 1) > dst_buf_size)
        return 0;
      *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24);
      dst_ofs++;

      src_adler32 <<= 8;
    }

    return dst_ofs;
  }

  static uint32_t pixel_deflate_dyn_3_rle_one_pass(
    const uint8_t* pImg, uint32_t w, uint32_t h,
    uint8_t* pDst, uint32_t dst_buf_size)
  {
    const uint32_t bpl = 1 + w * 3;

    if (dst_buf_size < sizeof(g_dyn_huff_3))
      return false;
    memcpy(pDst, g_dyn_huff_3, sizeof(g_dyn_huff_3));
    uint32_t dst_ofs = sizeof(g_dyn_huff_3);

    uint64_t bit_buf = DYN_HUFF_3_BITBUF;
    int bit_buf_size = DYN_HUFF_3_BITBUF_SIZE;

    const uint8_t* pSrc = pImg;
    uint32_t src_ofs = 0;

    uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT);

    for (uint32_t y = 0; y < h; y++)
    {
      const uint32_t end_src_ofs = src_ofs + bpl;

      const uint32_t filter_lit = pSrc[src_ofs++];
      PUT_BITS_CZ(g_dyn_huff_3_codes[filter_lit].m_code, g_dyn_huff_3_codes[filter_lit].m_code_size);

      uint32_t prev_lits;

      {
        uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs);

        PUT_BITS_CZ(g_dyn_huff_3_codes[lits & 0xFF].m_code, g_dyn_huff_3_codes[lits & 0xFF].m_code_size);
        PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code_size);
        PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 16)].m_code, g_dyn_huff_3_codes[(lits >> 16)].m_code_size);

        src_ofs += 3;

        prev_lits = lits;
      }

      PUT_BITS_FLUSH;

      while (src_ofs < end_src_ofs)
      {
        uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs);

        if (lits == prev_lits)
        {
          uint32_t match_len = 3;
          uint32_t max_match_len = minimum<int>(255, (int)(end_src_ofs - src_ofs));

          while (match_len < max_match_len)
          {
            if (READ_RGB_PIXEL(pSrc + src_ofs + match_len) != lits)
              break;
            match_len += 3;
          }

          uint32_t adj_match_len = match_len - 3;

          PUT_BITS_CZ(g_dyn_huff_3_codes[g_defl_len_sym[adj_match_len]].m_code, g_dyn_huff_3_codes[g_defl_len_sym[adj_match_len]].m_code_size);
          PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], g_defl_len_extra[adj_match_len] + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0

          src_ofs += match_len;
        }
        else
        {
          PUT_BITS_CZ(g_dyn_huff_3_codes[lits & 0xFF].m_code, g_dyn_huff_3_codes[lits & 0xFF].m_code_size);
          PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code_size);
          PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 16)].m_code, g_dyn_huff_3_codes[(lits >> 16)].m_code_size);

          prev_lits = lits;

          src_ofs += 3;
        }

        PUT_BITS_FLUSH;

      } // while (src_ofs < end_src_ofs)

    } // y

    assert(src_ofs == h * bpl);

    assert(bit_buf_size <= 7);

    PUT_BITS_CZ(g_dyn_huff_3_codes[256].m_code, g_dyn_huff_3_codes[256].m_code_size);

    PUT_BITS_FORCE_FLUSH;

    // Write zlib adler32
    for (uint32_t i = 0; i < 4; i++)
    {
      if ((dst_ofs + 1) > dst_buf_size)
        return 0;
      *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24);
      dst_ofs++;

      src_adler32 <<= 8;
    }

    return dst_ofs;
  }

  static uint32_t pixel_deflate_dyn_4_rle(
    const uint8_t* pImg, uint32_t w, uint32_t h,
    uint8_t* pDst, uint32_t dst_buf_size)
  {
    const uint32_t bpl = 1 + w * 4;

    uint64_t bit_buf = 0;
    int bit_buf_size = 0;

    uint32_t dst_ofs = 0;

    // zlib header
    PUT_BITS(0x78, 8);
    PUT_BITS(0x01, 8);

    // write BFINAL bit
    PUT_BITS(1, 1);

    std::vector<uint64_t> codes;
    codes.resize((w + 1) * h);
    uint64_t* pDst_codes = codes.data();

    uint32_t lit_freq[DEFL_MAX_HUFF_SYMBOLS_0];
    memset(lit_freq, 0, sizeof(lit_freq));

    const uint8_t* pSrc = pImg;
    uint32_t src_ofs = 0;

    uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT);

    const uint32_t dist_sym = g_defl_small_dist_sym[4 - 1];

    for (uint32_t y = 0; y < h; y++)
    {
      const uint32_t end_src_ofs = src_ofs + bpl;

      const uint32_t filter_lit = pSrc[src_ofs++];
      *pDst_codes++ = 1 | (filter_lit << 8);
      lit_freq[filter_lit]++;

      uint32_t prev_lits;
      {
        uint32_t lits = READ_LE32(pSrc + src_ofs);

        *pDst_codes++ = (uint64_t)lits << 8;

        lit_freq[lits & 0xFF]++;
        lit_freq[(lits >> 8) & 0xFF]++;
        lit_freq[(lits >> 16) & 0xFF]++;
        lit_freq[lits >> 24]++;

        src_ofs += 4;

        prev_lits = lits;
      }

      while (src_ofs < end_src_ofs)
      {
        uint32_t lits = READ_LE32(pSrc + src_ofs);

        if (lits == prev_lits)
        {
          uint32_t match_len = 4;
          uint32_t max_match_len = minimum<int>(252, (int)(end_src_ofs - src_ofs));

          while (match_len < max_match_len)
          {
            if (READ_LE32(pSrc + src_ofs + match_len) != lits)
              break;
            match_len += 4;
          }

          *pDst_codes++ = match_len - 1;

          uint32_t adj_match_len = match_len - 3;

          lit_freq[g_defl_len_sym[adj_match_len]]++;

          src_ofs += match_len;
        }
        else
        {
          *pDst_codes++ = (uint64_t)lits << 8;

          lit_freq[lits & 0xFF]++;
          lit_freq[(lits >> 8) & 0xFF]++;
          lit_freq[(lits >> 16) & 0xFF]++;
          lit_freq[lits >> 24]++;

          prev_lits = lits;

          src_ofs += 4;
        }

      } // while (src_ofs < end_src_ofs)

    } // y

    assert(src_ofs == h * bpl);
    const uint32_t total_codes = (uint32_t)(pDst_codes - codes.data());
    assert(total_codes <= codes.size());

    defl_huff dh;

    lit_freq[256] = 1;

    adjust_freq32(DEFL_MAX_HUFF_SYMBOLS_0, lit_freq, &dh.m_huff_count[0][0]);

    memset(&dh.m_huff_count[1][0], 0, sizeof(dh.m_huff_count[1][0]) * DEFL_MAX_HUFF_SYMBOLS_1);
    dh.m_huff_count[1][dist_sym] = 1;
    dh.m_huff_count[1][dist_sym + 1] = 1; // to workaround a bug in wuffs decoder

    if (!defl_start_dynamic_block(&dh, pDst, dst_ofs, dst_buf_size, bit_buf, bit_buf_size))
      return 0;

    assert(bit_buf_size <= 7);
    assert(dh.m_huff_codes[1][dist_sym] == 0 && dh.m_huff_code_sizes[1][dist_sym] == 1);

    for (uint32_t i = 0; i < total_codes; i++)
    {
      uint64_t c = codes[i];

      uint32_t c_type = (uint32_t)(c & 0xFF);
      if (c_type == 0)
      {
        uint32_t lits = (uint32_t)(c >> 8);

        PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]);
        lits >>= 8;

        PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]);
        lits >>= 8;

        PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]);
        lits >>= 8;

        if (bit_buf_size >= 49)
        {
          PUT_BITS_FLUSH;
        }

        PUT_BITS_CZ(dh.m_huff_codes[0][lits], dh.m_huff_code_sizes[0][lits]);
      }
      else if (c_type == 1)
      {
        uint32_t lit = (uint32_t)(c >> 8);
        PUT_BITS_CZ(dh.m_huff_codes[0][lit], dh.m_huff_code_sizes[0][lit]);
      }
      else
      {
        uint32_t match_len = c_type + 1;

        uint32_t adj_match_len = match_len - 3;

        PUT_BITS_CZ(dh.m_huff_codes[0][g_defl_len_sym[adj_match_len]], dh.m_huff_code_sizes[0][g_defl_len_sym[adj_match_len]]);
        PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], g_defl_len_extra[adj_match_len] + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0

        // no need to write the distance code, it's always 0
      }

      // up to 55 bits
      PUT_BITS_FLUSH;
    }

    PUT_BITS_CZ(dh.m_huff_codes[0][256], dh.m_huff_code_sizes[0][256]);

    PUT_BITS_FORCE_FLUSH;

    // Write zlib adler32
    for (uint32_t i = 0; i < 4; i++)
    {
      if ((dst_ofs + 1) > dst_buf_size)
        return 0;
      *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24);
      dst_ofs++;

      src_adler32 <<= 8;
    }

    return dst_ofs;
  }

  static uint32_t pixel_deflate_dyn_4_rle_one_pass(
    const uint8_t* pImg, uint32_t w, uint32_t h,
    uint8_t* pDst, uint32_t dst_buf_size)
  {
    const uint32_t bpl = 1 + w * 4;

    if (dst_buf_size < sizeof(g_dyn_huff_4))
      return false;
    memcpy(pDst, g_dyn_huff_4, sizeof(g_dyn_huff_4));
    uint32_t dst_ofs = sizeof(g_dyn_huff_4);

    uint64_t bit_buf = DYN_HUFF_4_BITBUF;
    int bit_buf_size = DYN_HUFF_4_BITBUF_SIZE;

    const uint8_t* pSrc = pImg;
    uint32_t src_ofs = 0;

    uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT);

    for (uint32_t y = 0; y < h; y++)
    {
      const uint32_t end_src_ofs = src_ofs + bpl;

      const uint32_t filter_lit = pSrc[src_ofs++];
      PUT_BITS_CZ(g_dyn_huff_4_codes[filter_lit].m_code, g_dyn_huff_4_codes[filter_lit].m_code_size);

      PUT_BITS_FLUSH;

      uint32_t prev_lits;
      {
        uint32_t lits = READ_LE32(pSrc + src_ofs);

        PUT_BITS_CZ(g_dyn_huff_4_codes[lits & 0xFF].m_code, g_dyn_huff_4_codes[lits & 0xFF].m_code_size);
        PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code_size);
        PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code_size);

        if (bit_buf_size >= 49)
        {
          PUT_BITS_FLUSH;
        }

        PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 24)].m_code, g_dyn_huff_4_codes[(lits >> 24)].m_code_size);

        src_ofs += 4;

        prev_lits = lits;
      }

      PUT_BITS_FLUSH;

      while (src_ofs < end_src_ofs)
      {
        uint32_t lits = READ_LE32(pSrc + src_ofs);

        if (lits == prev_lits)
        {
          uint32_t match_len = 4;
          uint32_t max_match_len = minimum<int>(252, (int)(end_src_ofs - src_ofs));

          while (match_len < max_match_len)
          {
            if (READ_LE32(pSrc + src_ofs + match_len) != lits)
              break;
            match_len += 4;
          }

          uint32_t adj_match_len = match_len - 3;

          const uint32_t match_code_bits = g_dyn_huff_4_codes[g_defl_len_sym[adj_match_len]].m_code_size;
          const uint32_t len_extra_bits = g_defl_len_extra[adj_match_len];

          if (match_len == 4)
          {
            // This check is optional - see if just encoding 4 literals would be cheaper than using a short match.
            uint32_t lit_bits = g_dyn_huff_4_codes[lits & 0xFF].m_code_size + g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code_size +
              g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code_size + g_dyn_huff_4_codes[(lits >> 24)].m_code_size;

            if ((match_code_bits + len_extra_bits + 1) > lit_bits)
              goto do_literals;
          }

          PUT_BITS_CZ(g_dyn_huff_4_codes[g_defl_len_sym[adj_match_len]].m_code, match_code_bits);
          PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], len_extra_bits + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0

          src_ofs += match_len;
        }
        else
        {
        do_literals:
          PUT_BITS_CZ(g_dyn_huff_4_codes[lits & 0xFF].m_code, g_dyn_huff_4_codes[lits & 0xFF].m_code_size);
          PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code_size);
          PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code_size);

          if (bit_buf_size >= 49)
          {
            PUT_BITS_FLUSH;
          }

          PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 24)].m_code, g_dyn_huff_4_codes[(lits >> 24)].m_code_size);

          src_ofs += 4;

          prev_lits = lits;
        }

        PUT_BITS_FLUSH;

      } // while (src_ofs < end_src_ofs)

    } // y

    assert(src_ofs == h * bpl);

    assert(bit_buf_size <= 7);

    PUT_BITS_CZ(g_dyn_huff_4_codes[256].m_code, g_dyn_huff_4_codes[256].m_code_size);

    PUT_BITS_FORCE_FLUSH;

    // Write zlib adler32
    for (uint32_t i = 0; i < 4; i++)
    {
      if ((dst_ofs + 1) > dst_buf_size)
        return 0;
      *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24);
      dst_ofs++;

      src_adler32 <<= 8;
    }

    return dst_ofs;
  }

  static void vector_append(std::vector<uint8_t>& buf, const void* pData, size_t len)
  {
    if (len)
    {
      size_t l = buf.size();
      buf.resize(l + len);
      memcpy(buf.data() + l, pData, len);
    }
  }

  static void apply_filter(uint32_t filter, int w, int h, uint32_t num_chans, uint32_t bpl, const uint8_t* pSrc, const uint8_t* pPrev_src, uint8_t* pDst)
  {
    (void)h;

    switch (filter)
    {
    case 0:
    {
      *pDst++ = 0;

      memcpy(pDst, pSrc, bpl);
      break;
    }
    case 2:
    {
      assert(pPrev_src);

      // Previous scanline
      *pDst++ = 2;

#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE
      if (g_cpu_info.can_use_sse41())
      {
        uint32_t bytes_to_process = w * num_chans, ofs = 0;
        for (; bytes_to_process >= 16; bytes_to_process -= 16, ofs += 16)
          _mm_storeu_si128((__m128i*)(pDst + ofs), _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(pSrc + ofs)), _mm_loadu_si128((const __m128i*)(pPrev_src + ofs))));

        for (; bytes_to_process; bytes_to_process--, ofs++)
          pDst[ofs] = (uint8_t)(pSrc[ofs] - pPrev_src[ofs]);
      }
      else
#endif
      {
        if (num_chans == 3)
        {
          for (uint32_t x = 0; x < (uint32_t)w; x++)
          {
            pDst[0] = (uint8_t)(pSrc[0] - pPrev_src[0]);
            pDst[1] = (uint8_t)(pSrc[1] - pPrev_src[1]);
            pDst[2] = (uint8_t)(pSrc[2] - pPrev_src[2]);

            pSrc += 3;
            pPrev_src += 3;
            pDst += 3;
          }
        }
        else
        {
          for (uint32_t x = 0; x < (uint32_t)w; x++)
          {
            pDst[0] = (uint8_t)(pSrc[0] - pPrev_src[0]);
            pDst[1] = (uint8_t)(pSrc[1] - pPrev_src[1]);
            pDst[2] = (uint8_t)(pSrc[2] - pPrev_src[2]);
            pDst[3] = (uint8_t)(pSrc[3] - pPrev_src[3]);

            pSrc += 4;
            pPrev_src += 4;
            pDst += 4;
          }
        }
      }

      break;
    }
    default:
      assert(0);
      break;
    }
  }

  bool fpng_encode_image_to_memory(const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, std::vector<uint8_t>& out_buf, uint32_t flags)
  {
    if (!endian_check())
    {
      assert(0);
      return false;
    }

    if ((w < 1) || (h < 1) || (w * (uint64_t)h > UINT32_MAX) || (w > FPNG_MAX_SUPPORTED_DIM) || (h > FPNG_MAX_SUPPORTED_DIM))
    {
      assert(0);
      return false;
    }

    if ((num_chans != 3) && (num_chans != 4))
    {
      assert(0);
      return false;
    }

    int i, bpl = w * num_chans;
    uint32_t y;

    std::vector<uint8_t> temp_buf;
    temp_buf.resize((bpl + 1) * h + 7);
    uint32_t temp_buf_ofs = 0;

    for (y = 0; y < h; ++y)
    {
      const uint8_t* pSrc = (uint8_t*)pImage + y * bpl;
      const uint8_t* pPrev_src = y ? ((uint8_t*)pImage + (y - 1) * bpl) : nullptr;

      uint8_t* pDst = &temp_buf[temp_buf_ofs];

      apply_filter(y ? 2 : 0, w, h, num_chans, bpl, pSrc, pPrev_src, pDst);

      temp_buf_ofs += 1 + bpl;
    }

    const uint32_t PNG_HEADER_SIZE = 58;

    uint32_t out_ofs = PNG_HEADER_SIZE;

    out_buf.resize((out_ofs + (bpl + 1) * h + 7) & ~7);

    uint32_t defl_size = 0;
    if ((flags & FPNG_FORCE_UNCOMPRESSED) == 0)
    {
      if (num_chans == 3)
      {
        if (flags & FPNG_ENCODE_SLOWER)
          defl_size = pixel_deflate_dyn_3_rle(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs);
        else
          defl_size = pixel_deflate_dyn_3_rle_one_pass(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs);
      }
      else
      {
        if (flags & FPNG_ENCODE_SLOWER)
          defl_size = pixel_deflate_dyn_4_rle(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs);
        else
          defl_size = pixel_deflate_dyn_4_rle_one_pass(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs);
      }
    }

    uint32_t zlib_size = defl_size;

    if (!defl_size)
    {
      // Dynamic block failed to compress - fall back to uncompressed blocks, filter 0.

      temp_buf_ofs = 0;

      for (y = 0; y < h; ++y)
      {
        const uint8_t* pSrc = (uint8_t*)pImage + y * bpl;

        uint8_t* pDst = &temp_buf[temp_buf_ofs];

        apply_filter(0, w, h, num_chans, bpl, pSrc, nullptr, pDst);

        temp_buf_ofs += 1 + bpl;
      }

      assert(temp_buf_ofs <= temp_buf.size());

      out_buf.resize(out_ofs + 6 + temp_buf_ofs + ((temp_buf_ofs + 65534) / 65535) * 5);

      uint32_t raw_size = write_raw_block(temp_buf.data(), (uint32_t)temp_buf_ofs, out_buf.data() + out_ofs, (uint32_t)out_buf.size() - out_ofs);
      if (!raw_size)
      {
        // Somehow we miscomputed the size of the output buffer.
        assert(0);
        return false;
      }

      zlib_size = raw_size;
    }

    assert((out_ofs + zlib_size) <= out_buf.size());

    out_buf.resize(out_ofs + zlib_size);

    const uint32_t idat_len = (uint32_t)out_buf.size() - PNG_HEADER_SIZE;

    // Write real PNG header, fdEC chunk, and the beginning of the IDAT chunk
    {
      static const uint8_t s_color_type[] = { 0x00, 0x00, 0x04, 0x02, 0x06 };

      uint8_t pnghdr[58] = {
        0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,   // PNG sig
        0x00,0x00,0x00,0x0d, 'I','H','D','R',  // IHDR chunk len, type
          0,0,(uint8_t)(w >> 8),(uint8_t)w, // width
        0,0,(uint8_t)(h >> 8),(uint8_t)h, // height
        8,   //bit_depth
        s_color_type[num_chans], // color_type
        0, // compression
        0, // filter
        0, // interlace
        0, 0, 0, 0, // IHDR crc32
        0, 0, 0, 5, 'f', 'd', 'E', 'C', 82, 36, 147, 227, FPNG_FDEC_VERSION,   0xE5, 0xAB, 0x62, 0x99, // our custom private, ancillary, do not copy, fdEC chunk
        (uint8_t)(idat_len >> 24),(uint8_t)(idat_len >> 16),(uint8_t)(idat_len >> 8),(uint8_t)idat_len, 'I','D','A','T' // IDATA chunk len, type
      };

      // Compute IHDR CRC32
      uint32_t c = (uint32_t)fpng_crc32(pnghdr + 12, 17, FPNG_CRC32_INIT);
      for (i = 0; i < 4; ++i, c <<= 8)
        ((uint8_t*)(pnghdr + 29))[i] = (uint8_t)(c >> 24);

      memcpy(out_buf.data(), pnghdr, PNG_HEADER_SIZE);
    }

    // Write IDAT chunk's CRC32 and a 0 length IEND chunk
    vector_append(out_buf, "\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16); // IDAT CRC32, followed by the IEND chunk

    // Compute IDAT crc32
    uint32_t c = (uint32_t)fpng_crc32(out_buf.data() + PNG_HEADER_SIZE - 4, idat_len + 4, FPNG_CRC32_INIT);

    for (i = 0; i < 4; ++i, c <<= 8)
      (out_buf.data() + out_buf.size() - 16)[i] = (uint8_t)(c >> 24);

    return true;
  }

  // Decompression

  const uint32_t FPNG_DECODER_TABLE_BITS = 12;
  const uint32_t FPNG_DECODER_TABLE_SIZE = 1 << FPNG_DECODER_TABLE_BITS;

  static bool build_decoder_table(uint32_t num_syms, uint8_t* pCode_sizes, uint32_t* pTable)
  {
    uint32_t num_codes[16];

    memset(num_codes, 0, sizeof(num_codes));
    for (uint32_t i = 0; i < num_syms; i++)
    {
      assert(pCode_sizes[i] <= FPNG_DECODER_TABLE_SIZE);
      num_codes[pCode_sizes[i]]++;
    }

    uint32_t next_code[17];
    next_code[0] = next_code[1] = 0;
    uint32_t total = 0;
    for (uint32_t i = 1; i <= 15; i++)
      next_code[i + 1] = (uint32_t)(total = ((total + ((uint32_t)num_codes[i])) << 1));

    if (total != 0x10000)
    {
      uint32_t j = 0;

      for (uint32_t i = 15; i != 0; i--)
        if ((j += num_codes[i]) > 1)
          return false;

      if (j != 1)
        return false;
    }

    uint32_t rev_codes[DEFL_MAX_HUFF_SYMBOLS];

    for (uint32_t i = 0; i < num_syms; i++)
      rev_codes[i] = next_code[pCode_sizes[i]]++;

    memset(pTable, 0, sizeof(uint32_t) * FPNG_DECODER_TABLE_SIZE);

    for (uint32_t i = 0; i < num_syms; i++)
    {
      const uint32_t code_size = pCode_sizes[i];
      if (!code_size)
        continue;

      uint32_t old_code = rev_codes[i], new_code = 0;
      for (uint32_t j = code_size; j != 0; j--)
      {
        new_code = (new_code << 1) | (old_code & 1);
        old_code >>= 1;
      }

      uint32_t j = 1 << code_size;

      while (new_code < FPNG_DECODER_TABLE_SIZE)
      {
        pTable[new_code] = i | (code_size << 9);
        new_code += j;
      }
    }

    return true;
  }

  static const uint16_t g_run_len3_to_4[259] =
  {
    0,
    0, 0, 4, 0, 0, 8, 0, 0, 12, 0, 0, 16, 0, 0, 20, 0, 0, 24, 0, 0, 28, 0, 0,
    32, 0, 0, 36, 0, 0, 40, 0, 0, 44, 0, 0, 48, 0, 0, 52, 0, 0, 56, 0, 0,
    60, 0, 0, 64, 0, 0, 68, 0, 0, 72, 0, 0, 76, 0, 0, 80, 0, 0, 84, 0, 0,
    88, 0, 0, 92, 0, 0, 96, 0, 0, 100, 0, 0, 104, 0, 0, 108, 0, 0, 112, 0, 0,
    116, 0, 0, 120, 0, 0, 124, 0, 0, 128, 0, 0, 132, 0, 0, 136, 0, 0, 140, 0, 0,
    144, 0, 0, 148, 0, 0, 152, 0, 0, 156, 0, 0, 160, 0, 0, 164, 0, 0, 168, 0, 0,
    172, 0, 0, 176, 0, 0, 180, 0, 0, 184, 0, 0, 188, 0, 0, 192, 0, 0, 196, 0, 0,
    200, 0, 0, 204, 0, 0, 208, 0, 0, 212, 0, 0, 216, 0, 0, 220, 0, 0, 224, 0, 0,
    228, 0, 0, 232, 0, 0, 236, 0, 0, 240, 0, 0, 244, 0, 0, 248, 0, 0, 252, 0, 0,
    256, 0, 0, 260, 0, 0, 264, 0, 0, 268, 0, 0, 272, 0, 0, 276, 0, 0, 280, 0, 0,
    284, 0, 0, 288, 0, 0, 292, 0, 0, 296, 0, 0, 300, 0, 0, 304, 0, 0, 308, 0, 0,
    312, 0, 0, 316, 0, 0, 320, 0, 0, 324, 0, 0, 328, 0, 0, 332, 0, 0, 336, 0, 0,
    340, 0, 0,
    344,
  };

  static const int s_length_extra[] = { 0,0,0,0, 0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 5,5,5,5, 0,    0,0 };
  static const int s_length_range[] = { 3,4,5,6, 7,8,9,10, 11,13,15,17, 19,23,27,31, 35,43,51,59, 67,83,99,115, 131,163,195,227, 258,    0,0 };

#define ENSURE_32BITS() do { \
  if (bit_buf_size < 32) { \
    if ((src_ofs + 4) > src_len) return false; \
    bit_buf |= ((uint64_t)READ_LE32(pSrc + src_ofs)) << bit_buf_size; \
    src_ofs += 4; bit_buf_size += 32; } \
  } while(0)

#define GET_BITS(b, ll) do { \
  uint32_t l = ll; assert(l && (l <= 32)); \
  b = (uint32_t)(bit_buf & g_bitmasks[l]); \
  bit_buf >>= l; \
  bit_buf_size -= l; \
  ENSURE_32BITS(); \
  } while(0)

#define SKIP_BITS(ll) do { \
  uint32_t l = ll; assert(l <= 32); \
  bit_buf >>= l; \
  bit_buf_size -= l; \
  ENSURE_32BITS(); \
  } while(0)

#define GET_BITS_NE(b, ll) do { \
  uint32_t l = ll; assert(l && (l <= 32) && (bit_buf_size >= l)); \
  b = (uint32_t)(bit_buf & g_bitmasks[l]); \
  bit_buf >>= l; \
  bit_buf_size -= l; \
  } while(0)

#define SKIP_BITS_NE(ll) do { \
  uint32_t l = ll; assert(l <= 32 && (bit_buf_size >= l)); \
  bit_buf >>= l; \
  bit_buf_size -= l; \
  } while(0)

  static bool prepare_dynamic_block(
    const uint8_t* pSrc, uint32_t src_len, uint32_t& src_ofs,
    uint32_t& bit_buf_size, uint64_t& bit_buf,
    uint32_t* pLit_table, uint32_t num_chans)
  {
    static const uint8_t s_bit_length_order[] = { 16, 17, 18, 0, 8,  7,  9, 6, 10,  5, 11, 4, 12,  3, 13, 2, 14,  1, 15 };

    uint32_t num_lit_codes, num_dist_codes, num_clen_codes;

    GET_BITS(num_lit_codes, 5);
    num_lit_codes += 257;

    GET_BITS(num_dist_codes, 5);
    num_dist_codes += 1;

    uint32_t total_codes = num_lit_codes + num_dist_codes;
    if (total_codes > (DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1))
      return false;

    uint8_t code_sizes[DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1];
    memset(code_sizes, 0, sizeof(code_sizes));

    GET_BITS(num_clen_codes, 4);
    num_clen_codes += 4;

    uint8_t clen_codesizes[DEFL_MAX_HUFF_SYMBOLS_2];
    memset(clen_codesizes, 0, sizeof(clen_codesizes));

    for (uint32_t i = 0; i < num_clen_codes; i++)
    {
      uint32_t len = 0;
      GET_BITS(len, 3);
      clen_codesizes[s_bit_length_order[i]] = (uint8_t)len;
    }

    uint32_t clen_table[FPNG_DECODER_TABLE_SIZE];
    if (!build_decoder_table(DEFL_MAX_HUFF_SYMBOLS_2, clen_codesizes, clen_table))
      return false;

    uint32_t min_code_size = 15;

    for (uint32_t cur_code = 0; cur_code < total_codes; )
    {
      uint32_t sym = clen_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
      uint32_t sym_len = sym >> 9;
      if (!sym_len)
        return false;
      SKIP_BITS(sym_len);
      sym &= 511;

      if (sym <= 15)
      {
        // Can't be a fpng Huffman table
        if (sym > FPNG_DECODER_TABLE_BITS)
          return false;

        if (sym)
          min_code_size = minimum(min_code_size, sym);

        code_sizes[cur_code++] = (uint8_t)sym;
        continue;
      }

      uint32_t rep_len = 0, rep_code_size = 0;

      switch (sym)
      {
      case 16:
      {
        GET_BITS(rep_len, 2);
        rep_len += 3;
        if (!cur_code)
          return false;
        rep_code_size = code_sizes[cur_code - 1];
        break;
      }
      case 17:
      {
        GET_BITS(rep_len, 3);
        rep_len += 3;
        rep_code_size = 0;
        break;
      }
      case 18:
      {
        GET_BITS(rep_len, 7);
        rep_len += 11;
        rep_code_size = 0;
        break;
      }
      }

      if ((cur_code + rep_len) > total_codes)
        return false;

      for (; rep_len; rep_len--)
        code_sizes[cur_code++] = (uint8_t)rep_code_size;
    }

    uint8_t lit_codesizes[DEFL_MAX_HUFF_SYMBOLS_0];

    memcpy(lit_codesizes, code_sizes, num_lit_codes);
    memset(lit_codesizes + num_lit_codes, 0, DEFL_MAX_HUFF_SYMBOLS_0 - num_lit_codes);

    uint32_t total_valid_distcodes = 0;
    for (uint32_t i = 0; i < num_dist_codes; i++)
      total_valid_distcodes += (code_sizes[num_lit_codes + i] == 1);

    // 1 or 2 because the first version of FPNG only issued 1 valid distance code, but that upset wuffs. So we let 1 or 2 through.
    if ((total_valid_distcodes < 1) || (total_valid_distcodes > 2))
      return false;

    if (code_sizes[num_lit_codes + (num_chans - 1)] != 1)
      return false;

    if (total_valid_distcodes == 2)
    {
      // If there are two valid distance codes, make sure the first is 1 bit.
      if (code_sizes[num_lit_codes + num_chans] != 1)
        return false;
    }

    if (!build_decoder_table(num_lit_codes, lit_codesizes, pLit_table))
      return false;

    // Add next symbol to decoder table, when it fits
    for (uint32_t i = 0; i < FPNG_DECODER_TABLE_SIZE; i++)
    {
      uint32_t sym = pLit_table[i] & 511;
      if (sym >= 256)
        continue;

      uint32_t sym_bits = (pLit_table[i] >> 9) & 15;
      if (!sym_bits)
        continue;
      assert(sym_bits <= FPNG_DECODER_TABLE_BITS);

      uint32_t bits_left = FPNG_DECODER_TABLE_BITS - sym_bits;
      if (bits_left < min_code_size)
        continue;

      uint32_t next_bits = i >> sym_bits;
      uint32_t next_sym = pLit_table[next_bits] & 511;
      uint32_t next_sym_bits = (pLit_table[next_bits] >> 9) & 15;
      if ((!next_sym_bits) || (bits_left < next_sym_bits))
        continue;

      pLit_table[i] |= (next_sym << 16) | (next_sym_bits << (16 + 9));
    }

    return true;
  }

  static bool fpng_pixel_zlib_raw_decompress(
    const uint8_t* pSrc, uint32_t src_len, uint32_t zlib_len,
    uint8_t* pDst, uint32_t w, uint32_t h,
    uint32_t src_chans, uint32_t dst_chans)
  {
    assert((src_chans == 3) || (src_chans == 4));
    assert((dst_chans == 3) || (dst_chans == 4));

    const uint32_t src_bpl = w * src_chans;
    const uint32_t dst_bpl = w * dst_chans;
    const uint32_t dst_len = dst_bpl * h;

    uint32_t src_ofs = 2;
    uint32_t dst_ofs = 0;
    uint32_t raster_ofs = 0;
    uint32_t comp_ofs = 0;

    for (; ; )
    {
      if ((src_ofs + 1) > src_len)
        return false;

      const bool bfinal = (pSrc[src_ofs] & 1) != 0;
      const uint32_t btype = (pSrc[src_ofs] >> 1) & 3;
      if (btype != 0)
        return false;

      src_ofs++;

      if ((src_ofs + 4) > src_len)
        return false;
      uint32_t len = pSrc[src_ofs + 0] | (pSrc[src_ofs + 1] << 8);
      uint32_t nlen = pSrc[src_ofs + 2] | (pSrc[src_ofs + 3] << 8);
      src_ofs += 4;

      if (len != (~nlen & 0xFFFF))
        return false;

      if ((src_ofs + len) > src_len)
        return false;

      // Raw blocks are a relatively uncommon case so this isn't well optimized.
      // Supports 3->4 and 4->3 byte/pixel conversion.
      for (uint32_t i = 0; i < len; i++)
      {
        uint32_t c = pSrc[src_ofs + i];

        if (!raster_ofs)
        {
          // Check filter type
          if (c != 0)
            return false;

          assert(!comp_ofs);
        }
        else
        {
          if (comp_ofs < dst_chans)
          {
            if (dst_ofs == dst_len)
              return false;

            pDst[dst_ofs++] = (uint8_t)c;
          }

          if (++comp_ofs == src_chans)
          {
            if (dst_chans > src_chans)
            {
              if (dst_ofs == dst_len)
                return false;

              pDst[dst_ofs++] = (uint8_t)0xFF;
            }

            comp_ofs = 0;
          }
        }

        if (++raster_ofs == (src_bpl + 1))
        {
          assert(!comp_ofs);
          raster_ofs = 0;
        }
      }

      src_ofs += len;

      if (bfinal)
        break;
    }

    if (comp_ofs != 0)
      return false;

    // Check for zlib adler32
    if ((src_ofs + 4) != zlib_len)
      return false;

    return (dst_ofs == dst_len);
  }

  template<uint32_t dst_comps>
  static bool fpng_pixel_zlib_decompress_3(
    const uint8_t* pSrc, uint32_t src_len, uint32_t zlib_len,
    uint8_t* pDst, uint32_t w, uint32_t h)
  {
    assert(src_len >= (zlib_len + 4));

    const uint32_t dst_bpl = w * dst_comps;
    //const uint32_t dst_len = dst_bpl * h;

    if (zlib_len < 7)
      return false;

    // check zlib header
    if ((pSrc[0] != 0x78) || (pSrc[1] != 0x01))
      return false;

    uint32_t src_ofs = 2;

    if ((pSrc[src_ofs] & 6) == 0)
      return fpng_pixel_zlib_raw_decompress(pSrc, src_len, zlib_len, pDst, w, h, 3, dst_comps);

    if ((src_ofs + 4) > src_len)
      return false;
    uint64_t bit_buf = READ_LE32(pSrc + src_ofs);
    src_ofs += 4;

    uint32_t bit_buf_size = 32;

    uint32_t bfinal, btype;
    GET_BITS(bfinal, 1);
    GET_BITS(btype, 2);

    // Must be the final block or it's not valid, and type=2 (dynamic)
    if ((bfinal != 1) || (btype != 2))
      return false;

    uint32_t lit_table[FPNG_DECODER_TABLE_SIZE];
    if (!prepare_dynamic_block(pSrc, src_len, src_ofs, bit_buf_size, bit_buf, lit_table, 3))
      return false;

    const uint8_t* pPrev_scanline = nullptr;
    uint8_t* pCur_scanline = pDst;

    for (uint32_t y = 0; y < h; y++)
    {
      // At start of PNG scanline, so read the filter literal
      assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
      uint32_t filter = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
      uint32_t filter_len = (filter >> 9) & 15;
      if (!filter_len)
        return false;
      SKIP_BITS(filter_len);
      filter &= 511;

      uint32_t expected_filter = (y ? 2 : 0);
      if (filter != expected_filter)
        return false;

      uint32_t x_ofs = 0;
      uint8_t prev_delta_r = 0, prev_delta_g = 0, prev_delta_b = 0;
      do
      {
        assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
        uint32_t lit0_tab = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];

        uint32_t lit0 = lit0_tab;
        uint32_t lit0_len = (lit0_tab >> 9) & 15;
        if (!lit0_len)
          return false;
        SKIP_BITS(lit0_len);

        if (lit0 & 256)
        {
          lit0 &= 511;

          // Can't be EOB - we still have more pixels to decompress.
          if (lit0 == 256)
            return false;

          // Must be an RLE match against the previous pixel.
          uint32_t run_len = s_length_range[lit0 - 257];
          if (lit0 >= 265)
          {
            uint32_t e;
            GET_BITS_NE(e, s_length_extra[lit0 - 257]);

            run_len += e;
          }

          // Skip match distance - it's always the same (3)
          SKIP_BITS_NE(1);

          // Matches must always be a multiple of 3/4 bytes
          assert((run_len % 3) == 0);

          if (dst_comps == 4)
          {
            const uint32_t x_ofs_end = x_ofs + g_run_len3_to_4[run_len];

            // Check for valid run lengths
            if (x_ofs == x_ofs_end)
              return false;

            // Matches cannot cross scanlines.
            if (x_ofs_end > dst_bpl)
              return false;

            if (pPrev_scanline)
            {
              if ((prev_delta_r | prev_delta_g | prev_delta_b) == 0)
              {
                memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, x_ofs_end - x_ofs);
                x_ofs = x_ofs_end;
              }
              else
              {
                do
                {
                  pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r);
                  pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g);
                  pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b);
                  pCur_scanline[x_ofs + 3] = 0xFF;
                  x_ofs += 4;
                } while (x_ofs < x_ofs_end);
              }
            }
            else
            {
              do
              {
                pCur_scanline[x_ofs] = prev_delta_r;
                pCur_scanline[x_ofs + 1] = prev_delta_g;
                pCur_scanline[x_ofs + 2] = prev_delta_b;
                pCur_scanline[x_ofs + 3] = 0xFF;
                x_ofs += 4;
              } while (x_ofs < x_ofs_end);
            }
          }
          else
          {
            // Check for valid run lengths
            if (!g_run_len3_to_4[run_len])
              return false;

            const uint32_t x_ofs_end = x_ofs + run_len;

            // Matches cannot cross scanlines.
            if (x_ofs_end > dst_bpl)
              return false;

            if (pPrev_scanline)
            {
              if ((prev_delta_r | prev_delta_g | prev_delta_b) == 0)
              {
                memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, run_len);
                x_ofs = x_ofs_end;
              }
              else
              {
                do
                {
                  pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r);
                  pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g);
                  pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b);
                  x_ofs += 3;
                } while (x_ofs < x_ofs_end);
              }
            }
            else
            {
              do
              {
                pCur_scanline[x_ofs] = prev_delta_r;
                pCur_scanline[x_ofs + 1] = prev_delta_g;
                pCur_scanline[x_ofs + 2] = prev_delta_b;
                x_ofs += 3;
              } while (x_ofs < x_ofs_end);
            }
          }
        }
        else
        {
          uint32_t lit1, lit2;

          uint32_t lit1_spec_len = (lit0_tab >> (16 + 9));
          uint32_t lit2_len;
          if (lit1_spec_len)
          {
            lit1 = (lit0_tab >> 16) & 511;
            SKIP_BITS_NE(lit1_spec_len);

            assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
            lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
            lit2_len = (lit2 >> 9) & 15;
            if (!lit2_len)
              return false;
          }
          else
          {
            assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
            lit1 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
            uint32_t lit1_len = (lit1 >> 9) & 15;
            if (!lit1_len)
              return false;
            SKIP_BITS_NE(lit1_len);

            lit2_len = (lit1 >> (16 + 9));
            if (lit2_len)
              lit2 = lit1 >> 16;
            else
            {
              assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
              lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
              lit2_len = (lit2 >> 9) & 15;
              if (!lit2_len)
                return false;
            }
          }

          SKIP_BITS(lit2_len);

          // Check for matches
          if ((lit1 | lit2) & 256)
            return false;

          if (dst_comps == 4)
          {
            if (pPrev_scanline)
            {
              pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
              pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
              pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
              pCur_scanline[x_ofs + 3] = 0xFF;
            }
            else
            {
              pCur_scanline[x_ofs] = (uint8_t)lit0;
              pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
              pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
              pCur_scanline[x_ofs + 3] = 0xFF;
            }
            x_ofs += 4;
          }
          else
          {
            if (pPrev_scanline)
            {
              pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
              pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
              pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
            }
            else
            {
              pCur_scanline[x_ofs] = (uint8_t)lit0;
              pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
              pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
            }
            x_ofs += 3;
          }

          prev_delta_r = (uint8_t)lit0;
          prev_delta_g = (uint8_t)lit1;
          prev_delta_b = (uint8_t)lit2;

          // See if we can decode one more pixel.
          uint32_t spec_next_len0_len = lit2 >> (16 + 9);
          if ((spec_next_len0_len) && (x_ofs < dst_bpl))
          {
            lit0 = (lit2 >> 16) & 511;
            if (lit0 < 256)
            {
              SKIP_BITS_NE(spec_next_len0_len);

              assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
              lit1 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
              uint32_t lit1_len = (lit1 >> 9) & 15;
              if (!lit1_len)
                return false;
              SKIP_BITS(lit1_len);

              lit2_len = (lit1 >> (16 + 9));
              if (lit2_len)
                lit2 = lit1 >> 16;
              else
              {
                assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
                lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
                lit2_len = (lit2 >> 9) & 15;
                if (!lit2_len)
                  return false;
              }

              SKIP_BITS_NE(lit2_len);

              // Check for matches
              if ((lit1 | lit2) & 256)
                return false;

              if (dst_comps == 4)
              {
                if (pPrev_scanline)
                {
                  pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
                  pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
                  pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
                  pCur_scanline[x_ofs + 3] = 0xFF;
                }
                else
                {
                  pCur_scanline[x_ofs] = (uint8_t)lit0;
                  pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
                  pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
                  pCur_scanline[x_ofs + 3] = 0xFF;
                }
                x_ofs += 4;
              }
              else
              {
                if (pPrev_scanline)
                {
                  pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
                  pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
                  pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
                }
                else
                {
                  pCur_scanline[x_ofs] = (uint8_t)lit0;
                  pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
                  pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
                }
                x_ofs += 3;
              }

              prev_delta_r = (uint8_t)lit0;
              prev_delta_g = (uint8_t)lit1;
              prev_delta_b = (uint8_t)lit2;

            } // if (lit0 < 256)

          } // if ((spec_next_len0_len) && (x_ofs < bpl))
        }

      } while (x_ofs < dst_bpl);

      pPrev_scanline = pCur_scanline;
      pCur_scanline += dst_bpl;

    } // y

    // The last symbol should be EOB
    assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
    uint32_t lit0 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
    uint32_t lit0_len = (lit0 >> 9) & 15;
    if (!lit0_len)
      return false;
    lit0 &= 511;
    if (lit0 != 256)
      return false;

    bit_buf_size -= lit0_len;
    bit_buf >>= lit0_len;

    uint32_t align_bits = bit_buf_size & 7;
    bit_buf_size -= align_bits;
    bit_buf >>= align_bits;

    if (src_ofs < (bit_buf_size >> 3))
      return false;
    src_ofs -= (bit_buf_size >> 3);

    // We should be at the very end, because the bit buf reads ahead 32-bits (which contains the zlib adler32).
    if ((src_ofs + 4) != zlib_len)
      return false;

    return true;
  }

  template<uint32_t dst_comps>
  static bool fpng_pixel_zlib_decompress_4(
    const uint8_t* pSrc, uint32_t src_len, uint32_t zlib_len,
    uint8_t* pDst, uint32_t w, uint32_t h)
  {
    assert(src_len >= (zlib_len + 4));

    const uint32_t dst_bpl = w * dst_comps;
    //const uint32_t dst_len = dst_bpl * h;

    if (zlib_len < 7)
      return false;

    // check zlib header
    if ((pSrc[0] != 0x78) || (pSrc[1] != 0x01))
      return false;

    uint32_t src_ofs = 2;

    if ((pSrc[src_ofs] & 6) == 0)
      return fpng_pixel_zlib_raw_decompress(pSrc, src_len, zlib_len, pDst, w, h, 4, dst_comps);

    if ((src_ofs + 4) > src_len)
      return false;
    uint64_t bit_buf = READ_LE32(pSrc + src_ofs);
    src_ofs += 4;

    uint32_t bit_buf_size = 32;

    uint32_t bfinal, btype;
    GET_BITS(bfinal, 1);
    GET_BITS(btype, 2);

    // Must be the final block or it's not valid, and type=2 (dynamic)
    if ((bfinal != 1) || (btype != 2))
      return false;

    uint32_t lit_table[FPNG_DECODER_TABLE_SIZE];
    if (!prepare_dynamic_block(pSrc, src_len, src_ofs, bit_buf_size, bit_buf, lit_table, 4))
      return false;

    const uint8_t* pPrev_scanline = nullptr;
    uint8_t* pCur_scanline = pDst;

    for (uint32_t y = 0; y < h; y++)
    {
      // At start of PNG scanline, so read the filter literal
      assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
      uint32_t filter = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
      uint32_t filter_len = (filter >> 9) & 15;
      if (!filter_len)
        return false;
      SKIP_BITS(filter_len);
      filter &= 511;

      uint32_t expected_filter = (y ? 2 : 0);
      if (filter != expected_filter)
        return false;

      uint32_t x_ofs = 0;
      uint8_t prev_delta_r = 0, prev_delta_g = 0, prev_delta_b = 0, prev_delta_a = 0;
      do
      {
        assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
        uint32_t lit0_tab = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];

        uint32_t lit0 = lit0_tab;
        uint32_t lit0_len = (lit0_tab >> 9) & 15;
        if (!lit0_len)
          return false;
        SKIP_BITS(lit0_len);

        if (lit0 & 256)
        {
          lit0 &= 511;

          // Can't be EOB - we still have more pixels to decompress.
          if (lit0 == 256)
            return false;

          // Must be an RLE match against the previous pixel.
          uint32_t run_len = s_length_range[lit0 - 257];
          if (lit0 >= 265)
          {
            uint32_t e;
            GET_BITS_NE(e, s_length_extra[lit0 - 257]);

            run_len += e;
          }

          // Skip match distance - it's always the same (4)
          SKIP_BITS_NE(1);

          // Matches must always be a multiple of 3/4 bytes
          if (run_len & 3)
            return false;

          if (dst_comps == 3)
          {
            const uint32_t run_len3 = (run_len >> 2) * 3;
            const uint32_t x_ofs_end = x_ofs + run_len3;

            // Matches cannot cross scanlines.
            if (x_ofs_end > dst_bpl)
              return false;

            if (pPrev_scanline)
            {
              if ((prev_delta_r | prev_delta_g | prev_delta_b | prev_delta_a) == 0)
              {
                memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, run_len3);
                x_ofs = x_ofs_end;
              }
              else
              {
                do
                {
                  pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r);
                  pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g);
                  pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b);
                  x_ofs += 3;
                } while (x_ofs < x_ofs_end);
              }
            }
            else
            {
              do
              {
                pCur_scanline[x_ofs] = prev_delta_r;
                pCur_scanline[x_ofs + 1] = prev_delta_g;
                pCur_scanline[x_ofs + 2] = prev_delta_b;
                x_ofs += 3;
              } while (x_ofs < x_ofs_end);
            }
          }
          else
          {
            const uint32_t x_ofs_end = x_ofs + run_len;

            // Matches cannot cross scanlines.
            if (x_ofs_end > dst_bpl)
              return false;

            if (pPrev_scanline)
            {
              if ((prev_delta_r | prev_delta_g | prev_delta_b | prev_delta_a) == 0)
              {
                memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, run_len);
                x_ofs = x_ofs_end;
              }
              else
              {
                do
                {
                  pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r);
                  pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g);
                  pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b);
                  pCur_scanline[x_ofs + 3] = (uint8_t)(pPrev_scanline[x_ofs + 3] + prev_delta_a);
                  x_ofs += 4;
                } while (x_ofs < x_ofs_end);
              }
            }
            else
            {
              do
              {
                pCur_scanline[x_ofs] = prev_delta_r;
                pCur_scanline[x_ofs + 1] = prev_delta_g;
                pCur_scanline[x_ofs + 2] = prev_delta_b;
                pCur_scanline[x_ofs + 3] = prev_delta_a;
                x_ofs += 4;
              } while (x_ofs < x_ofs_end);
            }
          }
        }
        else
        {
          uint32_t lit1, lit2;

          uint32_t lit1_spec_len = (lit0_tab >> (16 + 9));
          uint32_t lit2_len;
          if (lit1_spec_len)
          {
            lit1 = (lit0_tab >> 16) & 511;
            SKIP_BITS_NE(lit1_spec_len);

            assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
            lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
            lit2_len = (lit2 >> 9) & 15;
            if (!lit2_len)
              return false;
          }
          else
          {
            assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
            lit1 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
            uint32_t lit1_len = (lit1 >> 9) & 15;
            if (!lit1_len)
              return false;
            SKIP_BITS_NE(lit1_len);

            lit2_len = (lit1 >> (16 + 9));
            if (lit2_len)
              lit2 = lit1 >> 16;
            else
            {
              assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
              lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
              lit2_len = (lit2 >> 9) & 15;
              if (!lit2_len)
                return false;
            }
          }

          uint32_t lit3;
          uint32_t lit3_len = lit2 >> (16 + 9);

          if (lit3_len)
          {
            lit3 = (lit2 >> 16);
            SKIP_BITS(lit2_len + lit3_len);
          }
          else
          {
            SKIP_BITS(lit2_len);

            assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
            lit3 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
            lit3_len = (lit3 >> 9) & 15;
            if (!lit3_len)
              return false;

            SKIP_BITS_NE(lit3_len);
          }

          // Check for matches
          if ((lit1 | lit2 | lit3) & 256)
            return false;

          if (dst_comps == 3)
          {
            if (pPrev_scanline)
            {
              pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
              pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
              pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
            }
            else
            {
              pCur_scanline[x_ofs] = (uint8_t)lit0;
              pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
              pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
            }

            x_ofs += 3;
          }
          else
          {
            if (pPrev_scanline)
            {
              pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
              pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
              pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
              pCur_scanline[x_ofs + 3] = (uint8_t)(pPrev_scanline[x_ofs + 3] + lit3);
            }
            else
            {
              pCur_scanline[x_ofs] = (uint8_t)lit0;
              pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
              pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
              pCur_scanline[x_ofs + 3] = (uint8_t)lit3;
            }

            x_ofs += 4;
          }

          prev_delta_r = (uint8_t)lit0;
          prev_delta_g = (uint8_t)lit1;
          prev_delta_b = (uint8_t)lit2;
          prev_delta_a = (uint8_t)lit3;
        }

      } while (x_ofs < dst_bpl);

      pPrev_scanline = pCur_scanline;
      pCur_scanline += dst_bpl;
    } // y

    // The last symbol should be EOB
    assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
    uint32_t lit0 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
    uint32_t lit0_len = (lit0 >> 9) & 15;
    if (!lit0_len)
      return false;
    lit0 &= 511;
    if (lit0 != 256)
      return false;

    bit_buf_size -= lit0_len;
    bit_buf >>= lit0_len;

    uint32_t align_bits = bit_buf_size & 7;
    bit_buf_size -= align_bits;
    bit_buf >>= align_bits;

    if (src_ofs < (bit_buf_size >> 3))
      return false;
    src_ofs -= (bit_buf_size >> 3);

    // We should be at the very end, because the bit buf reads ahead 32-bits (which contains the zlib adler32).
    if ((src_ofs + 4) != zlib_len)
      return false;

    return true;
  }

#pragma pack(push)
#pragma pack(1)
  struct png_chunk_prefix
  {
    uint32_t m_length;
    uint8_t m_type[4];
  };
  struct png_ihdr
  {
    png_chunk_prefix m_prefix;
    uint32_t m_width;
    uint32_t m_height;
    uint8_t m_bitdepth;
    uint8_t m_color_type;
    uint8_t m_comp_method;
    uint8_t m_filter_method;
    uint8_t m_interlace_method;
    uint32_t m_crc32;
  };
  const uint32_t IHDR_EXPECTED_LENGTH = 13;
  struct png_iend
  {
    png_chunk_prefix m_prefix;
    uint32_t m_crc32;
  };
#pragma pack(pop)

  static int fpng_get_info_internal(const void* pImage, uint32_t image_size, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t& idat_ofs, uint32_t& idat_len)
  {
    static const uint8_t s_png_sig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };

    if (!endian_check())
    {
      assert(0);
      return false;
    }

    width = 0;
    height = 0;
    channels_in_file = 0;
    idat_ofs = 0, idat_len = 0;

    // Ensure the file has at least a minimum possible size
    if (image_size < (sizeof(s_png_sig) + sizeof(png_ihdr) + sizeof(png_chunk_prefix) + 1 + sizeof(uint32_t) + sizeof(png_iend)))
      return FPNG_DECODE_FAILED_NOT_PNG;

    if (memcmp(pImage, s_png_sig, 8) != 0)
      return FPNG_DECODE_FAILED_NOT_PNG;

    const uint8_t* pImage_u8 = static_cast<const uint8_t*>(pImage) + 8;

    const png_ihdr& ihdr = *reinterpret_cast<const png_ihdr*>(pImage_u8);
    pImage_u8 += sizeof(png_ihdr);

    if (READ_BE32(&ihdr.m_prefix.m_length) != IHDR_EXPECTED_LENGTH)
      return FPNG_DECODE_FAILED_NOT_PNG;

    if (fpng_crc32(ihdr.m_prefix.m_type, 4 + IHDR_EXPECTED_LENGTH, FPNG_CRC32_INIT) != READ_BE32(&ihdr.m_crc32))
      return FPNG_DECODE_FAILED_HEADER_CRC32;

    width = READ_BE32(&ihdr.m_width);
    height = READ_BE32(&ihdr.m_height);

    if (!width || !height || (width > FPNG_MAX_SUPPORTED_DIM) || (height > FPNG_MAX_SUPPORTED_DIM))
      return FPNG_DECODE_FAILED_INVALID_DIMENSIONS;

    uint64_t total_pixels = (uint64_t)width * height;
    if (total_pixels > (1 << 30))
      return FPNG_DECODE_FAILED_INVALID_DIMENSIONS;

    if ((ihdr.m_comp_method) || (ihdr.m_filter_method) || (ihdr.m_interlace_method) || (ihdr.m_bitdepth != 8))
      return FPNG_DECODE_NOT_FPNG;

    if (ihdr.m_color_type == 2)
      channels_in_file = 3;
    else if (ihdr.m_color_type == 6)
      channels_in_file = 4;

    if (!channels_in_file)
      return FPNG_DECODE_NOT_FPNG;

    // Scan all the chunks. Look for one IDAT, IEND, and our custom fdEC chunk that indicates the file was compressed by us. Skip any ancillary chunks.
    bool found_fdec_chunk = false;

    for (; ; )
    {
      const size_t src_ofs = pImage_u8 - static_cast<const uint8_t*>(pImage);
      if (src_ofs >= image_size)
        return FPNG_DECODE_FAILED_CHUNK_PARSING;

      const uint32_t bytes_remaining = image_size - (uint32_t)src_ofs;
      if (bytes_remaining < sizeof(uint32_t) * 3)
        return FPNG_DECODE_FAILED_CHUNK_PARSING;

      const png_chunk_prefix* pChunk = reinterpret_cast<const png_chunk_prefix*>(pImage_u8);

      const uint32_t chunk_len = READ_BE32(&pChunk->m_length);
      if ((src_ofs + sizeof(uint32_t) + chunk_len + sizeof(uint32_t)) > image_size)
        return FPNG_DECODE_FAILED_CHUNK_PARSING;

      for (uint32_t i = 0; i < 4; i++)
      {
        const uint8_t c = pChunk->m_type[i];
        const bool is_upper = (c >= 65) && (c <= 90), is_lower = (c >= 97) && (c <= 122);
        if ((!is_upper) && (!is_lower))
          return FPNG_DECODE_FAILED_CHUNK_PARSING;
      }

      const uint32_t expected_crc32 = READ_BE32(pImage_u8 + sizeof(uint32_t) * 2 + chunk_len);

      char chunk_type[5] = { (char)pChunk->m_type[0], (char)pChunk->m_type[1], (char)pChunk->m_type[2], (char)pChunk->m_type[3], 0 };
      const bool is_idat = strcmp(chunk_type, "IDAT") == 0;

#if !FPNG_DISABLE_DECODE_CRC32_CHECKS
      if (!is_idat)
      {
        uint32_t actual_crc32 = fpng_crc32(pImage_u8 + sizeof(uint32_t), sizeof(uint32_t) + chunk_len, FPNG_CRC32_INIT);
        if (actual_crc32 != expected_crc32)
          return FPNG_DECODE_FAILED_HEADER_CRC32;
      }
#endif

      const uint8_t* pChunk_data = pImage_u8 + sizeof(uint32_t) * 2;

      if (strcmp(chunk_type, "IEND") == 0)
        break;
      else if (is_idat)
      {
        // If there were multiple IDAT's, or we didn't find the fdEC chunk, then it's not FPNG.
        if ((idat_ofs) || (!found_fdec_chunk))
          return FPNG_DECODE_NOT_FPNG;

        idat_ofs = (uint32_t)src_ofs;
        idat_len = chunk_len;

        // Sanity check the IDAT chunk length
        if (idat_len < 7)
          return FPNG_DECODE_FAILED_INVALID_IDAT;
      }
      else if (strcmp(chunk_type, "fdEC") == 0)
      {
        if (found_fdec_chunk)
          return FPNG_DECODE_NOT_FPNG;

        // We've got our fdEC chunk. Now make sure it's big enough and check its contents.
        if (chunk_len != 5)
          return FPNG_DECODE_NOT_FPNG;

        // Check fdEC chunk sig
        if ((pChunk_data[0] != 82) || (pChunk_data[1] != 36) || (pChunk_data[2] != 147) || (pChunk_data[3] != 227))
          return FPNG_DECODE_NOT_FPNG;

        // Check fdEC version
        if (pChunk_data[4] != FPNG_FDEC_VERSION)
          return FPNG_DECODE_NOT_FPNG;

        found_fdec_chunk = true;
      }
      else
      {
        // Bail if it's a critical chunk - can't be FPNG
        if ((chunk_type[0] & 32) == 0)
          return FPNG_DECODE_NOT_FPNG;

        // ancillary chunk - skip it
      }

      pImage_u8 += sizeof(png_chunk_prefix) + chunk_len + sizeof(uint32_t);
    }

    if ((!found_fdec_chunk) || (!idat_ofs))
      return FPNG_DECODE_NOT_FPNG;

    return FPNG_DECODE_SUCCESS;
  }

  int fpng_get_info(const void* pImage, uint32_t image_size, uint32_t& width, uint32_t& height, uint32_t& channels_in_file)
  {
    uint32_t idat_ofs = 0, idat_len = 0;
    return fpng_get_info_internal(pImage, image_size, width, height, channels_in_file, idat_ofs, idat_len);
  }

  int fpng_decode_memory(const void* pImage, uint32_t image_size, std::vector<uint8_t>& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels)
  {
    out.resize(0);
    width = 0;
    height = 0;
    channels_in_file = 0;

    if ((!pImage) || (!image_size) || ((desired_channels != 3) && (desired_channels != 4)))
    {
      assert(0);
      return FPNG_DECODE_INVALID_ARG;
    }

    uint32_t idat_ofs = 0, idat_len = 0;
    int status = fpng_get_info_internal(pImage, image_size, width, height, channels_in_file, idat_ofs, idat_len);
    if (status)
      return status;

    const uint64_t mem_needed = (uint64_t)width * height * desired_channels;
    if (mem_needed > UINT32_MAX)
      return FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE;

    // On 32-bit systems do a quick sanity check before we try to resize the output buffer.
    if ((sizeof(size_t) == sizeof(uint32_t)) && (mem_needed >= 0x80000000))
      return FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE;

    out.resize(mem_needed);

    const uint8_t* pIDAT_data = static_cast<const uint8_t*>(pImage) + idat_ofs + sizeof(uint32_t) * 2;
    const uint32_t src_len = image_size - (idat_ofs + sizeof(uint32_t) * 2);

    bool decomp_status;
    if (desired_channels == 3)
    {
      if (channels_in_file == 3)
        decomp_status = fpng_pixel_zlib_decompress_3<3>(pIDAT_data, src_len, idat_len, out.data(), width, height);
      else
        decomp_status = fpng_pixel_zlib_decompress_4<3>(pIDAT_data, src_len, idat_len, out.data(), width, height);
    }
    else
    {
      if (channels_in_file == 3)
        decomp_status = fpng_pixel_zlib_decompress_3<4>(pIDAT_data, src_len, idat_len, out.data(), width, height);
      else
        decomp_status = fpng_pixel_zlib_decompress_4<4>(pIDAT_data, src_len, idat_len, out.data(), width, height);
    }
    if (!decomp_status)
    {
      // Something went wrong. Either the file data was corrupted, or it doesn't conform to one of our zlib/Deflate constraints.
      // The conservative thing to do is indicate it wasn't written by us, and let the general purpose PNG decoder handle it.
      return FPNG_DECODE_NOT_FPNG;
    }

    return FPNG_DECODE_SUCCESS;
  }
} // namespace PNG


void Png::init()
{
  Png::fpng_init();
}



static int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true)
{
  // picoPNG version 20101224
  // Copyright (c) 2005-2010 Lode Vandevenne
  //
  // This software is provided 'as-is', without any express or implied
  // warranty. In no event will the authors be held liable for any damages
  // arising from the use of this software.
  //
  // Permission is granted to anyone to use this software for any purpose,
  // including commercial applications, and to alter it and redistribute it
  // freely, subject to the following restrictions:
  //
  //     1. The origin of this software must not be misrepresented; you must not
  //     claim that you wrote the original software. If you use this software
  //     in a product, an acknowledgment in the product documentation would be
  //     appreciated but is not required.
  //     2. Altered source versions must be plainly marked as such, and must not be
  //     misrepresented as being the original software.
  //     3. This notice may not be removed or altered from any source distribution.

  // picoPNG is a PNG decoder in one C++ function of around 500 lines. Use picoPNG for
  // programs that need only 1 .cpp file. Since it's a single function, it's very limited,
  // it can convert a PNG to raw pixel data either converted to 32-bit RGBA color or
  // with no color conversion at all. For anything more complex, another tiny library
  // is available: LodePNG (lodepng.c(pp)), which is a single source and header file.
  // Apologies for the compact code style, it's to make this tiny.


  static const unsigned long LENBASE[29] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258 };
  static const unsigned long LENEXTRA[29] = { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4,  4,  5,  5,  5,  5,  0 };
  static const unsigned long DISTBASE[30] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577 };
  static const unsigned long DISTEXTRA[30] = { 0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5,  6,  6,  7,  7,  8,  8,   9,   9,  10,  10,  11,  11,  12,   12,   13,   13 };
  static const unsigned long CLCL[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
  struct Zlib
  {
    static unsigned long readBitFromStream(size_t& bitp, const unsigned char* bits) { unsigned long result = (bits[bitp >> 3] >> (bitp & 0x7)) & 1; bitp++; return result; }
    static unsigned long readBitsFromStream(size_t& bitp, const unsigned char* bits, size_t nbits)
    {
      unsigned long result = 0;
      for (size_t i = 0; i < nbits; i++) result += (readBitFromStream(bitp, bits)) << i;
      return result;
    }
    struct HuffmanTree
    {
      int makeFromLengths(const std::vector<unsigned long>& bitlen, unsigned long maxbitlen)
      {
        unsigned long numcodes = (unsigned long)(bitlen.size()), treepos = 0, nodefilled = 0;
        std::vector<unsigned long> tree1d(numcodes), blcount(maxbitlen + 1, 0), nextcode(maxbitlen + 1, 0);
        for (unsigned long bits = 0; bits < numcodes; bits++) blcount[bitlen[bits]]++;
        for (unsigned long bits = 1; bits <= maxbitlen; bits++) nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1;
        for (unsigned long n = 0; n < numcodes; n++) if (bitlen[n] != 0) tree1d[n] = nextcode[bitlen[n]]++;
        tree2d.clear(); tree2d.resize(numcodes * 2, 32767);
        for (unsigned long n = 0; n < numcodes; n++)
          for (unsigned long i = 0; i < bitlen[n]; i++)
          {
            unsigned long bit = (tree1d[n] >> (bitlen[n] - i - 1)) & 1;
            if (treepos > numcodes - 2) return 55;
            if (tree2d[2 * treepos + bit] == 32767)
            {
              if (i + 1 == bitlen[n]) { tree2d[2 * treepos + bit] = n; treepos = 0; }
              else { tree2d[2 * treepos + bit] = ++nodefilled + numcodes; treepos = nodefilled; }
            }
            else treepos = tree2d[2 * treepos + bit] - numcodes;
          }
        return 0;
      }
      int decode(bool& decoded, unsigned long& result, size_t& treepos, unsigned long bit) const
      {
        unsigned long numcodes = (unsigned long)tree2d.size() / 2;
        if (treepos >= numcodes) return 11;
        result = tree2d[2 * treepos + bit];
        decoded = (result < numcodes);
        treepos = decoded ? 0 : result - numcodes;
        return 0;
      }
      std::vector<unsigned long> tree2d;
    };
    struct Inflator
    {
      int error;
      void inflate(std::vector<unsigned char>& out, const std::vector<unsigned char>& in, size_t inpos = 0)
      {
        size_t bp = 0, pos = 0;
        error = 0;
        unsigned long BFINAL = 0;
        while (!BFINAL && !error)
        {
          if (bp >> 3 >= in.size()) { error = 52; return; }
          BFINAL = readBitFromStream(bp, &in[inpos]);
          unsigned long BTYPE = readBitFromStream(bp, &in[inpos]); BTYPE += 2 * readBitFromStream(bp, &in[inpos]);
          if (BTYPE == 3) { error = 20; return; }
          else if (BTYPE == 0) inflateNoCompression(out, &in[inpos], bp, pos, in.size());
          else inflateHuffmanBlock(out, &in[inpos], bp, pos, in.size(), BTYPE);
        }
        if (!error) out.resize(pos);
      }
      void generateFixedTrees(HuffmanTree& tree, HuffmanTree& treeD)
      {
        std::vector<unsigned long> bitlen(288, 8), bitlenD(32, 5);;
        for (size_t i = 144; i <= 255; i++) bitlen[i] = 9;
        for (size_t i = 256; i <= 279; i++) bitlen[i] = 7;
        tree.makeFromLengths(bitlen, 15);
        treeD.makeFromLengths(bitlenD, 15);
      }
      HuffmanTree codetree, codetreeD, codelengthcodetree;
      unsigned long huffmanDecodeSymbol(const unsigned char* in, size_t& bp, const HuffmanTree& codetree, size_t inlength)
      {
        bool decoded; unsigned long ct;
        for (size_t treepos = 0;;)
        {
          if ((bp & 0x07) == 0 && (bp >> 3) > inlength) { error = 10; return 0; }
          error = codetree.decode(decoded, ct, treepos, readBitFromStream(bp, in)); if (error) return 0;
          if (decoded) return ct;
        }
      }
      void getTreeInflateDynamic(HuffmanTree& tree, HuffmanTree& treeD, const unsigned char* in, size_t& bp, size_t inlength)
      {
        std::vector<unsigned long> bitlen(288, 0), bitlenD(32, 0);
        if (bp >> 3 >= inlength - 2) { error = 49; return; }
        size_t HLIT = readBitsFromStream(bp, in, 5) + 257;
        size_t HDIST = readBitsFromStream(bp, in, 5) + 1;
        size_t HCLEN = readBitsFromStream(bp, in, 4) + 4;
        std::vector<unsigned long> codelengthcode(19);
        for (size_t i = 0; i < 19; i++) codelengthcode[CLCL[i]] = (i < HCLEN) ? readBitsFromStream(bp, in, 3) : 0;
        error = codelengthcodetree.makeFromLengths(codelengthcode, 7); if (error) return;
        size_t i = 0, replength;
        while (i < HLIT + HDIST)
        {
          unsigned long code = huffmanDecodeSymbol(in, bp, codelengthcodetree, inlength); if (error) return;
          if (code <= 15) { if (i < HLIT) bitlen[i++] = code; else bitlenD[i++ - HLIT] = code; }
          else if (code == 16)
          {
            if (bp >> 3 >= inlength) { error = 50; return; }
            replength = 3 + readBitsFromStream(bp, in, 2);
            unsigned long value;
            if ((i - 1) < HLIT) value = bitlen[i - 1];
            else value = bitlenD[i - HLIT - 1];
            for (size_t n = 0; n < replength; n++)
            {
              if (i >= HLIT + HDIST) { error = 13; return; }
              if (i < HLIT) bitlen[i++] = value; else bitlenD[i++ - HLIT] = value;
            }
          }
          else if (code == 17)
          {
            if (bp >> 3 >= inlength) { error = 50; return; }
            replength = 3 + readBitsFromStream(bp, in, 3);
            for (size_t n = 0; n < replength; n++)
            {
              if (i >= HLIT + HDIST) { error = 14; return; }
              if (i < HLIT) bitlen[i++] = 0; else bitlenD[i++ - HLIT] = 0;
            }
          }
          else if (code == 18)
          {
            if (bp >> 3 >= inlength) { error = 50; return; }
            replength = 11 + readBitsFromStream(bp, in, 7);
            for (size_t n = 0; n < replength; n++)
            {
              if (i >= HLIT + HDIST) { error = 15; return; }
              if (i < HLIT) bitlen[i++] = 0; else bitlenD[i++ - HLIT] = 0;
            }
          }
          else { error = 16; return; }
        }
        if (bitlen[256] == 0) { error = 64; return; }
        error = tree.makeFromLengths(bitlen, 15); if (error) return;
        error = treeD.makeFromLengths(bitlenD, 15); if (error) return;
      }
      void inflateHuffmanBlock(std::vector<unsigned char>& out, const unsigned char* in, size_t& bp, size_t& pos, size_t inlength, unsigned long btype)
      {
        if (btype == 1) { generateFixedTrees(codetree, codetreeD); }
        else if (btype == 2) { getTreeInflateDynamic(codetree, codetreeD, in, bp, inlength); if (error) return; }
        for (;;)
        {
          unsigned long code = huffmanDecodeSymbol(in, bp, codetree, inlength); if (error) return;
          if (code == 256) return;
          else if (code <= 255)
          {
            if (pos >= out.size()) out.resize((pos + 1) * 2);
            out[pos++] = (unsigned char)(code);
          }
          else if (code >= 257 && code <= 285)
          {
            size_t length = LENBASE[code - 257], numextrabits = LENEXTRA[code - 257];
            if ((bp >> 3) >= inlength) { error = 51; return; }
            length += readBitsFromStream(bp, in, numextrabits);
            unsigned long codeD = huffmanDecodeSymbol(in, bp, codetreeD, inlength); if (error) return;
            if (codeD > 29) { error = 18; return; }
            unsigned long dist = DISTBASE[codeD], numextrabitsD = DISTEXTRA[codeD];
            if ((bp >> 3) >= inlength) { error = 51; return; }
            dist += readBitsFromStream(bp, in, numextrabitsD);
            size_t start = pos, back = start - dist;
            if (pos + length >= out.size()) out.resize((pos + length) * 2);
            for (size_t i = 0; i < length; i++) { out[pos++] = out[back++]; if (back >= start) back = start - dist; }
          }
        }
      }
      void inflateNoCompression(std::vector<unsigned char>& out, const unsigned char* in, size_t& bp, size_t& pos, size_t inlength)
      {
        while ((bp & 0x7) != 0) bp++;
        size_t p = bp / 8;
        if (p >= inlength - 4) { error = 52; return; }
        unsigned long LEN = in[p] + 256 * in[p + 1], NLEN = in[p + 2] + 256 * in[p + 3]; p += 4;
        if (LEN + NLEN != 65535) { error = 21; return; }
        if (pos + LEN >= out.size()) out.resize(pos + LEN);
        if (p + LEN > inlength) { error = 23; return; }
        for (unsigned long n = 0; n < LEN; n++) out[pos++] = in[p++];
        bp = p * 8;
      }
    };
    int decompress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in)
    {
      Inflator inflator;
      if (in.size() < 2) { return 53; }
      if ((in[0] * 256 + in[1]) % 31 != 0) { return 24; }
      unsigned long CM = in[0] & 15, CINFO = (in[0] >> 4) & 15, FDICT = (in[1] >> 5) & 1;
      if (CM != 8 || CINFO > 7) { return 25; }
      if (FDICT != 0) { return 26; }
      inflator.inflate(out, in, 2);
      return inflator.error;
    }
  };
  struct PNG
  {
    struct Info
    {
      unsigned long width, height, colorType, bitDepth, compressionMethod, filterMethod, interlaceMethod, key_r, key_g, key_b;
      bool key_defined;
      std::vector<unsigned char> palette;
    } info;
    int error;
    void decode(std::vector<unsigned char>& out, const unsigned char* in, size_t size, bool convert_to_rgba32)
    {
      error = 0;
      if (size == 0 || in == 0) { error = 48; return; }
      readPngHeader(&in[0], size); if (error) return;
      size_t pos = 33;
      std::vector<unsigned char> idat;
      bool IEND = false, known_type = true;
      info.key_defined = false;
      while (!IEND)
      {
        if (pos + 8 >= size) { error = 30; return; }
        size_t chunkLength = read32bitInt(&in[pos]); pos += 4;
        if (chunkLength > 2147483647) { error = 63; return; }
        if (pos + chunkLength >= size) { error = 35; return; }
        if (in[pos + 0] == 'I' && in[pos + 1] == 'D' && in[pos + 2] == 'A' && in[pos + 3] == 'T')
        {
          idat.insert(idat.end(), &in[pos + 4], &in[pos + 4 + chunkLength]);
          pos += (4 + chunkLength);
        }
        else if (in[pos + 0] == 'I' && in[pos + 1] == 'E' && in[pos + 2] == 'N' && in[pos + 3] == 'D') { pos += 4; IEND = true; }
        else if (in[pos + 0] == 'P' && in[pos + 1] == 'L' && in[pos + 2] == 'T' && in[pos + 3] == 'E')
        {
          pos += 4;
          info.palette.resize(4 * (chunkLength / 3));
          if (info.palette.size() > (4 * 256)) { error = 38; return; }
          for (size_t i = 0; i < info.palette.size(); i += 4)
          {
            for (size_t j = 0; j < 3; j++) info.palette[i + j] = in[pos++];
            info.palette[i + 3] = 255;
          }
        }
        else if (in[pos + 0] == 't' && in[pos + 1] == 'R' && in[pos + 2] == 'N' && in[pos + 3] == 'S')
        {
          pos += 4;
          if (info.colorType == 3)
          {
            if (4 * chunkLength > info.palette.size()) { error = 39; return; }
            for (size_t i = 0; i < chunkLength; i++) info.palette[4 * i + 3] = in[pos++];
          }
          else if (info.colorType == 0)
          {
            if (chunkLength != 2) { error = 40; return; }
            info.key_defined = 1; info.key_r = info.key_g = info.key_b = 256 * in[pos] + in[pos + 1]; pos += 2;
          }
          else if (info.colorType == 2)
          {
            if (chunkLength != 6) { error = 41; return; }
            info.key_defined = 1;
            info.key_r = 256 * in[pos] + in[pos + 1]; pos += 2;
            info.key_g = 256 * in[pos] + in[pos + 1]; pos += 2;
            info.key_b = 256 * in[pos] + in[pos + 1]; pos += 2;
          }
          else { error = 42; return; }
        }
        else
        {
          if (!(in[pos + 0] & 32)) { error = 69; return; }
          pos += (chunkLength + 4);
          known_type = false;
        }
        pos += 4;
      }
      unsigned long bpp = getBpp(info);
      std::vector<unsigned char> scanlines(((info.width * (info.height * bpp + 7)) / 8) + info.height);
      Zlib zlib;
      error = zlib.decompress(scanlines, idat); if (error) return;
      size_t bytewidth = (bpp + 7) / 8, outlength = (info.height * info.width * bpp + 7) / 8;
      out.resize(outlength);
      unsigned char* out_ = outlength ? &out[0] : 0;
      if (info.interlaceMethod == 0)
      {
        size_t linestart = 0, linelength = (info.width * bpp + 7) / 8;
        if (bpp >= 8)
          for (unsigned long y = 0; y < info.height; y++)
          {
            unsigned long filterType = scanlines[linestart];
            const unsigned char* prevline = (y == 0) ? 0 : &out_[(y - 1) * info.width * bytewidth];
            unFilterScanline(&out_[linestart - y], &scanlines[linestart + 1], prevline, bytewidth, filterType, linelength); if (error) return;
            linestart += (1 + linelength);
          }
        else
        {
          std::vector<unsigned char> templine((info.width * bpp + 7) >> 3);
          for (size_t y = 0, obp = 0; y < info.height; y++)
          {
            unsigned long filterType = scanlines[linestart];
            const unsigned char* prevline = (y == 0) ? 0 : &out_[(y - 1) * info.width * bytewidth];
            unFilterScanline(&templine[0], &scanlines[linestart + 1], prevline, bytewidth, filterType, linelength); if (error) return;
            for (size_t bp = 0; bp < info.width * bpp;) setBitOfReversedStream(obp, out_, readBitFromReversedStream(bp, &templine[0]));
            linestart += (1 + linelength);
          }
        }
      }
      else
      {
        size_t passw[7] = { (info.width + 7) / 8, (info.width + 3) / 8, (info.width + 3) / 4, (info.width + 1) / 4, (info.width + 1) / 2, (info.width + 0) / 2, (info.width + 0) / 1 };
        size_t passh[7] = { (info.height + 7) / 8, (info.height + 7) / 8, (info.height + 3) / 8, (info.height + 3) / 4, (info.height + 1) / 4, (info.height + 1) / 2, (info.height + 0) / 2 };
        size_t passstart[7] = { 0 };
        size_t pattern[28] = { 0,4,0,2,0,1,0,0,0,4,0,2,0,1,8,8,4,4,2,2,1,8,8,8,4,4,2,2 };
        for (int i = 0; i < 6; i++) passstart[i + 1] = passstart[i] + passh[i] * ((passw[i] ? 1 : 0) + (passw[i] * bpp + 7) / 8);
        std::vector<unsigned char> scanlineo((info.width * bpp + 7) / 8), scanlinen((info.width * bpp + 7) / 8);
        for (int i = 0; i < 7; i++)
          adam7Pass(&out_[0], &scanlinen[0], &scanlineo[0], &scanlines[passstart[i]], info.width, pattern[i], pattern[i + 7], pattern[i + 14], pattern[i + 21], passw[i], passh[i], bpp);
      }
      if (convert_to_rgba32 && (info.colorType != 6 || info.bitDepth != 8))
      {
        std::vector<unsigned char> data = out;
        error = convert(out, &data[0], info, info.width, info.height);
      }
    }
    void readPngHeader(const unsigned char* in, size_t inlength)
    {
      if (inlength < 29) { error = 27; return; }
      if (in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { error = 28; return; }
      if (in[12] != 'I' || in[13] != 'H' || in[14] != 'D' || in[15] != 'R') { error = 29; return; }
      info.width = read32bitInt(&in[16]); info.height = read32bitInt(&in[20]);
      info.bitDepth = in[24]; info.colorType = in[25];
      info.compressionMethod = in[26]; if (in[26] != 0) { error = 32; return; }
      info.filterMethod = in[27]; if (in[27] != 0) { error = 33; return; }
      info.interlaceMethod = in[28]; if (in[28] > 1) { error = 34; return; }
      error = checkColorValidity(info.colorType, info.bitDepth);
    }
    void unFilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, size_t bytewidth, unsigned long filterType, size_t length)
    {
      switch (filterType)
      {
      case 0: for (size_t i = 0; i < length; i++) recon[i] = scanline[i]; break;
      case 1:
        for (size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i];
        for (size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth];
        break;
      case 2:
        if (precon) for (size_t i = 0; i < length; i++) recon[i] = scanline[i] + precon[i];
        else       for (size_t i = 0; i < length; i++) recon[i] = scanline[i];
        break;
      case 3:
        if (precon)
        {
          for (size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i] + precon[i] / 2;
          for (size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) / 2);
        }
        else
        {
          for (size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i];
          for (size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth] / 2;
        }
        break;
      case 4:
        if (precon)
        {
          for (size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i] + paethPredictor(0, precon[i], 0);
          for (size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth]);
        }
        else
        {
          for (size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i];
          for (size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + paethPredictor(recon[i - bytewidth], 0, 0);
        }
        break;
      default: error = 36; return;
      }
    }
    void adam7Pass(unsigned char* out, unsigned char* linen, unsigned char* lineo, const unsigned char* in, unsigned long w, size_t passleft, size_t passtop, size_t spacex, size_t spacey, size_t passw, size_t passh, unsigned long bpp)
    {
      if (passw == 0) return;
      size_t bytewidth = (bpp + 7) / 8, linelength = 1 + ((bpp * passw + 7) / 8);
      for (unsigned long y = 0; y < passh; y++)
      {
        unsigned char filterType = in[y * linelength], * prevline = (y == 0) ? 0 : lineo;
        unFilterScanline(linen, &in[y * linelength + 1], prevline, bytewidth, filterType, (w * bpp + 7) / 8); if (error) return;
        if (bpp >= 8) for (size_t i = 0; i < passw; i++) for (size_t b = 0; b < bytewidth; b++)
          out[bytewidth * w * (passtop + spacey * y) + bytewidth * (passleft + spacex * i) + b] = linen[bytewidth * i + b];
        else for (size_t i = 0; i < passw; i++)
        {
          size_t obp = bpp * w * (passtop + spacey * y) + bpp * (passleft + spacex * i), bp = i * bpp;
          for (size_t b = 0; b < bpp; b++) setBitOfReversedStream(obp, out, readBitFromReversedStream(bp, &linen[0]));
        }
        unsigned char* temp = linen; linen = lineo; lineo = temp;
      }
    }
    static unsigned long readBitFromReversedStream(size_t& bitp, const unsigned char* bits) { unsigned long result = (bits[bitp >> 3] >> (7 - (bitp & 0x7))) & 1; bitp++; return result; }
    static unsigned long readBitsFromReversedStream(size_t& bitp, const unsigned char* bits, unsigned long nbits)
    {
      unsigned long result = 0;
      for (size_t i = nbits - 1; i < nbits; i--) result += ((readBitFromReversedStream(bitp, bits)) << i);
      return result;
    }
    void setBitOfReversedStream(size_t& bitp, unsigned char* bits, unsigned long bit) { bits[bitp >> 3] |= (bit << (7 - (bitp & 0x7))); bitp++; }
    unsigned long read32bitInt(const unsigned char* buffer) { return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; }
    int checkColorValidity(unsigned long colorType, unsigned long bd)
    {
      if ((colorType == 2 || colorType == 4 || colorType == 6)) { if (!(bd == 8 || bd == 16)) return 37; else return 0; }
      else if (colorType == 0) { if (!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; else return 0; }
      else if (colorType == 3) { if (!(bd == 1 || bd == 2 || bd == 4 || bd == 8)) return 37; else return 0; }
      else return 31;
    }
    unsigned long getBpp(const Info& info)
    {
      if (info.colorType == 2) return (3 * info.bitDepth);
      else if (info.colorType >= 4) return (info.colorType - 2) * info.bitDepth;
      else return info.bitDepth;
    }
    int convert(std::vector<unsigned char>& out, const unsigned char* in, Info& infoIn, unsigned long w, unsigned long h)
    {
      size_t numpixels = w * h, bp = 0;
      out.resize(numpixels * 4);
      unsigned char* out_ = out.empty() ? 0 : &out[0];
      if (infoIn.bitDepth == 8 && infoIn.colorType == 0)
        for (size_t i = 0; i < numpixels; i++)
        {
          out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[i];
          out_[4 * i + 3] = (infoIn.key_defined && in[i] == infoIn.key_r) ? 0 : 255;
        }
      else if (infoIn.bitDepth == 8 && infoIn.colorType == 2)
        for (size_t i = 0; i < numpixels; i++)
        {
          for (size_t c = 0; c < 3; c++) out_[4 * i + c] = in[3 * i + c];
          out_[4 * i + 3] = (infoIn.key_defined == 1 && in[3 * i + 0] == infoIn.key_r && in[3 * i + 1] == infoIn.key_g && in[3 * i + 2] == infoIn.key_b) ? 0 : 255;
        }
      else if (infoIn.bitDepth == 8 && infoIn.colorType == 3)
        for (size_t i = 0; i < numpixels; i++)
        {
          if (4U * in[i] >= infoIn.palette.size()) return 46;
          for (size_t c = 0; c < 4; c++) out_[4 * i + c] = infoIn.palette[4 * in[i] + c];
        }
      else if (infoIn.bitDepth == 8 && infoIn.colorType == 4)
        for (size_t i = 0; i < numpixels; i++)
        {
          out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[2 * i + 0];
          out_[4 * i + 3] = in[2 * i + 1];
        }
      else if (infoIn.bitDepth == 8 && infoIn.colorType == 6) for (size_t i = 0; i < numpixels; i++) for (size_t c = 0; c < 4; c++) out_[4 * i + c] = in[4 * i + c];
      else if (infoIn.bitDepth == 16 && infoIn.colorType == 0)
        for (size_t i = 0; i < numpixels; i++)
        {
          out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[2 * i];
          out_[4 * i + 3] = (infoIn.key_defined && 256U * in[i] + in[i + 1] == infoIn.key_r) ? 0 : 255;
        }
      else if (infoIn.bitDepth == 16 && infoIn.colorType == 2)
        for (size_t i = 0; i < numpixels; i++)
        {
          for (size_t c = 0; c < 3; c++) out_[4 * i + c] = in[6 * i + 2 * c];
          out_[4 * i + 3] = (infoIn.key_defined && 256U * in[6 * i + 0] + in[6 * i + 1] == infoIn.key_r && 256U * in[6 * i + 2] + in[6 * i + 3] == infoIn.key_g && 256U * in[6 * i + 4] + in[6 * i + 5] == infoIn.key_b) ? 0 : 255;
        }
      else if (infoIn.bitDepth == 16 && infoIn.colorType == 4)
        for (size_t i = 0; i < numpixels; i++)
        {
          out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[4 * i];
          out_[4 * i + 3] = in[4 * i + 2];
        }
      else if (infoIn.bitDepth == 16 && infoIn.colorType == 6) for (size_t i = 0; i < numpixels; i++) for (size_t c = 0; c < 4; c++) out_[4 * i + c] = in[8 * i + 2 * c];
      else if (infoIn.bitDepth < 8 && infoIn.colorType == 0)
        for (size_t i = 0; i < numpixels; i++)
        {
          unsigned long value = (readBitsFromReversedStream(bp, in, infoIn.bitDepth) * 255) / ((1 << infoIn.bitDepth) - 1);
          out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = (unsigned char)(value);
          out_[4 * i + 3] = (infoIn.key_defined && value && ((1U << infoIn.bitDepth) - 1U) == infoIn.key_r && ((1U << infoIn.bitDepth) - 1U)) ? 0 : 255;
        }
      else if (infoIn.bitDepth < 8 && infoIn.colorType == 3)
        for (size_t i = 0; i < numpixels; i++)
        {
          unsigned long value = readBitsFromReversedStream(bp, in, infoIn.bitDepth);
          if (4 * value >= infoIn.palette.size()) return 47;
          for (size_t c = 0; c < 4; c++) out_[4 * i + c] = infoIn.palette[4 * value + c];
        }
      return 0;
    }
    unsigned char paethPredictor(short a, short b, short c)
    {
      short p = a + b - c, pa = p > a ? (p - a) : (a - p), pb = p > b ? (p - b) : (b - p), pc = p > c ? (p - c) : (c - p);
      return (unsigned char)((pa <= pb && pa <= pc) ? a : pb <= pc ? b : c);
    }
  };
  PNG decoder; decoder.decode(out_image, in_png, in_size, convert_to_rgba32);
  image_width = decoder.info.width; image_height = decoder.info.height;
  return decoder.error;
}




struct fastObjTexture
{
  /* Texture name from .mtl file */
  char* name;

  /* Resolved path to texture */
  char* path;

};

struct fastObjMaterial
{
  /* Material name */
  char* name;

  /* Parameters */
  float                       Ka[3];  /* Ambient */
  float                       Kd[3];  /* Diffuse */
  float                       Ks[3];  /* Specular */
  float                       Ke[3];  /* Emission */
  float                       Kt[3];  /* Transmittance */
  float                       Ns;     /* Shininess */
  float                       Ni;     /* Index of refraction */
  float                       Tf[3];  /* Transmission filter */
  float                       d;      /* Disolve (alpha) */
  int                         illum;  /* Illumination model */

  /* Texture maps */
  fastObjTexture              map_Ka;
  fastObjTexture              map_Kd;
  fastObjTexture              map_Ks;
  fastObjTexture              map_Ke;
  fastObjTexture              map_Kt;
  fastObjTexture              map_Ns;
  fastObjTexture              map_Ni;
  fastObjTexture              map_d;
  fastObjTexture              map_bump;

};

typedef unsigned int fastObjUInt;

struct fastObjIndex
{
  fastObjUInt p;
  fastObjUInt t;
  fastObjUInt n;

};

struct fastObjGroup
{
  /* Group name */
  char* name;

  /* Number of faces */
  unsigned int                face_count;

  /* First face in fastObjMesh face_* arrays */
  unsigned int                face_offset;

  /* First index in fastObjMesh indices array */
  unsigned int                index_offset;

};

struct fastObjMesh
{
  /* Vertex data */
  unsigned int                position_count;
  float* positions;

  unsigned int                texcoord_count;
  float* texcoords;

  unsigned int                normal_count;
  float* normals;

  /* Face data: one element for each face */
  unsigned int                face_count;
  unsigned int* face_vertices;
  unsigned int* face_materials;

  /* Index data: one element for each face vertex */
  unsigned int                index_count;
  fastObjIndex* indices;

  /* Materials */
  unsigned int                material_count;
  fastObjMaterial* materials;

  /* Mesh objects ('o' tag in .obj file) */
  unsigned int                object_count;
  fastObjGroup* objects;

  /* Mesh groups ('g' tag in .obj file) */
  unsigned int                group_count;
  fastObjGroup* groups;

};

static fastObjMesh* decodeOBJ(const char* path)
{
  /*
  * fast_obj
  *
  * Version 1.2
  *
  * MIT License
  *
  * Copyright (c) 2018-2021 Richard Knight
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included in all
  * copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  * SOFTWARE.
  *
  */


#ifdef _WIN32
#define FAST_OBJ_SEPARATOR      '\\'
#define FAST_OBJ_OTHER_SEP      '/'
#else
#define FAST_OBJ_SEPARATOR      '/'
#define FAST_OBJ_OTHER_SEP      '\\'
#endif

  /* Size of buffer to read into */
#define BUFFER_SIZE             65536

/* Max supported power when parsing float */
#define MAX_POWER               20

  static const double POWER_10_POS[MAX_POWER] =
  {
      1.0e0,  1.0e1,  1.0e2,  1.0e3,  1.0e4,  1.0e5,  1.0e6,  1.0e7,  1.0e8,  1.0e9,
      1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19,
  };

  static const double POWER_10_NEG[MAX_POWER] =
  {
      1.0e0,   1.0e-1,  1.0e-2,  1.0e-3,  1.0e-4,  1.0e-5,  1.0e-6,  1.0e-7,  1.0e-8,  1.0e-9,
      1.0e-10, 1.0e-11, 1.0e-12, 1.0e-13, 1.0e-14, 1.0e-15, 1.0e-16, 1.0e-17, 1.0e-18, 1.0e-19,
  };

  typedef struct
  {
    void* (*file_open)(const char* path, void* user_data);
    void (*file_close)(void* file, void* user_data);
    size_t(*file_read)(void* file, void* dst, size_t bytes, void* user_data);
    unsigned long (*file_size)(void* file, void* user_data);
  } fastObjCallbacks;

  struct obj
  {
    struct fastObjData
    {
      /* Final mesh */
      fastObjMesh* mesh;

      /* Current object/group */
      fastObjGroup                object;
      fastObjGroup                group;

      /* Current material index */
      unsigned int                material;

      /* Current line in file */
      unsigned int                line;

      /* Base path for materials/textures */
      char* base;

    };

#define array_clean(_arr)       ((_arr) ? free(_array_header(_arr)), 0 : 0)
#define array_push(_arr, _val)  (_array_mgrow(_arr, 1) ? ((_arr)[_array_size(_arr)++] = (_val), _array_size(_arr) - 1) : 0)
#define array_size(_arr)        ((_arr) ? _array_size(_arr) : 0)
#define array_capacity(_arr)    ((_arr) ? _array_capacity(_arr) : 0)
#define array_empty(_arr)       (array_size(_arr) == 0)

#define _array_header(_arr)     ((fastObjUInt*)(_arr)-2)
#define _array_size(_arr)       (_array_header(_arr)[0])
#define _array_capacity(_arr)   (_array_header(_arr)[1])
#define _array_ngrow(_arr, _n)  ((_arr) == 0 || (_array_size(_arr) + (_n) >= _array_capacity(_arr)))
#define _array_mgrow(_arr, _n)  (_array_ngrow(_arr, _n) ? (_array_grow(_arr, _n) != 0) : 1)
#define _array_grow(_arr, _n)   (*((void**)&(_arr)) = array_realloc(_arr, _n, sizeof(*(_arr))))

    static void* array_realloc(void* ptr, fastObjUInt n, fastObjUInt b)
    {
      fastObjUInt sz = array_size(ptr);
      fastObjUInt nsz = sz + n;
      fastObjUInt cap = array_capacity(ptr);
      fastObjUInt ncap = 3 * cap / 2;
      fastObjUInt* r;


      if (ncap < nsz)
        ncap = nsz;
      ncap = (ncap + 15) & ~15u;

      r = (fastObjUInt*)(realloc(ptr ? _array_header(ptr) : 0, b * ncap + 2 * sizeof(fastObjUInt)));
      if (!r)
        return 0;

      r[0] = sz;
      r[1] = ncap;

      return (r + 2);
    }

    static char* string_copy(const char* s, const char* e)
    {
      size_t n;
      char* p;

      n = (size_t)(e - s);
      p = (char*)(realloc(0, n + 1));
      if (p)
      {
        memcpy(p, s, n);
        p[n] = '\0';
      }

      return p;
    }

    static char* string_substr(const char* s, size_t a, size_t b)
    {
      return string_copy(s + a, s + b);
    }

    static char* string_concat(const char* a, const char* s, const char* e)
    {
      size_t an;
      size_t sn;
      char* p;

      an = a ? strlen(a) : 0;
      sn = (size_t)(e - s);
      p = (char*)(realloc(0, an + sn + 1));
      if (p)
      {
        if (a)
          memcpy(p, a, an);
        memcpy(p + an, s, sn);
        p[an + sn] = '\0';
      }

      return p;
    }

    static int string_equal(const char* a, const char* s, const char* e)
    {
      size_t an = strlen(a);
      size_t sn = (size_t)(e - s);

      return an == sn && memcmp(a, s, an) == 0;
    }

    static void string_fix_separators(char* s)
    {
      while (*s)
      {
        if (*s == FAST_OBJ_OTHER_SEP)
          *s = FAST_OBJ_SEPARATOR;
        s++;
      }
    }

    static int is_whitespace(char c)
    {
      return (c == ' ' || c == '\t' || c == '\r');
    }

    static int is_end_of_name(char c)
    {
      return (c == '\t' || c == '\r' || c == '\n');
    }

    static int is_newline(char c)
    {
      return (c == '\n');
    }

    static int is_digit(char c)
    {
      return (c >= '0' && c <= '9');
    }

    static int is_exponent(char c)
    {
      return (c == 'e' || c == 'E');
    }

    static const char* skip_whitespace(const char* ptr)
    {
      while (is_whitespace(*ptr))
        ptr++;

      return ptr;
    }

    static const char* skip_line(const char* ptr)
    {
      while (!is_newline(*ptr++))
        ;

      return ptr;
    }

    static fastObjGroup object_default(void)
    {
      fastObjGroup object;

      object.name = 0;
      object.face_count = 0;
      object.face_offset = 0;
      object.index_offset = 0;

      return object;
    }

    static void object_clean(fastObjGroup* object)
    {
      free(object->name);
    }

    static void flush_object(fastObjData* data)
    {
      /* Add object if not empty */
      if (data->object.face_count > 0)
        array_push(data->mesh->objects, data->object);
      else
        object_clean(&data->object);

      /* Reset for more data */
      data->object = object_default();
      data->object.face_offset = array_size(data->mesh->face_vertices);
      data->object.index_offset = array_size(data->mesh->indices);
    }

    static fastObjGroup group_default(void)
    {
      fastObjGroup group;

      group.name = 0;
      group.face_count = 0;
      group.face_offset = 0;
      group.index_offset = 0;

      return group;
    }

    static void group_clean(fastObjGroup* group)
    {
      free(group->name);
    }

    static void flush_group(fastObjData* data)
    {
      /* Add group if not empty */
      if (data->group.face_count > 0)
        array_push(data->mesh->groups, data->group);
      else
        group_clean(&data->group);

      /* Reset for more data */
      data->group = group_default();
      data->group.face_offset = array_size(data->mesh->face_vertices);
      data->group.index_offset = array_size(data->mesh->indices);
    }

    static const char* parse_int(const char* ptr, int* val)
    {
      int sign;
      int num;


      if (*ptr == '-')
      {
        sign = -1;
        ptr++;
      }
      else
      {
        sign = +1;
      }

      num = 0;
      while (is_digit(*ptr))
        num = 10 * num + (*ptr++ - '0');

      *val = sign * num;

      return ptr;
    }

    static const char* parse_float(const char* ptr, float* val)
    {
      double        sign;
      double        num;
      double        fra;
      double        div;
      unsigned int  eval;
      const double* powers;

      ptr = skip_whitespace(ptr);

      switch (*ptr)
      {
      case '+':
        sign = 1.0;
        ptr++;
        break;

      case '-':
        sign = -1.0;
        ptr++;
        break;

      default:
        sign = 1.0;
        break;
      }


      num = 0.0;
      while (is_digit(*ptr))
        num = 10.0 * num + (double)(*ptr++ - '0');

      if (*ptr == '.')
        ptr++;

      fra = 0.0;
      div = 1.0;

      while (is_digit(*ptr))
      {
        fra = 10.0 * fra + (double)(*ptr++ - '0');
        div *= 10.0;
      }

      num += fra / div;

      if (is_exponent(*ptr))
      {
        ptr++;

        switch (*ptr)
        {
        case '+':
          powers = POWER_10_POS;
          ptr++;
          break;

        case '-':
          powers = POWER_10_NEG;
          ptr++;
          break;

        default:
          powers = POWER_10_POS;
          break;
        }

        eval = 0;
        while (is_digit(*ptr))
          eval = 10 * eval + (*ptr++ - '0');

        num *= (eval >= MAX_POWER) ? 0.0 : powers[eval];
      }

      *val = (float)(sign * num);

      return ptr;
    }

    static const char* parse_vertex(fastObjData* data, const char* ptr)
    {
      unsigned int ii;
      float        v;


      for (ii = 0; ii < 3; ii++)
      {
        ptr = parse_float(ptr, &v);
        array_push(data->mesh->positions, v);
      }

      return ptr;
    }

    static const char* parse_texcoord(fastObjData* data, const char* ptr)
    {
      unsigned int ii;
      float        v;


      for (ii = 0; ii < 2; ii++)
      {
        ptr = parse_float(ptr, &v);
        array_push(data->mesh->texcoords, v);
      }

      return ptr;
    }

    static const char* parse_normal(fastObjData* data, const char* ptr)
    {
      unsigned int ii;
      float        v;


      for (ii = 0; ii < 3; ii++)
      {
        ptr = parse_float(ptr, &v);
        array_push(data->mesh->normals, v);
      }

      return ptr;
    }

    static const char* parse_face(fastObjData* data, const char* ptr)
    {
      unsigned int count;
      fastObjIndex vn;
      int          v;
      int          t;
      int          n;


      ptr = skip_whitespace(ptr);

      count = 0;
      while (!is_newline(*ptr))
      {
        v = 0;
        t = 0;
        n = 0;

        ptr = parse_int(ptr, &v);
        if (*ptr == '/')
        {
          ptr++;
          if (*ptr != '/')
            ptr = parse_int(ptr, &t);

          if (*ptr == '/')
          {
            ptr++;
            ptr = parse_int(ptr, &n);
          }
        }

        if (v < 0)
          vn.p = (array_size(data->mesh->positions) / 3) - (fastObjUInt)(-v);
        else
          vn.p = (fastObjUInt)(v);

        if (t < 0)
          vn.t = (array_size(data->mesh->texcoords) / 2) - (fastObjUInt)(-t);
        else if (t > 0)
          vn.t = (fastObjUInt)(t);
        else
          vn.t = 0;

        if (n < 0)
          vn.n = (array_size(data->mesh->normals) / 3) - (fastObjUInt)(-n);
        else if (n > 0)
          vn.n = (fastObjUInt)(n);
        else
          vn.n = 0;

        array_push(data->mesh->indices, vn);
        count++;

        ptr = skip_whitespace(ptr);
      }

      array_push(data->mesh->face_vertices, count);
      array_push(data->mesh->face_materials, data->material);

      data->group.face_count++;
      data->object.face_count++;

      return ptr;
    }

    static const char* parse_object(fastObjData* data, const char* ptr)
    {
      const char* s;
      const char* e;


      ptr = skip_whitespace(ptr);

      s = ptr;
      while (!is_end_of_name(*ptr))
        ptr++;

      e = ptr;

      flush_object(data);
      data->object.name = string_copy(s, e);

      return ptr;
    }

    static const char* parse_group(fastObjData* data, const char* ptr)
    {
      const char* s;
      const char* e;


      ptr = skip_whitespace(ptr);

      s = ptr;
      while (!is_end_of_name(*ptr))
        ptr++;

      e = ptr;

      flush_group(data);
      data->group.name = string_copy(s, e);

      return ptr;
    }

    static fastObjTexture map_default(void)
    {
      fastObjTexture map;

      map.name = 0;
      map.path = 0;

      return map;
    }

    static fastObjMaterial mtl_default(void)
    {
      fastObjMaterial mtl;

      mtl.name = 0;

      mtl.Ka[0] = 0.0;
      mtl.Ka[1] = 0.0;
      mtl.Ka[2] = 0.0;
      mtl.Kd[0] = 1.0;
      mtl.Kd[1] = 1.0;
      mtl.Kd[2] = 1.0;
      mtl.Ks[0] = 0.0;
      mtl.Ks[1] = 0.0;
      mtl.Ks[2] = 0.0;
      mtl.Ke[0] = 0.0;
      mtl.Ke[1] = 0.0;
      mtl.Ke[2] = 0.0;
      mtl.Kt[0] = 0.0;
      mtl.Kt[1] = 0.0;
      mtl.Kt[2] = 0.0;
      mtl.Ns = 1.0;
      mtl.Ni = 1.0;
      mtl.Tf[0] = 1.0;
      mtl.Tf[1] = 1.0;
      mtl.Tf[2] = 1.0;
      mtl.d = 1.0;
      mtl.illum = 1;

      mtl.map_Ka = map_default();
      mtl.map_Kd = map_default();
      mtl.map_Ks = map_default();
      mtl.map_Ke = map_default();
      mtl.map_Kt = map_default();
      mtl.map_Ns = map_default();
      mtl.map_Ni = map_default();
      mtl.map_d = map_default();
      mtl.map_bump = map_default();

      return mtl;
    }

    static const char* parse_usemtl(fastObjData* data, const char* ptr)
    {
      const char* s;
      const char* e;
      unsigned int     idx;
      fastObjMaterial* mtl;


      ptr = skip_whitespace(ptr);

      /* Parse the material name */
      s = ptr;
      while (!is_end_of_name(*ptr))
        ptr++;

      e = ptr;

      /* Find an existing material with the same name */
      idx = 0;
      while (idx < array_size(data->mesh->materials))
      {
        mtl = &data->mesh->materials[idx];
        if (mtl->name && string_equal(mtl->name, s, e))
          break;

        idx++;
      }

      /* If doesn't exists, create a default one with this name
      Note: this case happens when OBJ doesn't have its MTL */
      if (idx == array_size(data->mesh->materials))
      {
        fastObjMaterial new_mtl = mtl_default();
        new_mtl.name = string_copy(s, e);
        array_push(data->mesh->materials, new_mtl);
      }

      data->material = idx;

      return ptr;
    }

    static void map_clean(fastObjTexture* map)
    {
      free(map->name);
      free(map->path);
    }

    static void mtl_clean(fastObjMaterial* mtl)
    {
      map_clean(&mtl->map_Ka);
      map_clean(&mtl->map_Kd);
      map_clean(&mtl->map_Ks);
      map_clean(&mtl->map_Ke);
      map_clean(&mtl->map_Kt);
      map_clean(&mtl->map_Ns);
      map_clean(&mtl->map_Ni);
      map_clean(&mtl->map_d);
      map_clean(&mtl->map_bump);

      free(mtl->name);
    }

    static const char* read_mtl_int(const char* p, int* v)
    {
      return parse_int(p, v);
    }


    static const char* read_mtl_single(const char* p, float* v)
    {
      return parse_float(p, v);
    }

    static const char* read_mtl_triple(const char* p, float v[3])
    {
      p = read_mtl_single(p, &v[0]);
      p = read_mtl_single(p, &v[1]);
      p = read_mtl_single(p, &v[2]);

      return p;
    }

    static const char* read_map(fastObjData* data, const char* ptr, fastObjTexture* map)
    {
      const char* s;
      const char* e;
      char* name;
      char* path;

      ptr = skip_whitespace(ptr);

      /* Don't support options at present */
      if (*ptr == '-')
        return ptr;


      /* Read name */
      s = ptr;
      while (!is_end_of_name(*ptr))
        ptr++;

      e = ptr;

      name = string_copy(s, e);

      path = string_concat(data->base, s, e);
      string_fix_separators(path);

      map->name = name;
      map->path = path;

      return e;
    }

    static int read_mtllib(fastObjData* data, void* file, const fastObjCallbacks* callbacks, void* user_data)
    {
      unsigned long   n;
      const char* s;
      char* contents;
      size_t          l;
      const char* p;
      const char* e;
      int             found_d;
      fastObjMaterial mtl;


      /* Read entire file */
      n = callbacks->file_size(file, user_data);

      contents = (char*)(realloc(0, n + 1));
      if (!contents)
        return 0;

      l = callbacks->file_read(file, contents, n, user_data);
      contents[l] = '\n';

      mtl = mtl_default();

      found_d = 0;

      p = contents;
      e = contents + l;
      while (p < e)
      {
        p = skip_whitespace(p);

        switch (*p)
        {
        case 'n':
          p++;
          if (p[0] == 'e' &&
            p[1] == 'w' &&
            p[2] == 'm' &&
            p[3] == 't' &&
            p[4] == 'l' &&
            is_whitespace(p[5]))
          {
            /* Push previous material (if there is one) */
            if (mtl.name)
            {
              array_push(data->mesh->materials, mtl);
              mtl = mtl_default();
            }


            /* Read name */
            p += 5;

            while (is_whitespace(*p))
              p++;

            s = p;
            while (!is_end_of_name(*p))
              p++;

            mtl.name = string_copy(s, p);
          }
          break;

        case 'K':
          if (p[1] == 'a')
            p = read_mtl_triple(p + 2, mtl.Ka);
          else if (p[1] == 'd')
            p = read_mtl_triple(p + 2, mtl.Kd);
          else if (p[1] == 's')
            p = read_mtl_triple(p + 2, mtl.Ks);
          else if (p[1] == 'e')
            p = read_mtl_triple(p + 2, mtl.Ke);
          else if (p[1] == 't')
            p = read_mtl_triple(p + 2, mtl.Kt);
          break;

        case 'N':
          if (p[1] == 's')
            p = read_mtl_single(p + 2, &mtl.Ns);
          else if (p[1] == 'i')
            p = read_mtl_single(p + 2, &mtl.Ni);
          break;

        case 'T':
          if (p[1] == 'r')
          {
            float Tr;
            p = read_mtl_single(p + 2, &Tr);
            if (!found_d)
            {
              /* Ignore Tr if we've already read d */
              mtl.d = 1.0f - Tr;
            }
          }
          else if (p[1] == 'f')
            p = read_mtl_triple(p + 2, mtl.Tf);
          break;

        case 'd':
          if (is_whitespace(p[1]))
          {
            p = read_mtl_single(p + 1, &mtl.d);
            found_d = 1;
          }
          break;

        case 'i':
          p++;
          if (p[0] == 'l' &&
            p[1] == 'l' &&
            p[2] == 'u' &&
            p[3] == 'm' &&
            is_whitespace(p[4]))
          {
            p = read_mtl_int(p + 4, &mtl.illum);
          }
          break;

        case 'm':
          p++;
          if (p[0] == 'a' &&
            p[1] == 'p' &&
            p[2] == '_')
          {
            p += 3;
            if (*p == 'K')
            {
              p++;
              if (is_whitespace(p[1]))
              {
                if (*p == 'a')
                  p = read_map(data, p + 1, &mtl.map_Ka);
                else if (*p == 'd')
                  p = read_map(data, p + 1, &mtl.map_Kd);
                else if (*p == 's')
                  p = read_map(data, p + 1, &mtl.map_Ks);
                else if (*p == 'e')
                  p = read_map(data, p + 1, &mtl.map_Ke);
                else if (*p == 't')
                  p = read_map(data, p + 1, &mtl.map_Kt);
              }
            }
            else if (*p == 'N')
            {
              p++;
              if (is_whitespace(p[1]))
              {
                if (*p == 's')
                  p = read_map(data, p + 1, &mtl.map_Ns);
                else if (*p == 'i')
                  p = read_map(data, p + 1, &mtl.map_Ni);
              }
            }
            else if (*p == 'd')
            {
              p++;
              if (is_whitespace(*p))
                p = read_map(data, p, &mtl.map_d);
            }
            else if ((p[0] == 'b' || p[0] == 'B') &&
              p[1] == 'u' &&
              p[2] == 'm' &&
              p[3] == 'p' &&
              is_whitespace(p[4]))
            {
              p = read_map(data, p + 4, &mtl.map_bump);
            }
          }
          break;

        case '#':
          break;
        }

        p = skip_line(p);
      }

      /* Push final material */
      if (mtl.name)
        array_push(data->mesh->materials, mtl);

      free(contents);

      return 1;
    }

    static const char* parse_mtllib(fastObjData* data, const char* ptr, const fastObjCallbacks* callbacks, void* user_data)
    {
      const char* s;
      const char* e;
      char* lib;
      void* file;

      ptr = skip_whitespace(ptr);

      s = ptr;
      while (!is_end_of_name(*ptr))
        ptr++;

      e = ptr;

      lib = string_concat(data->base, s, e);
      if (lib)
      {
        string_fix_separators(lib);

        file = callbacks->file_open(lib, user_data);
        if (file)
        {
          read_mtllib(data, file, callbacks, user_data);
          callbacks->file_close(file, user_data);
        }

        free(lib);
      }

      return ptr;
    }

    static void parse_buffer(fastObjData* data, const char* ptr, const char* end, const fastObjCallbacks* callbacks, void* user_data)
    {
      const char* p;


      p = ptr;
      while (p != end)
      {
        p = skip_whitespace(p);

        switch (*p)
        {
        case 'v':
          p++;

          switch (*p++)
          {
          case ' ':
          case '\t':
            p = parse_vertex(data, p);
            break;

          case 't':
            p = parse_texcoord(data, p);
            break;

          case 'n':
            p = parse_normal(data, p);
            break;

          default:
            p--; /* roll p++ back in case *p was a newline */
          }
          break;

        case 'f':
          p++;

          switch (*p++)
          {
          case ' ':
          case '\t':
            p = parse_face(data, p);
            break;

          default:
            p--; /* roll p++ back in case *p was a newline */
          }
          break;

        case 'o':
          p++;

          switch (*p++)
          {
          case ' ':
          case '\t':
            p = parse_object(data, p);
            break;

          default:
            p--; /* roll p++ back in case *p was a newline */
          }
          break;

        case 'g':
          p++;

          switch (*p++)
          {
          case ' ':
          case '\t':
            p = parse_group(data, p);
            break;

          default:
            p--; /* roll p++ back in case *p was a newline */
          }
          break;

        case 'm':
          p++;
          if (p[0] == 't' &&
            p[1] == 'l' &&
            p[2] == 'l' &&
            p[3] == 'i' &&
            p[4] == 'b' &&
            is_whitespace(p[5]))
            p = parse_mtllib(data, p + 5, callbacks, user_data);
          break;

        case 'u':
          p++;
          if (p[0] == 's' &&
            p[1] == 'e' &&
            p[2] == 'm' &&
            p[3] == 't' &&
            p[4] == 'l' &&
            is_whitespace(p[5]))
            p = parse_usemtl(data, p + 5);
          break;

        case '#':
          break;
        }

        p = skip_line(p);

        data->line++;
      }
    }

    static fastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastObjCallbacks* callbacks, void* user_data)
    {
      fastObjData  data;
      fastObjMesh* m;
      void* file;
      char* buffer;
      char* start;
      char* end;
      char* last;
      fastObjUInt  read;
      fastObjUInt  bytes;

      /* Check if callbacks are valid */
      if (!callbacks)
        return 0;


      /* Open file */
      file = callbacks->file_open(path, user_data);
      if (!file)
        return 0;


      /* Empty mesh */
      m = (fastObjMesh*)(realloc(0, sizeof(fastObjMesh)));
      if (!m)
        return 0;

      m->positions = 0;
      m->texcoords = 0;
      m->normals = 0;
      m->face_vertices = 0;
      m->face_materials = 0;
      m->indices = 0;
      m->materials = 0;
      m->objects = 0;
      m->groups = 0;


      /* Add dummy position/texcoord/normal */
      array_push(m->positions, 0.0f);
      array_push(m->positions, 0.0f);
      array_push(m->positions, 0.0f);

      array_push(m->texcoords, 0.0f);
      array_push(m->texcoords, 0.0f);

      array_push(m->normals, 0.0f);
      array_push(m->normals, 0.0f);
      array_push(m->normals, 1.0f);


      /* Data needed during parsing */
      data.mesh = m;
      data.object = object_default();
      data.group = group_default();
      data.material = 0;
      data.line = 1;
      data.base = 0;


      /* Find base path for materials/textures */
      {
        const char* sep1 = strrchr(path, FAST_OBJ_SEPARATOR);
        const char* sep2 = strrchr(path, FAST_OBJ_OTHER_SEP);

        /* Use the last separator in the path */
        const char* sep = sep2 && (!sep1 || sep1 < sep2) ? sep2 : sep1;

        if (sep)
          data.base = string_substr(path, 0, sep - path + 1);
      }


      /* Create buffer for reading file */
      buffer = (char*)(realloc(0, 2 * BUFFER_SIZE * sizeof(char)));
      if (!buffer)
        return 0;

      start = buffer;
      for (;;)
      {
        /* Read another buffer's worth from file */
        read = (fastObjUInt)(callbacks->file_read(file, start, BUFFER_SIZE, user_data));
        if (read == 0 && start == buffer)
          break;


        /* Ensure buffer ends in a newline */
        if (read < BUFFER_SIZE)
        {
          if (read == 0 || start[read - 1] != '\n')
            start[read++] = '\n';
        }

        end = start + read;
        if (end == buffer)
          break;


        /* Find last new line */
        last = end;
        while (last > buffer)
        {
          last--;
          if (*last == '\n')
            break;
        }


        /* Check there actually is a new line */
        if (*last != '\n')
          break;

        last++;


        /* Process buffer */
        parse_buffer(&data, buffer, last, callbacks, user_data);


        /* Copy overflow for next buffer */
        bytes = (fastObjUInt)(end - last);
        memmove(buffer, last, bytes);
        start = buffer + bytes;
      }


      /* Flush final object/group */
      flush_object(&data);
      object_clean(&data.object);

      flush_group(&data);
      group_clean(&data.group);

      m->position_count = array_size(m->positions) / 3;
      m->texcoord_count = array_size(m->texcoords) / 2;
      m->normal_count = array_size(m->normals) / 3;
      m->face_count = array_size(m->face_vertices);
      m->index_count = array_size(m->indices);
      m->material_count = array_size(m->materials);
      m->object_count = array_size(m->objects);
      m->group_count = array_size(m->groups);


      /* Clean up */
      free(buffer);
      free(data.base);

      callbacks->file_close(file, user_data);

      return m;
    }

    static void* file_open(const char* path, void* user_data)
    {
      (void)(user_data);
      return fopen(path, "rb");
    }

    static void file_close(void* file, void* user_data)
    {
      FILE* f;
      (void)(user_data);

      f = (FILE*)(file);
      fclose(f);
    }

    static size_t file_read(void* file, void* dst, size_t bytes, void* user_data)
    {
      FILE* f;
      (void)(user_data);

      f = (FILE*)(file);
      return fread(dst, 1, bytes, f);
    }

    static unsigned long file_size(void* file, void* user_data)
    {
      FILE* f;
      long p;
      long n;
      (void)(user_data);

      f = (FILE*)(file);

      p = ftell(f);
      fseek(f, 0, SEEK_END);
      n = ftell(f);
      fseek(f, p, SEEK_SET);

      if (n > 0)
        return (unsigned long)(n);
      else
        return 0;
    }
  };

  fastObjCallbacks callbacks;
  callbacks.file_open = obj::file_open;
  callbacks.file_close = obj::file_close;
  callbacks.file_read = obj::file_read;
  callbacks.file_size = obj::file_size;

  return obj::fast_obj_read_with_callbacks(path, &callbacks, 0);
}

#if 1
//namespace GLTF
//{
  /**
 * cgltf - a single-file glTF 2.0 parser written in C99.
 *
 * Version: 1.15
 *
 * Website: https://github.com/jkuhlmann/cgltf
 *
 * Distributed under the MIT License, see notice at the end of this file.
 *
 * Building:
 * Include this file where you need the struct and function
 * declarations. Have exactly one source file where you define
 * `CGLTF_IMPLEMENTATION` before including this file to get the
 * function definitions.
 *
 * Reference:
 * `cgltf_result cgltf_parse(const cgltf_options*, const void*,
 * cgltf_size, cgltf_data**)` parses both glTF and GLB data. If
 * this function returns `cgltf_result_success`, you have to call
 * `cgltf_free()` on the created `cgltf_data*` variable.
 * Note that contents of external files for buffers and images are not
 * automatically loaded. You'll need to read these files yourself using
 * URIs in the `cgltf_data` structure.
 *
 * `cgltf_options` is the struct passed to `cgltf_parse()` to control
 * parts of the parsing process. You can use it to force the file type
 * and provide memory allocation as well as file operation callbacks.
 * Should be zero-initialized to trigger default behavior.
 *
 * `cgltf_data` is the struct allocated and filled by `cgltf_parse()`.
 * It generally mirrors the glTF format as described by the spec (see
 * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0).
 *
 * `void cgltf_free(cgltf_data*)` frees the allocated `cgltf_data`
 * variable.
 *
 * `cgltf_result cgltf_load_buffers(const cgltf_options*, cgltf_data*,
 * const char* gltf_path)` can be optionally called to open and read buffer
 * files using the `FILE*` APIs. The `gltf_path` argument is the path to
 * the original glTF file, which allows the parser to resolve the path to
 * buffer files.
 *
 * `cgltf_result cgltf_load_buffer_base64(const cgltf_options* options,
 * cgltf_size size, const char* base64, void** out_data)` decodes
 * base64-encoded data content. Used internally by `cgltf_load_buffers()`.
 * This is useful when decoding data URIs in images.
 *
 * `cgltf_result cgltf_parse_file(const cgltf_options* options, const
 * char* path, cgltf_data** out_data)` can be used to open the given
 * file using `FILE*` APIs and parse the data using `cgltf_parse()`.
 *
 * `cgltf_result cgltf_validate(cgltf_data*)` can be used to do additional
 * checks to make sure the parsed glTF data is valid.
 *
 * `cgltf_node_transform_local` converts the translation / rotation / scale properties of a node
 * into a mat4.
 *
 * `cgltf_node_transform_world` calls `cgltf_node_transform_local` on every ancestor in order
 * to compute the root-to-node transformation.
 *
 * `cgltf_accessor_unpack_floats` reads in the data from an accessor, applies sparse data (if any),
 * and converts them to floating point. Assumes that `cgltf_load_buffers` has already been called.
 * By passing null for the output pointer, users can find out how many floats are required in the
 * output buffer.
 *
 * `cgltf_accessor_unpack_indices` reads in the index data from an accessor. Assumes that
 * `cgltf_load_buffers` has already been called. By passing null for the output pointer, users can
 * find out how many indices are required in the output buffer. Returns 0 if the accessor is
 * sparse or if the output component size is less than the accessor's component size.
 *
 * `cgltf_num_components` is a tiny utility that tells you the dimensionality of
 * a certain accessor type. This can be used before `cgltf_accessor_unpack_floats` to help allocate
 * the necessary amount of memory. `cgltf_component_size` and `cgltf_calc_size` exist for
 * similar purposes.
 *
 * `cgltf_accessor_read_float` reads a certain element from a non-sparse accessor and converts it to
 * floating point, assuming that `cgltf_load_buffers` has already been called. The passed-in element
 * size is the number of floats in the output buffer, which should be in the range [1, 16]. Returns
 * false if the passed-in element_size is too small, or if the accessor is sparse.
 *
 * `cgltf_accessor_read_uint` is similar to its floating-point counterpart, but limited to reading
 * vector types and does not support matrix types. The passed-in element size is the number of uints
 * in the output buffer, which should be in the range [1, 4]. Returns false if the passed-in
 * element_size is too small, or if the accessor is sparse.
 *
 * `cgltf_accessor_read_index` is similar to its floating-point counterpart, but it returns size_t
 * and only works with single-component data types.
 *
 * `cgltf_copy_extras_json` allows users to retrieve the "extras" data that can be attached to many
 * glTF objects (which can be arbitrary JSON data). This is a legacy function, consider using
 * cgltf_extras::data directly instead. You can parse this data using your own JSON parser
 * or, if you've included the cgltf implementation using the integrated JSMN JSON parser.
 */
#ifndef CGLTF_H_INCLUDED__
#define CGLTF_H_INCLUDED__

#include <stddef.h>
#include <stdint.h> /* For uint8_t, uint32_t */

#ifdef __cplusplus
extern "C" {
#endif

  typedef size_t cgltf_size;
  typedef long long int cgltf_ssize;
  typedef float cgltf_float;
  typedef int cgltf_int;
  typedef unsigned int cgltf_uint;
  typedef int cgltf_bool;

  typedef enum cgltf_file_type
  {
    cgltf_file_type_invalid,
    cgltf_file_type_gltf,
    cgltf_file_type_glb,
    cgltf_file_type_max_enum
  } cgltf_file_type;

  typedef enum cgltf_result
  {
    cgltf_result_success,
    cgltf_result_data_too_short,
    cgltf_result_unknown_format,
    cgltf_result_invalid_json,
    cgltf_result_invalid_gltf,
    cgltf_result_invalid_options,
    cgltf_result_file_not_found,
    cgltf_result_io_error,
    cgltf_result_out_of_memory,
    cgltf_result_legacy_gltf,
    cgltf_result_max_enum
  } cgltf_result;

  typedef struct cgltf_memory_options
  {
    void* (*alloc_func)(void* user, cgltf_size size);
    void (*free_func) (void* user, void* ptr);
    void* user_data;
  } cgltf_memory_options;

  typedef struct cgltf_file_options
  {
    cgltf_result(*read)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data);
    void (*release)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data);
    void* user_data;
  } cgltf_file_options;

  typedef struct cgltf_options
  {
    cgltf_file_type type; /* invalid == auto detect */
    cgltf_size json_token_count; /* 0 == auto */
    cgltf_memory_options memory;
    cgltf_file_options file;
  } cgltf_options;

  typedef enum cgltf_buffer_view_type
  {
    cgltf_buffer_view_type_invalid,
    cgltf_buffer_view_type_indices,
    cgltf_buffer_view_type_vertices,
    cgltf_buffer_view_type_max_enum
  } cgltf_buffer_view_type;

  typedef enum cgltf_attribute_type
  {
    cgltf_attribute_type_invalid,
    cgltf_attribute_type_position,
    cgltf_attribute_type_normal,
    cgltf_attribute_type_tangent,
    cgltf_attribute_type_texcoord,
    cgltf_attribute_type_color,
    cgltf_attribute_type_joints,
    cgltf_attribute_type_weights,
    cgltf_attribute_type_custom,
    cgltf_attribute_type_max_enum
  } cgltf_attribute_type;

  typedef enum cgltf_component_type
  {
    cgltf_component_type_invalid,
    cgltf_component_type_r_8, /* BYTE */
    cgltf_component_type_r_8u, /* UNSIGNED_BYTE */
    cgltf_component_type_r_16, /* SHORT */
    cgltf_component_type_r_16u, /* UNSIGNED_SHORT */
    cgltf_component_type_r_32u, /* UNSIGNED_INT */
    cgltf_component_type_r_32f, /* FLOAT */
    cgltf_component_type_max_enum
  } cgltf_component_type;

  typedef enum cgltf_type
  {
    cgltf_type_invalid,
    cgltf_type_scalar,
    cgltf_type_vec2,
    cgltf_type_vec3,
    cgltf_type_vec4,
    cgltf_type_mat2,
    cgltf_type_mat3,
    cgltf_type_mat4,
    cgltf_type_max_enum
  } cgltf_type;

  typedef enum cgltf_primitive_type
  {
    cgltf_primitive_type_invalid,
    cgltf_primitive_type_points,
    cgltf_primitive_type_lines,
    cgltf_primitive_type_line_loop,
    cgltf_primitive_type_line_strip,
    cgltf_primitive_type_triangles,
    cgltf_primitive_type_triangle_strip,
    cgltf_primitive_type_triangle_fan,
    cgltf_primitive_type_max_enum
  } cgltf_primitive_type;

  typedef enum cgltf_alpha_mode
  {
    cgltf_alpha_mode_opaque,
    cgltf_alpha_mode_mask,
    cgltf_alpha_mode_blend,
    cgltf_alpha_mode_max_enum
  } cgltf_alpha_mode;

  typedef enum cgltf_animation_path_type {
    cgltf_animation_path_type_invalid,
    cgltf_animation_path_type_translation,
    cgltf_animation_path_type_rotation,
    cgltf_animation_path_type_scale,
    cgltf_animation_path_type_weights,
    cgltf_animation_path_type_max_enum
  } cgltf_animation_path_type;

  typedef enum cgltf_interpolation_type {
    cgltf_interpolation_type_linear,
    cgltf_interpolation_type_step,
    cgltf_interpolation_type_cubic_spline,
    cgltf_interpolation_type_max_enum
  } cgltf_interpolation_type;

  typedef enum cgltf_camera_type {
    cgltf_camera_type_invalid,
    cgltf_camera_type_perspective,
    cgltf_camera_type_orthographic,
    cgltf_camera_type_max_enum
  } cgltf_camera_type;

  typedef enum cgltf_light_type {
    cgltf_light_type_invalid,
    cgltf_light_type_directional,
    cgltf_light_type_point,
    cgltf_light_type_spot,
    cgltf_light_type_max_enum
  } cgltf_light_type;

  typedef enum cgltf_data_free_method {
    cgltf_data_free_method_none,
    cgltf_data_free_method_file_release,
    cgltf_data_free_method_memory_free,
    cgltf_data_free_method_max_enum
  } cgltf_data_free_method;

  typedef struct cgltf_extras {
    cgltf_size start_offset; /* this field is deprecated and will be removed in the future; use data instead */
    cgltf_size end_offset; /* this field is deprecated and will be removed in the future; use data instead */

    char* data;
  } cgltf_extras;

  typedef struct cgltf_extension {
    char* name;
    char* data;
  } cgltf_extension;

  typedef struct cgltf_buffer
  {
    char* name;
    cgltf_size size;
    char* uri;
    void* data; /* loaded by cgltf_load_buffers */
    cgltf_data_free_method data_free_method;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_buffer;

  typedef enum cgltf_meshopt_compression_mode {
    cgltf_meshopt_compression_mode_invalid,
    cgltf_meshopt_compression_mode_attributes,
    cgltf_meshopt_compression_mode_triangles,
    cgltf_meshopt_compression_mode_indices,
    cgltf_meshopt_compression_mode_max_enum
  } cgltf_meshopt_compression_mode;

  typedef enum cgltf_meshopt_compression_filter {
    cgltf_meshopt_compression_filter_none,
    cgltf_meshopt_compression_filter_octahedral,
    cgltf_meshopt_compression_filter_quaternion,
    cgltf_meshopt_compression_filter_exponential,
    cgltf_meshopt_compression_filter_max_enum
  } cgltf_meshopt_compression_filter;

  typedef struct cgltf_meshopt_compression
  {
    cgltf_buffer* buffer;
    cgltf_size offset;
    cgltf_size size;
    cgltf_size stride;
    cgltf_size count;
    cgltf_meshopt_compression_mode mode;
    cgltf_meshopt_compression_filter filter;
  } cgltf_meshopt_compression;

  typedef struct cgltf_buffer_view
  {
    char* name;
    cgltf_buffer* buffer;
    cgltf_size offset;
    cgltf_size size;
    cgltf_size stride; /* 0 == automatically determined by accessor */
    cgltf_buffer_view_type type;
    void* data; /* overrides buffer->data if present, filled by extensions */
    cgltf_bool has_meshopt_compression;
    cgltf_meshopt_compression meshopt_compression;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_buffer_view;

  typedef struct cgltf_accessor_sparse
  {
    cgltf_size count;
    cgltf_buffer_view* indices_buffer_view;
    cgltf_size indices_byte_offset;
    cgltf_component_type indices_component_type;
    cgltf_buffer_view* values_buffer_view;
    cgltf_size values_byte_offset;
  } cgltf_accessor_sparse;

  typedef struct cgltf_accessor
  {
    char* name;
    cgltf_component_type component_type;
    cgltf_bool normalized;
    cgltf_type type;
    cgltf_size offset;
    cgltf_size count;
    cgltf_size stride;
    cgltf_buffer_view* buffer_view;
    cgltf_bool has_min;
    cgltf_float min[16];
    cgltf_bool has_max;
    cgltf_float max[16];
    cgltf_bool is_sparse;
    cgltf_accessor_sparse sparse;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_accessor;

  typedef struct cgltf_attribute
  {
    char* name;
    cgltf_attribute_type type;
    cgltf_int index;
    cgltf_accessor* data;
  } cgltf_attribute;

  typedef struct cgltf_image
  {
    char* name;
    char* uri;
    cgltf_buffer_view* buffer_view;
    char* mime_type;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_image;

  typedef enum cgltf_filter_type {
    cgltf_filter_type_undefined = 0,
    cgltf_filter_type_nearest = 9728,
    cgltf_filter_type_linear = 9729,
    cgltf_filter_type_nearest_mipmap_nearest = 9984,
    cgltf_filter_type_linear_mipmap_nearest = 9985,
    cgltf_filter_type_nearest_mipmap_linear = 9986,
    cgltf_filter_type_linear_mipmap_linear = 9987
  } cgltf_filter_type;

  typedef enum cgltf_wrap_mode {
    cgltf_wrap_mode_clamp_to_edge = 33071,
    cgltf_wrap_mode_mirrored_repeat = 33648,
    cgltf_wrap_mode_repeat = 10497
  } cgltf_wrap_mode;

  typedef struct cgltf_sampler
  {
    char* name;
    cgltf_filter_type mag_filter;
    cgltf_filter_type min_filter;
    cgltf_wrap_mode wrap_s;
    cgltf_wrap_mode wrap_t;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_sampler;

  typedef struct cgltf_texture
  {
    char* name;
    cgltf_image* image;
    cgltf_sampler* sampler;
    cgltf_bool has_basisu;
    cgltf_image* basisu_image;
    cgltf_bool has_webp;
    cgltf_image* webp_image;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_texture;

  typedef struct cgltf_texture_transform
  {
    cgltf_float offset[2];
    cgltf_float rotation;
    cgltf_float scale[2];
    cgltf_bool has_texcoord;
    cgltf_int texcoord;
  } cgltf_texture_transform;

  typedef struct cgltf_texture_view
  {
    cgltf_texture* texture;
    cgltf_int texcoord;
    cgltf_float scale; /* equivalent to strength for occlusion_texture */
    cgltf_bool has_transform;
    cgltf_texture_transform transform;
  } cgltf_texture_view;

  typedef struct cgltf_pbr_metallic_roughness
  {
    cgltf_texture_view base_color_texture;
    cgltf_texture_view metallic_roughness_texture;

    cgltf_float base_color_factor[4];
    cgltf_float metallic_factor;
    cgltf_float roughness_factor;
  } cgltf_pbr_metallic_roughness;

  typedef struct cgltf_pbr_specular_glossiness
  {
    cgltf_texture_view diffuse_texture;
    cgltf_texture_view specular_glossiness_texture;

    cgltf_float diffuse_factor[4];
    cgltf_float specular_factor[3];
    cgltf_float glossiness_factor;
  } cgltf_pbr_specular_glossiness;

  typedef struct cgltf_clearcoat
  {
    cgltf_texture_view clearcoat_texture;
    cgltf_texture_view clearcoat_roughness_texture;
    cgltf_texture_view clearcoat_normal_texture;

    cgltf_float clearcoat_factor;
    cgltf_float clearcoat_roughness_factor;
  } cgltf_clearcoat;

  typedef struct cgltf_transmission
  {
    cgltf_texture_view transmission_texture;
    cgltf_float transmission_factor;
  } cgltf_transmission;

  typedef struct cgltf_ior
  {
    cgltf_float ior;
  } cgltf_ior;

  typedef struct cgltf_specular
  {
    cgltf_texture_view specular_texture;
    cgltf_texture_view specular_color_texture;
    cgltf_float specular_color_factor[3];
    cgltf_float specular_factor;
  } cgltf_specular;

  typedef struct cgltf_volume
  {
    cgltf_texture_view thickness_texture;
    cgltf_float thickness_factor;
    cgltf_float attenuation_color[3];
    cgltf_float attenuation_distance;
  } cgltf_volume;

  typedef struct cgltf_sheen
  {
    cgltf_texture_view sheen_color_texture;
    cgltf_float sheen_color_factor[3];
    cgltf_texture_view sheen_roughness_texture;
    cgltf_float sheen_roughness_factor;
  } cgltf_sheen;

  typedef struct cgltf_emissive_strength
  {
    cgltf_float emissive_strength;
  } cgltf_emissive_strength;

  typedef struct cgltf_iridescence
  {
    cgltf_float iridescence_factor;
    cgltf_texture_view iridescence_texture;
    cgltf_float iridescence_ior;
    cgltf_float iridescence_thickness_min;
    cgltf_float iridescence_thickness_max;
    cgltf_texture_view iridescence_thickness_texture;
  } cgltf_iridescence;

  typedef struct cgltf_diffuse_transmission
  {
    cgltf_texture_view diffuse_transmission_texture;
    cgltf_float diffuse_transmission_factor;
    cgltf_float diffuse_transmission_color_factor[3];
    cgltf_texture_view diffuse_transmission_color_texture;
  } cgltf_diffuse_transmission;

  typedef struct cgltf_anisotropy
  {
    cgltf_float anisotropy_strength;
    cgltf_float anisotropy_rotation;
    cgltf_texture_view anisotropy_texture;
  } cgltf_anisotropy;

  typedef struct cgltf_dispersion
  {
    cgltf_float dispersion;
  } cgltf_dispersion;

  typedef struct cgltf_material
  {
    char* name;
    cgltf_bool has_pbr_metallic_roughness;
    cgltf_bool has_pbr_specular_glossiness;
    cgltf_bool has_clearcoat;
    cgltf_bool has_transmission;
    cgltf_bool has_volume;
    cgltf_bool has_ior;
    cgltf_bool has_specular;
    cgltf_bool has_sheen;
    cgltf_bool has_emissive_strength;
    cgltf_bool has_iridescence;
    cgltf_bool has_diffuse_transmission;
    cgltf_bool has_anisotropy;
    cgltf_bool has_dispersion;
    cgltf_pbr_metallic_roughness pbr_metallic_roughness;
    cgltf_pbr_specular_glossiness pbr_specular_glossiness;
    cgltf_clearcoat clearcoat;
    cgltf_ior ior;
    cgltf_specular specular;
    cgltf_sheen sheen;
    cgltf_transmission transmission;
    cgltf_volume volume;
    cgltf_emissive_strength emissive_strength;
    cgltf_iridescence iridescence;
    cgltf_diffuse_transmission diffuse_transmission;
    cgltf_anisotropy anisotropy;
    cgltf_dispersion dispersion;
    cgltf_texture_view normal_texture;
    cgltf_texture_view occlusion_texture;
    cgltf_texture_view emissive_texture;
    cgltf_float emissive_factor[3];
    cgltf_alpha_mode alpha_mode;
    cgltf_float alpha_cutoff;
    cgltf_bool double_sided;
    cgltf_bool unlit;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_material;

  typedef struct cgltf_material_mapping
  {
    cgltf_size variant;
    cgltf_material* material;
    cgltf_extras extras;
  } cgltf_material_mapping;

  typedef struct cgltf_morph_target {
    cgltf_attribute* attributes;
    cgltf_size attributes_count;
  } cgltf_morph_target;

  typedef struct cgltf_draco_mesh_compression {
    cgltf_buffer_view* buffer_view;
    cgltf_attribute* attributes;
    cgltf_size attributes_count;
  } cgltf_draco_mesh_compression;

  typedef struct cgltf_mesh_gpu_instancing {
    cgltf_attribute* attributes;
    cgltf_size attributes_count;
  } cgltf_mesh_gpu_instancing;

  typedef struct cgltf_primitive {
    cgltf_primitive_type type;
    cgltf_accessor* indices;
    cgltf_material* material;
    cgltf_attribute* attributes;
    cgltf_size attributes_count;
    cgltf_morph_target* targets;
    cgltf_size targets_count;
    cgltf_extras extras;
    cgltf_bool has_draco_mesh_compression;
    cgltf_draco_mesh_compression draco_mesh_compression;
    cgltf_material_mapping* mappings;
    cgltf_size mappings_count;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_primitive;

  typedef struct cgltf_mesh {
    char* name;
    cgltf_primitive* primitives;
    cgltf_size primitives_count;
    cgltf_float* weights;
    cgltf_size weights_count;
    char** target_names;
    cgltf_size target_names_count;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_mesh;

  typedef struct cgltf_node cgltf_node;

  typedef struct cgltf_skin {
    char* name;
    cgltf_node** joints;
    cgltf_size joints_count;
    cgltf_node* skeleton;
    cgltf_accessor* inverse_bind_matrices;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_skin;

  typedef struct cgltf_camera_perspective {
    cgltf_bool has_aspect_ratio;
    cgltf_float aspect_ratio;
    cgltf_float yfov;
    cgltf_bool has_zfar;
    cgltf_float zfar;
    cgltf_float znear;
    cgltf_extras extras;
  } cgltf_camera_perspective;

  typedef struct cgltf_camera_orthographic {
    cgltf_float xmag;
    cgltf_float ymag;
    cgltf_float zfar;
    cgltf_float znear;
    cgltf_extras extras;
  } cgltf_camera_orthographic;

  typedef struct cgltf_camera {
    char* name;
    cgltf_camera_type type;
    union {
      cgltf_camera_perspective perspective;
      cgltf_camera_orthographic orthographic;
    } data;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_camera;

  typedef struct cgltf_light {
    char* name;
    cgltf_float color[3];
    cgltf_float intensity;
    cgltf_light_type type;
    cgltf_float range;
    cgltf_float spot_inner_cone_angle;
    cgltf_float spot_outer_cone_angle;
    cgltf_extras extras;
  } cgltf_light;

  struct cgltf_node {
    char* name;
    cgltf_node* parent;
    cgltf_node** children;
    cgltf_size children_count;
    cgltf_skin* skin;
    cgltf_mesh* mesh;
    cgltf_camera* camera;
    cgltf_light* light;
    cgltf_float* weights;
    cgltf_size weights_count;
    cgltf_bool has_translation;
    cgltf_bool has_rotation;
    cgltf_bool has_scale;
    cgltf_bool has_matrix;
    cgltf_float translation[3];
    cgltf_float rotation[4];
    cgltf_float scale[3];
    cgltf_float matrix[16];
    cgltf_extras extras;
    cgltf_bool has_mesh_gpu_instancing;
    cgltf_mesh_gpu_instancing mesh_gpu_instancing;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  };

  typedef struct cgltf_scene {
    char* name;
    cgltf_node** nodes;
    cgltf_size nodes_count;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_scene;

  typedef struct cgltf_animation_sampler {
    cgltf_accessor* input;
    cgltf_accessor* output;
    cgltf_interpolation_type interpolation;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_animation_sampler;

  typedef struct cgltf_animation_channel {
    cgltf_animation_sampler* sampler;
    cgltf_node* target_node;
    cgltf_animation_path_type target_path;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_animation_channel;

  typedef struct cgltf_animation {
    char* name;
    cgltf_animation_sampler* samplers;
    cgltf_size samplers_count;
    cgltf_animation_channel* channels;
    cgltf_size channels_count;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_animation;

  typedef struct cgltf_material_variant
  {
    char* name;
    cgltf_extras extras;
  } cgltf_material_variant;

  typedef struct cgltf_asset {
    char* copyright;
    char* generator;
    char* version;
    char* min_version;
    cgltf_extras extras;
    cgltf_size extensions_count;
    cgltf_extension* extensions;
  } cgltf_asset;

  typedef struct cgltf_data
  {
    cgltf_file_type file_type;
    void* file_data;

    cgltf_asset asset;

    cgltf_mesh* meshes;
    cgltf_size meshes_count;

    cgltf_material* materials;
    cgltf_size materials_count;

    cgltf_accessor* accessors;
    cgltf_size accessors_count;

    cgltf_buffer_view* buffer_views;
    cgltf_size buffer_views_count;

    cgltf_buffer* buffers;
    cgltf_size buffers_count;

    cgltf_image* images;
    cgltf_size images_count;

    cgltf_texture* textures;
    cgltf_size textures_count;

    cgltf_sampler* samplers;
    cgltf_size samplers_count;

    cgltf_skin* skins;
    cgltf_size skins_count;

    cgltf_camera* cameras;
    cgltf_size cameras_count;

    cgltf_light* lights;
    cgltf_size lights_count;

    cgltf_node* nodes;
    cgltf_size nodes_count;

    cgltf_scene* scenes;
    cgltf_size scenes_count;

    cgltf_scene* scene;

    cgltf_animation* animations;
    cgltf_size animations_count;

    cgltf_material_variant* variants;
    cgltf_size variants_count;

    cgltf_extras extras;

    cgltf_size data_extensions_count;
    cgltf_extension* data_extensions;

    char** extensions_used;
    cgltf_size extensions_used_count;

    char** extensions_required;
    cgltf_size extensions_required_count;

    const char* json;
    cgltf_size json_size;

    const void* bin;
    cgltf_size bin_size;

    cgltf_memory_options memory;
    cgltf_file_options file;
  } cgltf_data;

  cgltf_result cgltf_parse(
    const cgltf_options* options,
    const void* data,
    cgltf_size size,
    cgltf_data** out_data);

  cgltf_result cgltf_parse_file(
    const cgltf_options* options,
    const char* path,
    cgltf_data** out_data);

  cgltf_result cgltf_load_buffers(
    const cgltf_options* options,
    cgltf_data* data,
    const char* gltf_path);

  cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data);

  cgltf_size cgltf_decode_string(char* string);
  cgltf_size cgltf_decode_uri(char* uri);

  cgltf_result cgltf_validate(cgltf_data* data);

  void cgltf_free(cgltf_data* data);

  void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix);
  void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix);

  const uint8_t* cgltf_buffer_view_data(const cgltf_buffer_view* view);

  const cgltf_accessor* cgltf_find_accessor(const cgltf_primitive* prim, cgltf_attribute_type type, cgltf_int index);

  cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size);
  cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size);
  cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index);

  cgltf_size cgltf_num_components(cgltf_type type);
  cgltf_size cgltf_component_size(cgltf_component_type component_type);
  cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type);

  cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count);
  cgltf_size cgltf_accessor_unpack_indices(const cgltf_accessor* accessor, void* out, cgltf_size out_component_size, cgltf_size index_count);

  /* this function is deprecated and will be removed in the future; use cgltf_extras::data instead */
  cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size);

  cgltf_size cgltf_mesh_index(const cgltf_data* data, const cgltf_mesh* object);
  cgltf_size cgltf_material_index(const cgltf_data* data, const cgltf_material* object);
  cgltf_size cgltf_accessor_index(const cgltf_data* data, const cgltf_accessor* object);
  cgltf_size cgltf_buffer_view_index(const cgltf_data* data, const cgltf_buffer_view* object);
  cgltf_size cgltf_buffer_index(const cgltf_data* data, const cgltf_buffer* object);
  cgltf_size cgltf_image_index(const cgltf_data* data, const cgltf_image* object);
  cgltf_size cgltf_texture_index(const cgltf_data* data, const cgltf_texture* object);
  cgltf_size cgltf_sampler_index(const cgltf_data* data, const cgltf_sampler* object);
  cgltf_size cgltf_skin_index(const cgltf_data* data, const cgltf_skin* object);
  cgltf_size cgltf_camera_index(const cgltf_data* data, const cgltf_camera* object);
  cgltf_size cgltf_light_index(const cgltf_data* data, const cgltf_light* object);
  cgltf_size cgltf_node_index(const cgltf_data* data, const cgltf_node* object);
  cgltf_size cgltf_scene_index(const cgltf_data* data, const cgltf_scene* object);
  cgltf_size cgltf_animation_index(const cgltf_data* data, const cgltf_animation* object);
  cgltf_size cgltf_animation_sampler_index(const cgltf_animation* animation, const cgltf_animation_sampler* object);
  cgltf_size cgltf_animation_channel_index(const cgltf_animation* animation, const cgltf_animation_channel* object);

#ifdef __cplusplus
}
#endif

#endif /* #ifndef CGLTF_H_INCLUDED__ */

/*
 *
 * Stop now, if you are only interested in the API.
 * Below, you find the implementation.
 *
 */

 //#if defined(__INTELLISENSE__) || defined(__JETBRAINS_IDE__)
  /* This makes MSVC/CLion intellisense work. */
#define CGLTF_IMPLEMENTATION
//#endif

#ifdef CGLTF_IMPLEMENTATION

#include <assert.h> /* For assert */
#include <string.h> /* For strncpy */
#include <stdio.h>  /* For fopen */
#include <limits.h> /* For UINT_MAX etc */
#include <float.h>  /* For FLT_MAX */

#if !defined(CGLTF_MALLOC) || !defined(CGLTF_FREE) || !defined(CGLTF_ATOI) || !defined(CGLTF_ATOF) || !defined(CGLTF_ATOLL)
#include <stdlib.h> /* For malloc, free, atoi, atof */
#endif

/* JSMN_PARENT_LINKS is necessary to make parsing large structures linear in input size */
#define JSMN_PARENT_LINKS

/* JSMN_STRICT is necessary to reject invalid JSON documents */
#define JSMN_STRICT

/*
 * -- jsmn.h start --
 * Source: https://github.com/zserge/jsmn
 * License: MIT
 */
typedef enum {
  JSMN_UNDEFINED = 0,
  JSMN_OBJECT = 1,
  JSMN_ARRAY = 2,
  JSMN_STRING = 3,
  JSMN_PRIMITIVE = 4
} jsmntype_t;
enum jsmnerr {
  /* Not enough tokens were provided */
  JSMN_ERROR_NOMEM = -1,
  /* Invalid character inside JSON string */
  JSMN_ERROR_INVAL = -2,
  /* The string is not a full JSON packet, more bytes expected */
  JSMN_ERROR_PART = -3
};
typedef struct {
  jsmntype_t type;
  ptrdiff_t start;
  ptrdiff_t end;
  int size;
#ifdef JSMN_PARENT_LINKS
  int parent;
#endif
} jsmntok_t;
typedef struct {
  size_t pos; /* offset in the JSON string */
  unsigned int toknext; /* next token to allocate */
  int toksuper; /* superior token node, e.g parent object or array */
} jsmn_parser;
static void jsmn_init(jsmn_parser* parser);
static int jsmn_parse(jsmn_parser* parser, const char* js, size_t len, jsmntok_t* tokens, size_t num_tokens);
/*
 * -- jsmn.h end --
 */


#ifndef CGLTF_CONSTS
#define GlbHeaderSize 12
#define GlbChunkHeaderSize 8
static const uint32_t GlbVersion = 2;
static const uint32_t GlbMagic = 0x46546C67;
static const uint32_t GlbMagicJsonChunk = 0x4E4F534A;
static const uint32_t GlbMagicBinChunk = 0x004E4942;
#define CGLTF_CONSTS
#endif

#ifndef CGLTF_MALLOC
#define CGLTF_MALLOC(size) malloc(size)
#endif
#ifndef CGLTF_FREE
#define CGLTF_FREE(ptr) free(ptr)
#endif
#ifndef CGLTF_ATOI
#define CGLTF_ATOI(str) atoi(str)
#endif
#ifndef CGLTF_ATOF
#define CGLTF_ATOF(str) atof(str)
#endif
#ifndef CGLTF_ATOLL
#define CGLTF_ATOLL(str) atoll(str)
#endif
#ifndef CGLTF_VALIDATE_ENABLE_ASSERTS
#define CGLTF_VALIDATE_ENABLE_ASSERTS 0
#endif

static void* cgltf_default_alloc(void* user, cgltf_size size)
{
  (void)user;
  return CGLTF_MALLOC(size);
}

static void cgltf_default_free(void* user, void* ptr)
{
  (void)user;
  CGLTF_FREE(ptr);
}

static void* cgltf_calloc(cgltf_options* options, size_t element_size, cgltf_size count)
{
  if (SIZE_MAX / element_size < count)
  {
    return NULL;
  }
  void* result = options->memory.alloc_func(options->memory.user_data, element_size * count);
  if (!result)
  {
    return NULL;
  }
  memset(result, 0, element_size * count);
  return result;
}

static cgltf_result cgltf_default_file_read(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data)
{
  (void)file_options;
  void* (*memory_alloc)(void*, cgltf_size) = memory_options->alloc_func ? memory_options->alloc_func : &cgltf_default_alloc;
  void (*memory_free)(void*, void*) = memory_options->free_func ? memory_options->free_func : &cgltf_default_free;

  FILE* file = fopen(path, "rb");
  if (!file)
  {
    return cgltf_result_file_not_found;
  }

  cgltf_size file_size = size ? *size : 0;

  if (file_size == 0)
  {
    fseek(file, 0, SEEK_END);

#ifdef _MSC_VER
    __int64 length = _ftelli64(file);
#else
    long length = ftell(file);
#endif

    if (length < 0)
    {
      fclose(file);
      return cgltf_result_io_error;
    }

    fseek(file, 0, SEEK_SET);
    file_size = (cgltf_size)length;
  }

  char* file_data = (char*)memory_alloc(memory_options->user_data, file_size);
  if (!file_data)
  {
    fclose(file);
    return cgltf_result_out_of_memory;
  }

  cgltf_size read_size = fread(file_data, 1, file_size, file);

  fclose(file);

  if (read_size != file_size)
  {
    memory_free(memory_options->user_data, file_data);
    return cgltf_result_io_error;
  }

  if (size)
  {
    *size = file_size;
  }
  if (data)
  {
    *data = file_data;
  }

  return cgltf_result_success;
}

static void cgltf_default_file_release(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data)
{
  (void)file_options;
  void (*memfree)(void*, void*) = memory_options->free_func ? memory_options->free_func : &cgltf_default_free;
  memfree(memory_options->user_data, data);
}

static cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data);

cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_size size, cgltf_data** out_data)
{
  if (size < GlbHeaderSize)
  {
    return cgltf_result_data_too_short;
  }

  if (options == NULL)
  {
    return cgltf_result_invalid_options;
  }

  cgltf_options fixed_options = *options;
  if (fixed_options.memory.alloc_func == NULL)
  {
    fixed_options.memory.alloc_func = &cgltf_default_alloc;
  }
  if (fixed_options.memory.free_func == NULL)
  {
    fixed_options.memory.free_func = &cgltf_default_free;
  }

  uint32_t tmp;
  // Magic
  memcpy(&tmp, data, 4);
  if (tmp != GlbMagic)
  {
    if (fixed_options.type == cgltf_file_type_invalid)
    {
      fixed_options.type = cgltf_file_type_gltf;
    }
    else if (fixed_options.type == cgltf_file_type_glb)
    {
      return cgltf_result_unknown_format;
    }
  }

  if (fixed_options.type == cgltf_file_type_gltf)
  {
    cgltf_result json_result = cgltf_parse_json(&fixed_options, (const uint8_t*)data, size, out_data);
    if (json_result != cgltf_result_success)
    {
      return json_result;
    }

    (*out_data)->file_type = cgltf_file_type_gltf;

    return cgltf_result_success;
  }

  const uint8_t* ptr = (const uint8_t*)data;
  // Version
  memcpy(&tmp, ptr + 4, 4);
  uint32_t version = tmp;
  if (version != GlbVersion)
  {
    return version < GlbVersion ? cgltf_result_legacy_gltf : cgltf_result_unknown_format;
  }

  // Total length
  memcpy(&tmp, ptr + 8, 4);
  if (tmp > size)
  {
    return cgltf_result_data_too_short;
  }

  const uint8_t* json_chunk = ptr + GlbHeaderSize;

  if (GlbHeaderSize + GlbChunkHeaderSize > size)
  {
    return cgltf_result_data_too_short;
  }

  // JSON chunk: length
  uint32_t json_length;
  memcpy(&json_length, json_chunk, 4);
  if (json_length > size - GlbHeaderSize - GlbChunkHeaderSize)
  {
    return cgltf_result_data_too_short;
  }

  // JSON chunk: magic
  memcpy(&tmp, json_chunk + 4, 4);
  if (tmp != GlbMagicJsonChunk)
  {
    return cgltf_result_unknown_format;
  }

  json_chunk += GlbChunkHeaderSize;

  const void* bin = NULL;
  cgltf_size bin_size = 0;

  if (GlbChunkHeaderSize <= size - GlbHeaderSize - GlbChunkHeaderSize - json_length)
  {
    // We can read another chunk
    const uint8_t* bin_chunk = json_chunk + json_length;

    // Bin chunk: length
    uint32_t bin_length;
    memcpy(&bin_length, bin_chunk, 4);
    if (bin_length > size - GlbHeaderSize - GlbChunkHeaderSize - json_length - GlbChunkHeaderSize)
    {
      return cgltf_result_data_too_short;
    }

    // Bin chunk: magic
    memcpy(&tmp, bin_chunk + 4, 4);
    if (tmp != GlbMagicBinChunk)
    {
      return cgltf_result_unknown_format;
    }

    bin_chunk += GlbChunkHeaderSize;

    bin = bin_chunk;
    bin_size = bin_length;
  }

  cgltf_result json_result = cgltf_parse_json(&fixed_options, json_chunk, json_length, out_data);
  if (json_result != cgltf_result_success)
  {
    return json_result;
  }

  (*out_data)->file_type = cgltf_file_type_glb;
  (*out_data)->bin = bin;
  (*out_data)->bin_size = bin_size;

  return cgltf_result_success;
}

cgltf_result cgltf_parse_file(const cgltf_options* options, const char* path, cgltf_data** out_data)
{
  if (options == NULL)
  {
    return cgltf_result_invalid_options;
  }

  cgltf_result(*file_read)(const struct cgltf_memory_options*, const struct cgltf_file_options*, const char*, cgltf_size*, void**) = options->file.read ? options->file.read : &cgltf_default_file_read;
  void (*file_release)(const struct cgltf_memory_options*, const struct cgltf_file_options*, void* data) = options->file.release ? options->file.release : cgltf_default_file_release;

  void* file_data = NULL;
  cgltf_size file_size = 0;
  cgltf_result result = file_read(&options->memory, &options->file, path, &file_size, &file_data);
  if (result != cgltf_result_success)
  {
    return result;
  }

  result = cgltf_parse(options, file_data, file_size, out_data);

  if (result != cgltf_result_success)
  {
    file_release(&options->memory, &options->file, file_data);
    return result;
  }

  (*out_data)->file_data = file_data;

  return cgltf_result_success;
}

static void cgltf_combine_paths(char* path, const char* base, const char* uri)
{
  const char* s0 = strrchr(base, '/');
  const char* s1 = strrchr(base, '\\');
  const char* slash = s0 ? (s1 && s1 > s0 ? s1 : s0) : s1;

  if (slash)
  {
    size_t prefix = slash - base + 1;

    strncpy(path, base, prefix);
    strcpy(path + prefix, uri);
  }
  else
  {
    strcpy(path, uri);
  }
}

static cgltf_result cgltf_load_buffer_file(const cgltf_options* options, cgltf_size size, const char* uri, const char* gltf_path, void** out_data)
{
  void* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc_func ? options->memory.alloc_func : &cgltf_default_alloc;
  void (*memory_free)(void*, void*) = options->memory.free_func ? options->memory.free_func : &cgltf_default_free;
  cgltf_result(*file_read)(const struct cgltf_memory_options*, const struct cgltf_file_options*, const char*, cgltf_size*, void**) = options->file.read ? options->file.read : &cgltf_default_file_read;

  char* path = (char*)memory_alloc(options->memory.user_data, strlen(uri) + strlen(gltf_path) + 1);
  if (!path)
  {
    return cgltf_result_out_of_memory;
  }

  cgltf_combine_paths(path, gltf_path, uri);

  // after combining, the tail of the resulting path is a uri; decode_uri converts it into path
  cgltf_decode_uri(path + strlen(path) - strlen(uri));

  void* file_data = NULL;
  cgltf_result result = file_read(&options->memory, &options->file, path, &size, &file_data);

  memory_free(options->memory.user_data, path);

  *out_data = (result == cgltf_result_success) ? file_data : NULL;

  return result;
}

cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data)
{
  void* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc_func ? options->memory.alloc_func : &cgltf_default_alloc;
  void (*memory_free)(void*, void*) = options->memory.free_func ? options->memory.free_func : &cgltf_default_free;

  unsigned char* data = (unsigned char*)memory_alloc(options->memory.user_data, size);
  if (!data)
  {
    return cgltf_result_out_of_memory;
  }

  unsigned int buffer = 0;
  unsigned int buffer_bits = 0;

  for (cgltf_size i = 0; i < size; ++i)
  {
    while (buffer_bits < 8)
    {
      char ch = *base64++;

      int index =
        (unsigned)(ch - 'A') < 26 ? (ch - 'A') :
        (unsigned)(ch - 'a') < 26 ? (ch - 'a') + 26 :
        (unsigned)(ch - '0') < 10 ? (ch - '0') + 52 :
        ch == '+' ? 62 :
        ch == '/' ? 63 :
        -1;

      if (index < 0)
      {
        memory_free(options->memory.user_data, data);
        return cgltf_result_io_error;
      }

      buffer = (buffer << 6) | index;
      buffer_bits += 6;
    }

    data[i] = (unsigned char)(buffer >> (buffer_bits - 8));
    buffer_bits -= 8;
  }

  *out_data = data;

  return cgltf_result_success;
}

static int cgltf_unhex(char ch)
{
  return
    (unsigned)(ch - '0') < 10 ? (ch - '0') :
    (unsigned)(ch - 'A') < 6 ? (ch - 'A') + 10 :
    (unsigned)(ch - 'a') < 6 ? (ch - 'a') + 10 :
    -1;
}

cgltf_size cgltf_decode_string(char* string)
{
  char* read = string + strcspn(string, "\\");
  if (*read == 0)
  {
    return read - string;
  }
  char* write = string;
  char* last = string;

  for (;;)
  {
    // Copy characters since last escaped sequence
    cgltf_size written = read - last;
    memmove(write, last, written);
    write += written;

    if (*read++ == 0)
    {
      break;
    }

    // jsmn already checked that all escape sequences are valid
    switch (*read++)
    {
    case '\"': *write++ = '\"'; break;
    case '/':  *write++ = '/';  break;
    case '\\': *write++ = '\\'; break;
    case 'b':  *write++ = '\b'; break;
    case 'f':  *write++ = '\f'; break;
    case 'r':  *write++ = '\r'; break;
    case 'n':  *write++ = '\n'; break;
    case 't':  *write++ = '\t'; break;
    case 'u':
    {
      // UCS-2 codepoint \uXXXX to UTF-8
      int character = 0;
      for (cgltf_size i = 0; i < 4; ++i)
      {
        character = (character << 4) + cgltf_unhex(*read++);
      }

      if (character <= 0x7F)
      {
        *write++ = character & 0xFF;
      }
      else if (character <= 0x7FF)
      {
        *write++ = 0xC0 | ((character >> 6) & 0xFF);
        *write++ = 0x80 | (character & 0x3F);
      }
      else
      {
        *write++ = 0xE0 | ((character >> 12) & 0xFF);
        *write++ = 0x80 | ((character >> 6) & 0x3F);
        *write++ = 0x80 | (character & 0x3F);
      }
      break;
    }
    default:
      break;
    }

    last = read;
    read += strcspn(read, "\\");
  }

  *write = 0;
  return write - string;
}

cgltf_size cgltf_decode_uri(char* uri)
{
  char* write = uri;
  char* i = uri;

  while (*i)
  {
    if (*i == '%')
    {
      int ch1 = cgltf_unhex(i[1]);

      if (ch1 >= 0)
      {
        int ch2 = cgltf_unhex(i[2]);

        if (ch2 >= 0)
        {
          *write++ = (char)(ch1 * 16 + ch2);
          i += 3;
          continue;
        }
      }
    }

    *write++ = *i++;
  }

  *write = 0;
  return write - uri;
}

cgltf_result cgltf_load_buffers(const cgltf_options* options, cgltf_data* data, const char* gltf_path)
{
  if (options == NULL)
  {
    return cgltf_result_invalid_options;
  }

  if (data->buffers_count && data->buffers[0].data == NULL && data->buffers[0].uri == NULL && data->bin)
  {
    if (data->bin_size < data->buffers[0].size)
    {
      return cgltf_result_data_too_short;
    }

    data->buffers[0].data = (void*)data->bin;
    data->buffers[0].data_free_method = cgltf_data_free_method_none;
  }

  for (cgltf_size i = 0; i < data->buffers_count; ++i)
  {
    if (data->buffers[i].data)
    {
      continue;
    }

    const char* uri = data->buffers[i].uri;

    if (uri == NULL)
    {
      continue;
    }

    if (strncmp(uri, "data:", 5) == 0)
    {
      const char* comma = strchr(uri, ',');

      if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0)
      {
        cgltf_result res = cgltf_load_buffer_base64(options, data->buffers[i].size, comma + 1, &data->buffers[i].data);
        data->buffers[i].data_free_method = cgltf_data_free_method_memory_free;

        if (res != cgltf_result_success)
        {
          return res;
        }
      }
      else
      {
        return cgltf_result_unknown_format;
      }
    }
    else if (strstr(uri, "://") == NULL && gltf_path)
    {
      cgltf_result res = cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data);
      data->buffers[i].data_free_method = cgltf_data_free_method_file_release;

      if (res != cgltf_result_success)
      {
        return res;
      }
    }
    else
    {
      return cgltf_result_unknown_format;
    }
  }

  return cgltf_result_success;
}

static cgltf_size cgltf_calc_index_bound(cgltf_buffer_view* buffer_view, cgltf_size offset, cgltf_component_type component_type, cgltf_size count)
{
  char* data = (char*)buffer_view->buffer->data + offset + buffer_view->offset;
  cgltf_size bound = 0;

  switch (component_type)
  {
  case cgltf_component_type_r_8u:
    for (size_t i = 0; i < count; ++i)
    {
      cgltf_size v = ((unsigned char*)data)[i];
      bound = bound > v ? bound : v;
    }
    break;

  case cgltf_component_type_r_16u:
    for (size_t i = 0; i < count; ++i)
    {
      cgltf_size v = ((unsigned short*)data)[i];
      bound = bound > v ? bound : v;
    }
    break;

  case cgltf_component_type_r_32u:
    for (size_t i = 0; i < count; ++i)
    {
      cgltf_size v = ((unsigned int*)data)[i];
      bound = bound > v ? bound : v;
    }
    break;

  default:
    ;
  }

  return bound;
}

#if CGLTF_VALIDATE_ENABLE_ASSERTS
#define CGLTF_ASSERT_IF(cond, result) assert(!(cond)); if (cond) return result;
#else
#define CGLTF_ASSERT_IF(cond, result) if (cond) return result;
#endif

cgltf_result cgltf_validate(cgltf_data* data)
{
  for (cgltf_size i = 0; i < data->accessors_count; ++i)
  {
    cgltf_accessor* accessor = &data->accessors[i];

    CGLTF_ASSERT_IF(data->accessors[i].component_type == cgltf_component_type_invalid, cgltf_result_invalid_gltf);
    CGLTF_ASSERT_IF(data->accessors[i].type == cgltf_type_invalid, cgltf_result_invalid_gltf);

    cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type);

    if (accessor->buffer_view)
    {
      cgltf_size req_size = accessor->offset + accessor->stride * (accessor->count - 1) + element_size;

      CGLTF_ASSERT_IF(accessor->buffer_view->size < req_size, cgltf_result_data_too_short);
    }

    if (accessor->is_sparse)
    {
      cgltf_accessor_sparse* sparse = &accessor->sparse;

      cgltf_size indices_component_size = cgltf_component_size(sparse->indices_component_type);
      cgltf_size indices_req_size = sparse->indices_byte_offset + indices_component_size * sparse->count;
      cgltf_size values_req_size = sparse->values_byte_offset + element_size * sparse->count;

      CGLTF_ASSERT_IF(sparse->indices_buffer_view->size < indices_req_size ||
        sparse->values_buffer_view->size < values_req_size, cgltf_result_data_too_short);

      CGLTF_ASSERT_IF(sparse->indices_component_type != cgltf_component_type_r_8u &&
        sparse->indices_component_type != cgltf_component_type_r_16u &&
        sparse->indices_component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf);

      if (sparse->indices_buffer_view->buffer->data)
      {
        cgltf_size index_bound = cgltf_calc_index_bound(sparse->indices_buffer_view, sparse->indices_byte_offset, sparse->indices_component_type, sparse->count);

        CGLTF_ASSERT_IF(index_bound >= accessor->count, cgltf_result_data_too_short);
      }
    }
  }

  for (cgltf_size i = 0; i < data->buffer_views_count; ++i)
  {
    cgltf_size req_size = data->buffer_views[i].offset + data->buffer_views[i].size;

    CGLTF_ASSERT_IF(data->buffer_views[i].buffer && data->buffer_views[i].buffer->size < req_size, cgltf_result_data_too_short);

    if (data->buffer_views[i].has_meshopt_compression)
    {
      cgltf_meshopt_compression* mc = &data->buffer_views[i].meshopt_compression;

      CGLTF_ASSERT_IF(mc->buffer == NULL || mc->buffer->size < mc->offset + mc->size, cgltf_result_data_too_short);

      CGLTF_ASSERT_IF(data->buffer_views[i].stride && mc->stride != data->buffer_views[i].stride, cgltf_result_invalid_gltf);

      CGLTF_ASSERT_IF(data->buffer_views[i].size != mc->stride * mc->count, cgltf_result_invalid_gltf);

      CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_invalid, cgltf_result_invalid_gltf);

      CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_attributes && !(mc->stride % 4 == 0 && mc->stride <= 256), cgltf_result_invalid_gltf);

      CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_triangles && mc->count % 3 != 0, cgltf_result_invalid_gltf);

      CGLTF_ASSERT_IF((mc->mode == cgltf_meshopt_compression_mode_triangles || mc->mode == cgltf_meshopt_compression_mode_indices) && mc->stride != 2 && mc->stride != 4, cgltf_result_invalid_gltf);

      CGLTF_ASSERT_IF((mc->mode == cgltf_meshopt_compression_mode_triangles || mc->mode == cgltf_meshopt_compression_mode_indices) && mc->filter != cgltf_meshopt_compression_filter_none, cgltf_result_invalid_gltf);

      CGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_octahedral && mc->stride != 4 && mc->stride != 8, cgltf_result_invalid_gltf);

      CGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_quaternion && mc->stride != 8, cgltf_result_invalid_gltf);
    }
  }

  for (cgltf_size i = 0; i < data->meshes_count; ++i)
  {
    if (data->meshes[i].weights)
    {
      CGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].weights_count, cgltf_result_invalid_gltf);
    }

    if (data->meshes[i].target_names)
    {
      CGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].target_names_count, cgltf_result_invalid_gltf);
    }

    for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j)
    {
      CGLTF_ASSERT_IF(data->meshes[i].primitives[j].type == cgltf_primitive_type_invalid, cgltf_result_invalid_gltf);
      CGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets_count != data->meshes[i].primitives[0].targets_count, cgltf_result_invalid_gltf);

      CGLTF_ASSERT_IF(data->meshes[i].primitives[j].attributes_count == 0, cgltf_result_invalid_gltf);

      cgltf_accessor* first = data->meshes[i].primitives[j].attributes[0].data;

      CGLTF_ASSERT_IF(first->count == 0, cgltf_result_invalid_gltf);

      for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k)
      {
        CGLTF_ASSERT_IF(data->meshes[i].primitives[j].attributes[k].data->count != first->count, cgltf_result_invalid_gltf);
      }

      for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k)
      {
        for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m)
        {
          CGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets[k].attributes[m].data->count != first->count, cgltf_result_invalid_gltf);
        }
      }

      cgltf_accessor* indices = data->meshes[i].primitives[j].indices;

      CGLTF_ASSERT_IF(indices &&
        indices->component_type != cgltf_component_type_r_8u &&
        indices->component_type != cgltf_component_type_r_16u &&
        indices->component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf);

      CGLTF_ASSERT_IF(indices && indices->type != cgltf_type_scalar, cgltf_result_invalid_gltf);
      CGLTF_ASSERT_IF(indices && indices->stride != cgltf_component_size(indices->component_type), cgltf_result_invalid_gltf);

      if (indices && indices->buffer_view && indices->buffer_view->buffer->data)
      {
        cgltf_size index_bound = cgltf_calc_index_bound(indices->buffer_view, indices->offset, indices->component_type, indices->count);

        CGLTF_ASSERT_IF(index_bound >= first->count, cgltf_result_data_too_short);
      }

      for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k)
      {
        CGLTF_ASSERT_IF(data->meshes[i].primitives[j].mappings[k].variant >= data->variants_count, cgltf_result_invalid_gltf);
      }
    }
  }

  for (cgltf_size i = 0; i < data->nodes_count; ++i)
  {
    if (data->nodes[i].weights && data->nodes[i].mesh)
    {
      CGLTF_ASSERT_IF(data->nodes[i].mesh->primitives_count && data->nodes[i].mesh->primitives[0].targets_count != data->nodes[i].weights_count, cgltf_result_invalid_gltf);
    }

    if (data->nodes[i].has_mesh_gpu_instancing)
    {
      CGLTF_ASSERT_IF(data->nodes[i].mesh == NULL, cgltf_result_invalid_gltf);
      CGLTF_ASSERT_IF(data->nodes[i].mesh_gpu_instancing.attributes_count == 0, cgltf_result_invalid_gltf);

      cgltf_accessor* first = data->nodes[i].mesh_gpu_instancing.attributes[0].data;

      for (cgltf_size k = 0; k < data->nodes[i].mesh_gpu_instancing.attributes_count; ++k)
      {
        CGLTF_ASSERT_IF(data->nodes[i].mesh_gpu_instancing.attributes[k].data->count != first->count, cgltf_result_invalid_gltf);
      }
    }
  }

  for (cgltf_size i = 0; i < data->nodes_count; ++i)
  {
    cgltf_node* p1 = data->nodes[i].parent;
    cgltf_node* p2 = p1 ? p1->parent : NULL;

    while (p1 && p2)
    {
      CGLTF_ASSERT_IF(p1 == p2, cgltf_result_invalid_gltf);

      p1 = p1->parent;
      p2 = p2->parent ? p2->parent->parent : NULL;
    }
  }

  for (cgltf_size i = 0; i < data->scenes_count; ++i)
  {
    for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j)
    {
      CGLTF_ASSERT_IF(data->scenes[i].nodes[j]->parent, cgltf_result_invalid_gltf);
    }
  }

  for (cgltf_size i = 0; i < data->animations_count; ++i)
  {
    for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j)
    {
      cgltf_animation_channel* channel = &data->animations[i].channels[j];

      if (!channel->target_node)
      {
        continue;
      }

      cgltf_size components = 1;

      if (channel->target_path == cgltf_animation_path_type_weights)
      {
        CGLTF_ASSERT_IF(!channel->target_node->mesh || !channel->target_node->mesh->primitives_count, cgltf_result_invalid_gltf);

        components = channel->target_node->mesh->primitives[0].targets_count;
      }

      cgltf_size values = channel->sampler->interpolation == cgltf_interpolation_type_cubic_spline ? 3 : 1;

      CGLTF_ASSERT_IF(channel->sampler->input->count * components * values != channel->sampler->output->count, cgltf_result_invalid_gltf);
    }
  }

  for (cgltf_size i = 0; i < data->variants_count; ++i)
  {
    CGLTF_ASSERT_IF(!data->variants[i].name, cgltf_result_invalid_gltf);
  }

  return cgltf_result_success;
}

cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size)
{
  cgltf_size json_size = extras->end_offset - extras->start_offset;

  if (!dest)
  {
    if (dest_size)
    {
      *dest_size = json_size + 1;
      return cgltf_result_success;
    }
    return cgltf_result_invalid_options;
  }

  if (*dest_size + 1 < json_size)
  {
    strncpy(dest, data->json + extras->start_offset, *dest_size - 1);
    dest[*dest_size - 1] = 0;
  }
  else
  {
    strncpy(dest, data->json + extras->start_offset, json_size);
    dest[json_size] = 0;
  }

  return cgltf_result_success;
}

static void cgltf_free_extras(cgltf_data* data, cgltf_extras* extras)
{
  data->memory.free_func(data->memory.user_data, extras->data);
}

static void cgltf_free_extensions(cgltf_data* data, cgltf_extension* extensions, cgltf_size extensions_count)
{
  for (cgltf_size i = 0; i < extensions_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, extensions[i].name);
    data->memory.free_func(data->memory.user_data, extensions[i].data);
  }
  data->memory.free_func(data->memory.user_data, extensions);
}

void cgltf_free(cgltf_data* data)
{
  if (!data)
  {
    return;
  }

  void (*file_release)(const struct cgltf_memory_options*, const struct cgltf_file_options*, void* data) = data->file.release ? data->file.release : cgltf_default_file_release;

  data->memory.free_func(data->memory.user_data, data->asset.copyright);
  data->memory.free_func(data->memory.user_data, data->asset.generator);
  data->memory.free_func(data->memory.user_data, data->asset.version);
  data->memory.free_func(data->memory.user_data, data->asset.min_version);

  cgltf_free_extensions(data, data->asset.extensions, data->asset.extensions_count);
  cgltf_free_extras(data, &data->asset.extras);

  for (cgltf_size i = 0; i < data->accessors_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->accessors[i].name);

    cgltf_free_extensions(data, data->accessors[i].extensions, data->accessors[i].extensions_count);
    cgltf_free_extras(data, &data->accessors[i].extras);
  }
  data->memory.free_func(data->memory.user_data, data->accessors);

  for (cgltf_size i = 0; i < data->buffer_views_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->buffer_views[i].name);
    data->memory.free_func(data->memory.user_data, data->buffer_views[i].data);

    cgltf_free_extensions(data, data->buffer_views[i].extensions, data->buffer_views[i].extensions_count);
    cgltf_free_extras(data, &data->buffer_views[i].extras);
  }
  data->memory.free_func(data->memory.user_data, data->buffer_views);

  for (cgltf_size i = 0; i < data->buffers_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->buffers[i].name);

    if (data->buffers[i].data_free_method == cgltf_data_free_method_file_release)
    {
      file_release(&data->memory, &data->file, data->buffers[i].data);
    }
    else if (data->buffers[i].data_free_method == cgltf_data_free_method_memory_free)
    {
      data->memory.free_func(data->memory.user_data, data->buffers[i].data);
    }

    data->memory.free_func(data->memory.user_data, data->buffers[i].uri);

    cgltf_free_extensions(data, data->buffers[i].extensions, data->buffers[i].extensions_count);
    cgltf_free_extras(data, &data->buffers[i].extras);
  }
  data->memory.free_func(data->memory.user_data, data->buffers);

  for (cgltf_size i = 0; i < data->meshes_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->meshes[i].name);

    for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j)
    {
      for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k)
      {
        data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].attributes[k].name);
      }

      data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].attributes);

      for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k)
      {
        for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m)
        {
          data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes[m].name);
        }

        data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes);
      }

      data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets);

      if (data->meshes[i].primitives[j].has_draco_mesh_compression)
      {
        for (cgltf_size k = 0; k < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++k)
        {
          data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes[k].name);
        }

        data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes);
      }

      for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k)
      {
        cgltf_free_extras(data, &data->meshes[i].primitives[j].mappings[k].extras);
      }

      data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].mappings);

      cgltf_free_extensions(data, data->meshes[i].primitives[j].extensions, data->meshes[i].primitives[j].extensions_count);
      cgltf_free_extras(data, &data->meshes[i].primitives[j].extras);
    }

    data->memory.free_func(data->memory.user_data, data->meshes[i].primitives);
    data->memory.free_func(data->memory.user_data, data->meshes[i].weights);

    for (cgltf_size j = 0; j < data->meshes[i].target_names_count; ++j)
    {
      data->memory.free_func(data->memory.user_data, data->meshes[i].target_names[j]);
    }

    cgltf_free_extensions(data, data->meshes[i].extensions, data->meshes[i].extensions_count);
    cgltf_free_extras(data, &data->meshes[i].extras);

    data->memory.free_func(data->memory.user_data, data->meshes[i].target_names);
  }

  data->memory.free_func(data->memory.user_data, data->meshes);

  for (cgltf_size i = 0; i < data->materials_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->materials[i].name);

    cgltf_free_extensions(data, data->materials[i].extensions, data->materials[i].extensions_count);
    cgltf_free_extras(data, &data->materials[i].extras);
  }

  data->memory.free_func(data->memory.user_data, data->materials);

  for (cgltf_size i = 0; i < data->images_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->images[i].name);
    data->memory.free_func(data->memory.user_data, data->images[i].uri);
    data->memory.free_func(data->memory.user_data, data->images[i].mime_type);

    cgltf_free_extensions(data, data->images[i].extensions, data->images[i].extensions_count);
    cgltf_free_extras(data, &data->images[i].extras);
  }

  data->memory.free_func(data->memory.user_data, data->images);

  for (cgltf_size i = 0; i < data->textures_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->textures[i].name);

    cgltf_free_extensions(data, data->textures[i].extensions, data->textures[i].extensions_count);
    cgltf_free_extras(data, &data->textures[i].extras);
  }

  data->memory.free_func(data->memory.user_data, data->textures);

  for (cgltf_size i = 0; i < data->samplers_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->samplers[i].name);

    cgltf_free_extensions(data, data->samplers[i].extensions, data->samplers[i].extensions_count);
    cgltf_free_extras(data, &data->samplers[i].extras);
  }

  data->memory.free_func(data->memory.user_data, data->samplers);

  for (cgltf_size i = 0; i < data->skins_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->skins[i].name);
    data->memory.free_func(data->memory.user_data, data->skins[i].joints);

    cgltf_free_extensions(data, data->skins[i].extensions, data->skins[i].extensions_count);
    cgltf_free_extras(data, &data->skins[i].extras);
  }

  data->memory.free_func(data->memory.user_data, data->skins);

  for (cgltf_size i = 0; i < data->cameras_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->cameras[i].name);

    if (data->cameras[i].type == cgltf_camera_type_perspective)
    {
      cgltf_free_extras(data, &data->cameras[i].data.perspective.extras);
    }
    else if (data->cameras[i].type == cgltf_camera_type_orthographic)
    {
      cgltf_free_extras(data, &data->cameras[i].data.orthographic.extras);
    }

    cgltf_free_extensions(data, data->cameras[i].extensions, data->cameras[i].extensions_count);
    cgltf_free_extras(data, &data->cameras[i].extras);
  }

  data->memory.free_func(data->memory.user_data, data->cameras);

  for (cgltf_size i = 0; i < data->lights_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->lights[i].name);

    cgltf_free_extras(data, &data->lights[i].extras);
  }

  data->memory.free_func(data->memory.user_data, data->lights);

  for (cgltf_size i = 0; i < data->nodes_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->nodes[i].name);
    data->memory.free_func(data->memory.user_data, data->nodes[i].children);
    data->memory.free_func(data->memory.user_data, data->nodes[i].weights);

    if (data->nodes[i].has_mesh_gpu_instancing)
    {
      for (cgltf_size j = 0; j < data->nodes[i].mesh_gpu_instancing.attributes_count; ++j)
      {
        data->memory.free_func(data->memory.user_data, data->nodes[i].mesh_gpu_instancing.attributes[j].name);
      }

      data->memory.free_func(data->memory.user_data, data->nodes[i].mesh_gpu_instancing.attributes);
    }

    cgltf_free_extensions(data, data->nodes[i].extensions, data->nodes[i].extensions_count);
    cgltf_free_extras(data, &data->nodes[i].extras);
  }

  data->memory.free_func(data->memory.user_data, data->nodes);

  for (cgltf_size i = 0; i < data->scenes_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->scenes[i].name);
    data->memory.free_func(data->memory.user_data, data->scenes[i].nodes);

    cgltf_free_extensions(data, data->scenes[i].extensions, data->scenes[i].extensions_count);
    cgltf_free_extras(data, &data->scenes[i].extras);
  }

  data->memory.free_func(data->memory.user_data, data->scenes);

  for (cgltf_size i = 0; i < data->animations_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->animations[i].name);
    for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j)
    {
      cgltf_free_extensions(data, data->animations[i].samplers[j].extensions, data->animations[i].samplers[j].extensions_count);
      cgltf_free_extras(data, &data->animations[i].samplers[j].extras);
    }
    data->memory.free_func(data->memory.user_data, data->animations[i].samplers);

    for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j)
    {
      cgltf_free_extensions(data, data->animations[i].channels[j].extensions, data->animations[i].channels[j].extensions_count);
      cgltf_free_extras(data, &data->animations[i].channels[j].extras);
    }
    data->memory.free_func(data->memory.user_data, data->animations[i].channels);

    cgltf_free_extensions(data, data->animations[i].extensions, data->animations[i].extensions_count);
    cgltf_free_extras(data, &data->animations[i].extras);
  }

  data->memory.free_func(data->memory.user_data, data->animations);

  for (cgltf_size i = 0; i < data->variants_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->variants[i].name);

    cgltf_free_extras(data, &data->variants[i].extras);
  }

  data->memory.free_func(data->memory.user_data, data->variants);

  cgltf_free_extensions(data, data->data_extensions, data->data_extensions_count);
  cgltf_free_extras(data, &data->extras);

  for (cgltf_size i = 0; i < data->extensions_used_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->extensions_used[i]);
  }

  data->memory.free_func(data->memory.user_data, data->extensions_used);

  for (cgltf_size i = 0; i < data->extensions_required_count; ++i)
  {
    data->memory.free_func(data->memory.user_data, data->extensions_required[i]);
  }

  data->memory.free_func(data->memory.user_data, data->extensions_required);

  file_release(&data->memory, &data->file, data->file_data);

  data->memory.free_func(data->memory.user_data, data);
}

void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix)
{
  cgltf_float* lm = out_matrix;

  if (node->has_matrix)
  {
    memcpy(lm, node->matrix, sizeof(float) * 16);
  }
  else
  {
    float tx = node->translation[0];
    float ty = node->translation[1];
    float tz = node->translation[2];

    float qx = node->rotation[0];
    float qy = node->rotation[1];
    float qz = node->rotation[2];
    float qw = node->rotation[3];

    float sx = node->scale[0];
    float sy = node->scale[1];
    float sz = node->scale[2];

    lm[0] = (1 - 2 * qy * qy - 2 * qz * qz) * sx;
    lm[1] = (2 * qx * qy + 2 * qz * qw) * sx;
    lm[2] = (2 * qx * qz - 2 * qy * qw) * sx;
    lm[3] = 0.f;

    lm[4] = (2 * qx * qy - 2 * qz * qw) * sy;
    lm[5] = (1 - 2 * qx * qx - 2 * qz * qz) * sy;
    lm[6] = (2 * qy * qz + 2 * qx * qw) * sy;
    lm[7] = 0.f;

    lm[8] = (2 * qx * qz + 2 * qy * qw) * sz;
    lm[9] = (2 * qy * qz - 2 * qx * qw) * sz;
    lm[10] = (1 - 2 * qx * qx - 2 * qy * qy) * sz;
    lm[11] = 0.f;

    lm[12] = tx;
    lm[13] = ty;
    lm[14] = tz;
    lm[15] = 1.f;
  }
}

void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix)
{
  cgltf_float* lm = out_matrix;
  cgltf_node_transform_local(node, lm);

  const cgltf_node* parent = node->parent;

  while (parent)
  {
    float pm[16];
    cgltf_node_transform_local(parent, pm);

    for (int i = 0; i < 4; ++i)
    {
      float l0 = lm[i * 4 + 0];
      float l1 = lm[i * 4 + 1];
      float l2 = lm[i * 4 + 2];

      float r0 = l0 * pm[0] + l1 * pm[4] + l2 * pm[8];
      float r1 = l0 * pm[1] + l1 * pm[5] + l2 * pm[9];
      float r2 = l0 * pm[2] + l1 * pm[6] + l2 * pm[10];

      lm[i * 4 + 0] = r0;
      lm[i * 4 + 1] = r1;
      lm[i * 4 + 2] = r2;
    }

    lm[12] += pm[12];
    lm[13] += pm[13];
    lm[14] += pm[14];

    parent = parent->parent;
  }
}

static cgltf_ssize cgltf_component_read_integer(const void* in, cgltf_component_type component_type)
{
  switch (component_type)
  {
  case cgltf_component_type_r_16:
    return *((const int16_t*)in);
  case cgltf_component_type_r_16u:
    return *((const uint16_t*)in);
  case cgltf_component_type_r_32u:
    return *((const uint32_t*)in);
  case cgltf_component_type_r_8:
    return *((const int8_t*)in);
  case cgltf_component_type_r_8u:
    return *((const uint8_t*)in);
  default:
    return 0;
  }
}

static cgltf_size cgltf_component_read_index(const void* in, cgltf_component_type component_type)
{
  switch (component_type)
  {
  case cgltf_component_type_r_16u:
    return *((const uint16_t*)in);
  case cgltf_component_type_r_32u:
    return *((const uint32_t*)in);
  case cgltf_component_type_r_8u:
    return *((const uint8_t*)in);
  default:
    return 0;
  }
}

static cgltf_float cgltf_component_read_float(const void* in, cgltf_component_type component_type, cgltf_bool normalized)
{
  if (component_type == cgltf_component_type_r_32f)
  {
    return *((const float*)in);
  }

  if (normalized)
  {
    switch (component_type)
    {
      // note: glTF spec doesn't currently define normalized conversions for 32-bit integers
    case cgltf_component_type_r_16:
      return *((const int16_t*)in) / (cgltf_float)32767;
    case cgltf_component_type_r_16u:
      return *((const uint16_t*)in) / (cgltf_float)65535;
    case cgltf_component_type_r_8:
      return *((const int8_t*)in) / (cgltf_float)127;
    case cgltf_component_type_r_8u:
      return *((const uint8_t*)in) / (cgltf_float)255;
    default:
      return 0;
    }
  }

  return (cgltf_float)cgltf_component_read_integer(in, component_type);
}

static cgltf_bool cgltf_element_read_float(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_bool normalized, cgltf_float* out, cgltf_size element_size)
{
  cgltf_size num_components = cgltf_num_components(type);

  if (element_size < num_components) {
    return 0;
  }

  // There are three special cases for component extraction, see #data-alignment in the 2.0 spec.

  cgltf_size component_size = cgltf_component_size(component_type);

  if (type == cgltf_type_mat2 && component_size == 1)
  {
    out[0] = cgltf_component_read_float(element, component_type, normalized);
    out[1] = cgltf_component_read_float(element + 1, component_type, normalized);
    out[2] = cgltf_component_read_float(element + 4, component_type, normalized);
    out[3] = cgltf_component_read_float(element + 5, component_type, normalized);
    return 1;
  }

  if (type == cgltf_type_mat3 && component_size == 1)
  {
    out[0] = cgltf_component_read_float(element, component_type, normalized);
    out[1] = cgltf_component_read_float(element + 1, component_type, normalized);
    out[2] = cgltf_component_read_float(element + 2, component_type, normalized);
    out[3] = cgltf_component_read_float(element + 4, component_type, normalized);
    out[4] = cgltf_component_read_float(element + 5, component_type, normalized);
    out[5] = cgltf_component_read_float(element + 6, component_type, normalized);
    out[6] = cgltf_component_read_float(element + 8, component_type, normalized);
    out[7] = cgltf_component_read_float(element + 9, component_type, normalized);
    out[8] = cgltf_component_read_float(element + 10, component_type, normalized);
    return 1;
  }

  if (type == cgltf_type_mat3 && component_size == 2)
  {
    out[0] = cgltf_component_read_float(element, component_type, normalized);
    out[1] = cgltf_component_read_float(element + 2, component_type, normalized);
    out[2] = cgltf_component_read_float(element + 4, component_type, normalized);
    out[3] = cgltf_component_read_float(element + 8, component_type, normalized);
    out[4] = cgltf_component_read_float(element + 10, component_type, normalized);
    out[5] = cgltf_component_read_float(element + 12, component_type, normalized);
    out[6] = cgltf_component_read_float(element + 16, component_type, normalized);
    out[7] = cgltf_component_read_float(element + 18, component_type, normalized);
    out[8] = cgltf_component_read_float(element + 20, component_type, normalized);
    return 1;
  }

  for (cgltf_size i = 0; i < num_components; ++i)
  {
    out[i] = cgltf_component_read_float(element + component_size * i, component_type, normalized);
  }
  return 1;
}

const uint8_t* cgltf_buffer_view_data(const cgltf_buffer_view* view)
{
  if (view->data)
    return (const uint8_t*)view->data;

  if (!view->buffer->data)
    return NULL;

  const uint8_t* result = (const uint8_t*)view->buffer->data;
  result += view->offset;
  return result;
}

const cgltf_accessor* cgltf_find_accessor(const cgltf_primitive* prim, cgltf_attribute_type type, cgltf_int index)
{
  for (cgltf_size i = 0; i < prim->attributes_count; ++i)
  {
    const cgltf_attribute* attr = &prim->attributes[i];
    if (attr->type == type && attr->index == index)
      return attr->data;
  }

  return NULL;
}

cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size)
{
  if (accessor->is_sparse)
  {
    return 0;
  }
  if (accessor->buffer_view == NULL)
  {
    memset(out, 0, element_size * sizeof(cgltf_float));
    return 1;
  }
  const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view);
  if (element == NULL)
  {
    return 0;
  }
  element += accessor->offset + accessor->stride * index;
  return cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, out, element_size);
}

cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count)
{
  cgltf_size floats_per_element = cgltf_num_components(accessor->type);
  cgltf_size available_floats = accessor->count * floats_per_element;
  if (out == NULL)
  {
    return available_floats;
  }

  float_count = available_floats < float_count ? available_floats : float_count;
  cgltf_size element_count = float_count / floats_per_element;

  // First pass: convert each element in the base accessor.
  if (accessor->buffer_view == NULL)
  {
    memset(out, 0, element_count * floats_per_element * sizeof(cgltf_float));
  }
  else
  {
    const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view);
    if (element == NULL)
    {
      return 0;
    }
    element += accessor->offset;

    if (accessor->component_type == cgltf_component_type_r_32f && accessor->stride == floats_per_element * sizeof(cgltf_float))
    {
      memcpy(out, element, element_count * floats_per_element * sizeof(cgltf_float));
    }
    else
    {
      cgltf_float* dest = out;

      for (cgltf_size index = 0; index < element_count; index++, dest += floats_per_element, element += accessor->stride)
      {
        if (!cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, dest, floats_per_element))
        {
          return 0;
        }
      }
    }
  }

  // Second pass: write out each element in the sparse accessor.
  if (accessor->is_sparse)
  {
    const cgltf_accessor_sparse* sparse = &accessor->sparse;

    const uint8_t* index_data = cgltf_buffer_view_data(sparse->indices_buffer_view);
    const uint8_t* reader_head = cgltf_buffer_view_data(sparse->values_buffer_view);

    if (index_data == NULL || reader_head == NULL)
    {
      return 0;
    }

    index_data += sparse->indices_byte_offset;
    reader_head += sparse->values_byte_offset;

    cgltf_size index_stride = cgltf_component_size(sparse->indices_component_type);
    for (cgltf_size reader_index = 0; reader_index < sparse->count; reader_index++, index_data += index_stride, reader_head += accessor->stride)
    {
      size_t writer_index = cgltf_component_read_index(index_data, sparse->indices_component_type);
      float* writer_head = out + writer_index * floats_per_element;

      if (!cgltf_element_read_float(reader_head, accessor->type, accessor->component_type, accessor->normalized, writer_head, floats_per_element))
      {
        return 0;
      }
    }
  }

  return element_count * floats_per_element;
}

static cgltf_uint cgltf_component_read_uint(const void* in, cgltf_component_type component_type)
{
  switch (component_type)
  {
  case cgltf_component_type_r_8:
    return *((const int8_t*)in);

  case cgltf_component_type_r_8u:
    return *((const uint8_t*)in);

  case cgltf_component_type_r_16:
    return *((const int16_t*)in);

  case cgltf_component_type_r_16u:
    return *((const uint16_t*)in);

  case cgltf_component_type_r_32u:
    return *((const uint32_t*)in);

  default:
    return 0;
  }
}

static cgltf_bool cgltf_element_read_uint(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_uint* out, cgltf_size element_size)
{
  cgltf_size num_components = cgltf_num_components(type);

  if (element_size < num_components)
  {
    return 0;
  }

  // Reading integer matrices is not a valid use case
  if (type == cgltf_type_mat2 || type == cgltf_type_mat3 || type == cgltf_type_mat4)
  {
    return 0;
  }

  cgltf_size component_size = cgltf_component_size(component_type);

  for (cgltf_size i = 0; i < num_components; ++i)
  {
    out[i] = cgltf_component_read_uint(element + component_size * i, component_type);
  }
  return 1;
}

cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size)
{
  if (accessor->is_sparse)
  {
    return 0;
  }
  if (accessor->buffer_view == NULL)
  {
    memset(out, 0, element_size * sizeof(cgltf_uint));
    return 1;
  }
  const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view);
  if (element == NULL)
  {
    return 0;
  }
  element += accessor->offset + accessor->stride * index;
  return cgltf_element_read_uint(element, accessor->type, accessor->component_type, out, element_size);
}

cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index)
{
  if (accessor->is_sparse)
  {
    return 0; // This is an error case, but we can't communicate the error with existing interface.
  }
  if (accessor->buffer_view == NULL)
  {
    return 0;
  }
  const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view);
  if (element == NULL)
  {
    return 0; // This is an error case, but we can't communicate the error with existing interface.
  }
  element += accessor->offset + accessor->stride * index;
  return cgltf_component_read_index(element, accessor->component_type);
}

cgltf_size cgltf_mesh_index(const cgltf_data* data, const cgltf_mesh* object)
{
  assert(object && (cgltf_size)(object - data->meshes) < data->meshes_count);
  return (cgltf_size)(object - data->meshes);
}

cgltf_size cgltf_material_index(const cgltf_data* data, const cgltf_material* object)
{
  assert(object && (cgltf_size)(object - data->materials) < data->materials_count);
  return (cgltf_size)(object - data->materials);
}

cgltf_size cgltf_accessor_index(const cgltf_data* data, const cgltf_accessor* object)
{
  assert(object && (cgltf_size)(object - data->accessors) < data->accessors_count);
  return (cgltf_size)(object - data->accessors);
}

cgltf_size cgltf_buffer_view_index(const cgltf_data* data, const cgltf_buffer_view* object)
{
  assert(object && (cgltf_size)(object - data->buffer_views) < data->buffer_views_count);
  return (cgltf_size)(object - data->buffer_views);
}

cgltf_size cgltf_buffer_index(const cgltf_data* data, const cgltf_buffer* object)
{
  assert(object && (cgltf_size)(object - data->buffers) < data->buffers_count);
  return (cgltf_size)(object - data->buffers);
}

cgltf_size cgltf_image_index(const cgltf_data* data, const cgltf_image* object)
{
  assert(object && (cgltf_size)(object - data->images) < data->images_count);
  return (cgltf_size)(object - data->images);
}

cgltf_size cgltf_texture_index(const cgltf_data* data, const cgltf_texture* object)
{
  assert(object && (cgltf_size)(object - data->textures) < data->textures_count);
  return (cgltf_size)(object - data->textures);
}

cgltf_size cgltf_sampler_index(const cgltf_data* data, const cgltf_sampler* object)
{
  assert(object && (cgltf_size)(object - data->samplers) < data->samplers_count);
  return (cgltf_size)(object - data->samplers);
}

cgltf_size cgltf_skin_index(const cgltf_data* data, const cgltf_skin* object)
{
  assert(object && (cgltf_size)(object - data->skins) < data->skins_count);
  return (cgltf_size)(object - data->skins);
}

cgltf_size cgltf_camera_index(const cgltf_data* data, const cgltf_camera* object)
{
  assert(object && (cgltf_size)(object - data->cameras) < data->cameras_count);
  return (cgltf_size)(object - data->cameras);
}

cgltf_size cgltf_light_index(const cgltf_data* data, const cgltf_light* object)
{
  assert(object && (cgltf_size)(object - data->lights) < data->lights_count);
  return (cgltf_size)(object - data->lights);
}

cgltf_size cgltf_node_index(const cgltf_data* data, const cgltf_node* object)
{
  assert(object && (cgltf_size)(object - data->nodes) < data->nodes_count);
  return (cgltf_size)(object - data->nodes);
}

cgltf_size cgltf_scene_index(const cgltf_data* data, const cgltf_scene* object)
{
  assert(object && (cgltf_size)(object - data->scenes) < data->scenes_count);
  return (cgltf_size)(object - data->scenes);
}

cgltf_size cgltf_animation_index(const cgltf_data* data, const cgltf_animation* object)
{
  assert(object && (cgltf_size)(object - data->animations) < data->animations_count);
  return (cgltf_size)(object - data->animations);
}

cgltf_size cgltf_animation_sampler_index(const cgltf_animation* animation, const cgltf_animation_sampler* object)
{
  assert(object && (cgltf_size)(object - animation->samplers) < animation->samplers_count);
  return (cgltf_size)(object - animation->samplers);
}

cgltf_size cgltf_animation_channel_index(const cgltf_animation* animation, const cgltf_animation_channel* object)
{
  assert(object && (cgltf_size)(object - animation->channels) < animation->channels_count);
  return (cgltf_size)(object - animation->channels);
}

cgltf_size cgltf_accessor_unpack_indices(const cgltf_accessor* accessor, void* out, cgltf_size out_component_size, cgltf_size index_count)
{
  if (out == NULL)
  {
    return accessor->count;
  }

  cgltf_size numbers_per_element = cgltf_num_components(accessor->type);
  cgltf_size available_numbers = accessor->count * numbers_per_element;

  index_count = available_numbers < index_count ? available_numbers : index_count;
  cgltf_size index_component_size = cgltf_component_size(accessor->component_type);

  if (accessor->is_sparse)
  {
    return 0;
  }
  if (accessor->buffer_view == NULL)
  {
    return 0;
  }
  if (index_component_size > out_component_size)
  {
    return 0;
  }
  const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view);
  if (element == NULL)
  {
    return 0;
  }
  element += accessor->offset;

  if (index_component_size == out_component_size && accessor->stride == out_component_size * numbers_per_element)
  {
    memcpy(out, element, index_count * index_component_size);
    return index_count;
  }

  // Data couldn't be copied with memcpy due to stride being larger than the component size.
  // OR
  // The component size of the output array is larger than the component size of the index data, so index data will be padded.
  switch (out_component_size)
  {
  case 1:
    for (cgltf_size index = 0; index < index_count; index++, element += accessor->stride)
    {
      ((uint8_t*)out)[index] = (uint8_t)cgltf_component_read_index(element, accessor->component_type);
    }
    break;
  case 2:
    for (cgltf_size index = 0; index < index_count; index++, element += accessor->stride)
    {
      ((uint16_t*)out)[index] = (uint16_t)cgltf_component_read_index(element, accessor->component_type);
    }
    break;
  case 4:
    for (cgltf_size index = 0; index < index_count; index++, element += accessor->stride)
    {
      ((uint32_t*)out)[index] = (uint32_t)cgltf_component_read_index(element, accessor->component_type);
    }
    break;
  default:
    return 0;
  }

  return index_count;
}

#define CGLTF_ERROR_JSON -1
#define CGLTF_ERROR_NOMEM -2
#define CGLTF_ERROR_LEGACY -3

#define CGLTF_CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return CGLTF_ERROR_JSON; }
#define CGLTF_CHECK_TOKTYPE_RET(tok_, type_, ret_) if ((tok_).type != (type_)) { return ret_; }
#define CGLTF_CHECK_KEY(tok_) if ((tok_).type != JSMN_STRING || (tok_).size == 0) { return CGLTF_ERROR_JSON; } /* checking size for 0 verifies that a value follows the key */

#define CGLTF_PTRINDEX(type, idx) (type*)((cgltf_size)idx + 1)
#define CGLTF_PTRFIXUP(var, data, size) if (var) { if ((cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; }
#define CGLTF_PTRFIXUP_REQ(var, data, size) if (!var || (cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1];

static int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_chunk, const char* str)
{
  CGLTF_CHECK_TOKTYPE(*tok, JSMN_STRING);
  size_t const str_len = strlen(str);
  size_t const name_length = (size_t)(tok->end - tok->start);
  return (str_len == name_length) ? strncmp((const char*)json_chunk + tok->start, str, str_len) : 128;
}

static int cgltf_json_to_int(jsmntok_t const* tok, const uint8_t* json_chunk)
{
  CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE);
  char tmp[128];
  int size = (size_t)(tok->end - tok->start) < sizeof(tmp) ? (int)(tok->end - tok->start) : (int)(sizeof(tmp) - 1);
  strncpy(tmp, (const char*)json_chunk + tok->start, size);
  tmp[size] = 0;
  return CGLTF_ATOI(tmp);
}

static cgltf_size cgltf_json_to_size(jsmntok_t const* tok, const uint8_t* json_chunk)
{
  CGLTF_CHECK_TOKTYPE_RET(*tok, JSMN_PRIMITIVE, 0);
  char tmp[128];
  int size = (size_t)(tok->end - tok->start) < sizeof(tmp) ? (int)(tok->end - tok->start) : (int)(sizeof(tmp) - 1);
  strncpy(tmp, (const char*)json_chunk + tok->start, size);
  tmp[size] = 0;
  long long res = CGLTF_ATOLL(tmp);
  return res < 0 ? 0 : (cgltf_size)res;
}

static cgltf_float cgltf_json_to_float(jsmntok_t const* tok, const uint8_t* json_chunk)
{
  CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE);
  char tmp[128];
  int size = (size_t)(tok->end - tok->start) < sizeof(tmp) ? (int)(tok->end - tok->start) : (int)(sizeof(tmp) - 1);
  strncpy(tmp, (const char*)json_chunk + tok->start, size);
  tmp[size] = 0;
  return (cgltf_float)CGLTF_ATOF(tmp);
}

static cgltf_bool cgltf_json_to_bool(jsmntok_t const* tok, const uint8_t* json_chunk)
{
  int size = (int)(tok->end - tok->start);
  return size == 4 && memcmp(json_chunk + tok->start, "true", 4) == 0;
}

static int cgltf_skip_json(jsmntok_t const* tokens, int i)
{
  int end = i + 1;

  while (i < end)
  {
    switch (tokens[i].type)
    {
    case JSMN_OBJECT:
      end += tokens[i].size * 2;
      break;

    case JSMN_ARRAY:
      end += tokens[i].size;
      break;

    case JSMN_PRIMITIVE:
    case JSMN_STRING:
      break;

    default:
      return -1;
    }

    i++;
  }

  return i;
}

static void cgltf_fill_float_array(float* out_array, int size, float value)
{
  for (int j = 0; j < size; ++j)
  {
    out_array[j] = value;
  }
}

static int cgltf_parse_json_float_array(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, float* out_array, int size)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY);
  if (tokens[i].size != size)
  {
    return CGLTF_ERROR_JSON;
  }
  ++i;
  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);
    out_array[j] = cgltf_json_to_float(tokens + i, json_chunk);
    ++i;
  }
  return i;
}

static int cgltf_parse_json_string(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char** out_string)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING);
  if (*out_string)
  {
    return CGLTF_ERROR_JSON;
  }
  int size = (int)(tokens[i].end - tokens[i].start);
  char* result = (char*)options->memory.alloc_func(options->memory.user_data, size + 1);
  if (!result)
  {
    return CGLTF_ERROR_NOMEM;
  }
  strncpy(result, (const char*)json_chunk + tokens[i].start, size);
  result[size] = 0;
  *out_string = result;
  return i + 1;
}

static int cgltf_parse_json_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, size_t element_size, void** out_array, cgltf_size* out_size)
{
  (void)json_chunk;
  if (tokens[i].type != JSMN_ARRAY)
  {
    return tokens[i].type == JSMN_OBJECT ? CGLTF_ERROR_LEGACY : CGLTF_ERROR_JSON;
  }
  if (*out_array)
  {
    return CGLTF_ERROR_JSON;
  }
  int size = tokens[i].size;
  void* result = cgltf_calloc(options, element_size, size);
  if (!result)
  {
    return CGLTF_ERROR_NOMEM;
  }
  *out_array = result;
  *out_size = size;
  return i + 1;
}

static int cgltf_parse_json_string_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char*** out_array, cgltf_size* out_size)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY);
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(char*), (void**)out_array, out_size);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < *out_size; ++j)
  {
    i = cgltf_parse_json_string(options, tokens, i, json_chunk, j + (*out_array));
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static void cgltf_parse_attribute_type(const char* name, cgltf_attribute_type* out_type, int* out_index)
{
  if (*name == '_')
  {
    *out_type = cgltf_attribute_type_custom;
    return;
  }

  const char* us = strchr(name, '_');
  size_t len = us ? (size_t)(us - name) : strlen(name);

  if (len == 8 && strncmp(name, "POSITION", 8) == 0)
  {
    *out_type = cgltf_attribute_type_position;
  }
  else if (len == 6 && strncmp(name, "NORMAL", 6) == 0)
  {
    *out_type = cgltf_attribute_type_normal;
  }
  else if (len == 7 && strncmp(name, "TANGENT", 7) == 0)
  {
    *out_type = cgltf_attribute_type_tangent;
  }
  else if (len == 8 && strncmp(name, "TEXCOORD", 8) == 0)
  {
    *out_type = cgltf_attribute_type_texcoord;
  }
  else if (len == 5 && strncmp(name, "COLOR", 5) == 0)
  {
    *out_type = cgltf_attribute_type_color;
  }
  else if (len == 6 && strncmp(name, "JOINTS", 6) == 0)
  {
    *out_type = cgltf_attribute_type_joints;
  }
  else if (len == 7 && strncmp(name, "WEIGHTS", 7) == 0)
  {
    *out_type = cgltf_attribute_type_weights;
  }
  else
  {
    *out_type = cgltf_attribute_type_invalid;
  }

  if (us && *out_type != cgltf_attribute_type_invalid)
  {
    *out_index = CGLTF_ATOI(us + 1);
    if (*out_index < 0)
    {
      *out_type = cgltf_attribute_type_invalid;
      *out_index = 0;
    }
  }
}

static int cgltf_parse_json_attribute_list(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_attribute** out_attributes, cgltf_size* out_attributes_count)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  if (*out_attributes)
  {
    return CGLTF_ERROR_JSON;
  }

  *out_attributes_count = tokens[i].size;
  *out_attributes = (cgltf_attribute*)cgltf_calloc(options, sizeof(cgltf_attribute), *out_attributes_count);
  ++i;

  if (!*out_attributes)
  {
    return CGLTF_ERROR_NOMEM;
  }

  for (cgltf_size j = 0; j < *out_attributes_count; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    i = cgltf_parse_json_string(options, tokens, i, json_chunk, &(*out_attributes)[j].name);
    if (i < 0)
    {
      return CGLTF_ERROR_JSON;
    }

    cgltf_parse_attribute_type((*out_attributes)[j].name, &(*out_attributes)[j].type, &(*out_attributes)[j].index);

    (*out_attributes)[j].data = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk));
    ++i;
  }

  return i;
}

static int cgltf_parse_json_extras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extras* out_extras)
{
  if (out_extras->data)
  {
    return CGLTF_ERROR_JSON;
  }

  /* fill deprecated fields for now, this will be removed in the future */
  out_extras->start_offset = tokens[i].start;
  out_extras->end_offset = tokens[i].end;

  size_t start = tokens[i].start;
  size_t size = tokens[i].end - start;
  out_extras->data = (char*)options->memory.alloc_func(options->memory.user_data, size + 1);
  if (!out_extras->data)
  {
    return CGLTF_ERROR_NOMEM;
  }
  strncpy(out_extras->data, (const char*)json_chunk + start, size);
  out_extras->data[size] = '\0';

  i = cgltf_skip_json(tokens, i);
  return i;
}

static int cgltf_parse_json_unprocessed_extension(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extension* out_extension)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING);
  CGLTF_CHECK_TOKTYPE(tokens[i + 1], JSMN_OBJECT);
  if (out_extension->name)
  {
    return CGLTF_ERROR_JSON;
  }

  cgltf_size name_length = tokens[i].end - tokens[i].start;
  out_extension->name = (char*)options->memory.alloc_func(options->memory.user_data, name_length + 1);
  if (!out_extension->name)
  {
    return CGLTF_ERROR_NOMEM;
  }
  strncpy(out_extension->name, (const char*)json_chunk + tokens[i].start, name_length);
  out_extension->name[name_length] = 0;
  i++;

  size_t start = tokens[i].start;
  size_t size = tokens[i].end - start;
  out_extension->data = (char*)options->memory.alloc_func(options->memory.user_data, size + 1);
  if (!out_extension->data)
  {
    return CGLTF_ERROR_NOMEM;
  }
  strncpy(out_extension->data, (const char*)json_chunk + start, size);
  out_extension->data[size] = '\0';

  i = cgltf_skip_json(tokens, i);

  return i;
}

static int cgltf_parse_json_unprocessed_extensions(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_size* out_extensions_count, cgltf_extension** out_extensions)
{
  ++i;

  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  if (*out_extensions)
  {
    return CGLTF_ERROR_JSON;
  }

  int extensions_size = tokens[i].size;
  *out_extensions_count = 0;
  *out_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);

  if (!*out_extensions)
  {
    return CGLTF_ERROR_NOMEM;
  }

  ++i;

  for (int j = 0; j < extensions_size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    cgltf_size extension_index = (*out_extensions_count)++;
    cgltf_extension* extension = &((*out_extensions)[extension_index]);
    i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, extension);

    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_draco_mesh_compression(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_draco_mesh_compression* out_draco_mesh_compression)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "attributes") == 0)
    {
      i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_draco_mesh_compression->attributes, &out_draco_mesh_compression->attributes_count);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferView") == 0)
    {
      ++i;
      out_draco_mesh_compression->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_mesh_gpu_instancing(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_mesh_gpu_instancing* out_mesh_gpu_instancing)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "attributes") == 0)
    {
      i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_mesh_gpu_instancing->attributes, &out_mesh_gpu_instancing->attributes_count);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_material_mapping_data(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material_mapping* out_mappings, cgltf_size* offset)
{
  (void)options;
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

    int obj_size = tokens[i].size;
    ++i;

    int material = -1;
    int variants_tok = -1;
    int extras_tok = -1;

    for (int k = 0; k < obj_size; ++k)
    {
      CGLTF_CHECK_KEY(tokens[i]);

      if (cgltf_json_strcmp(tokens + i, json_chunk, "material") == 0)
      {
        ++i;
        material = cgltf_json_to_int(tokens + i, json_chunk);
        ++i;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "variants") == 0)
      {
        variants_tok = i + 1;
        CGLTF_CHECK_TOKTYPE(tokens[variants_tok], JSMN_ARRAY);

        i = cgltf_skip_json(tokens, i + 1);
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
      {
        extras_tok = i + 1;
        i = cgltf_skip_json(tokens, extras_tok);
      }
      else
      {
        i = cgltf_skip_json(tokens, i + 1);
      }

      if (i < 0)
      {
        return i;
      }
    }

    if (material < 0 || variants_tok < 0)
    {
      return CGLTF_ERROR_JSON;
    }

    if (out_mappings)
    {
      for (int k = 0; k < tokens[variants_tok].size; ++k)
      {
        int variant = cgltf_json_to_int(&tokens[variants_tok + 1 + k], json_chunk);
        if (variant < 0)
          return variant;

        out_mappings[*offset].material = CGLTF_PTRINDEX(cgltf_material, material);
        out_mappings[*offset].variant = variant;

        if (extras_tok >= 0)
        {
          int e = cgltf_parse_json_extras(options, tokens, extras_tok, json_chunk, &out_mappings[*offset].extras);
          if (e < 0)
            return e;
        }

        (*offset)++;
      }
    }
    else
    {
      (*offset) += tokens[variants_tok].size;
    }
  }

  return i;
}

static int cgltf_parse_json_material_mappings(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "mappings") == 0)
    {
      if (out_prim->mappings)
      {
        return CGLTF_ERROR_JSON;
      }

      cgltf_size mappings_offset = 0;
      int k = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, NULL, &mappings_offset);
      if (k < 0)
      {
        return k;
      }

      out_prim->mappings_count = mappings_offset;
      out_prim->mappings = (cgltf_material_mapping*)cgltf_calloc(options, sizeof(cgltf_material_mapping), out_prim->mappings_count);

      mappings_offset = 0;
      i = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, out_prim->mappings, &mappings_offset);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static cgltf_primitive_type cgltf_json_to_primitive_type(jsmntok_t const* tok, const uint8_t* json_chunk)
{
  int type = cgltf_json_to_int(tok, json_chunk);

  switch (type)
  {
  case 0:
    return cgltf_primitive_type_points;
  case 1:
    return cgltf_primitive_type_lines;
  case 2:
    return cgltf_primitive_type_line_loop;
  case 3:
    return cgltf_primitive_type_line_strip;
  case 4:
    return cgltf_primitive_type_triangles;
  case 5:
    return cgltf_primitive_type_triangle_strip;
  case 6:
    return cgltf_primitive_type_triangle_fan;
  default:
    return cgltf_primitive_type_invalid;
  }
}

static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  out_prim->type = cgltf_primitive_type_triangles;

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "mode") == 0)
    {
      ++i;
      out_prim->type = cgltf_json_to_primitive_type(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "indices") == 0)
    {
      ++i;
      out_prim->indices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "material") == 0)
    {
      ++i;
      out_prim->material = CGLTF_PTRINDEX(cgltf_material, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "attributes") == 0)
    {
      i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_prim->attributes, &out_prim->attributes_count);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "targets") == 0)
    {
      i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_morph_target), (void**)&out_prim->targets, &out_prim->targets_count);
      if (i < 0)
      {
        return i;
      }

      for (cgltf_size k = 0; k < out_prim->targets_count; ++k)
      {
        i = cgltf_parse_json_attribute_list(options, tokens, i, json_chunk, &out_prim->targets[k].attributes, &out_prim->targets[k].attributes_count);
        if (i < 0)
        {
          return i;
        }
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_prim->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      ++i;

      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
      if (out_prim->extensions)
      {
        return CGLTF_ERROR_JSON;
      }

      int extensions_size = tokens[i].size;
      out_prim->extensions_count = 0;
      out_prim->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);

      if (!out_prim->extensions)
      {
        return CGLTF_ERROR_NOMEM;
      }

      ++i;
      for (int k = 0; k < extensions_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_draco_mesh_compression") == 0)
        {
          out_prim->has_draco_mesh_compression = 1;
          i = cgltf_parse_json_draco_mesh_compression(options, tokens, i + 1, json_chunk, &out_prim->draco_mesh_compression);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_variants") == 0)
        {
          i = cgltf_parse_json_material_mappings(options, tokens, i + 1, json_chunk, out_prim);
        }
        else
        {
          i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_prim->extensions[out_prim->extensions_count++]));
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_mesh* out_mesh)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_mesh->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "primitives") == 0)
    {
      i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_primitive), (void**)&out_mesh->primitives, &out_mesh->primitives_count);
      if (i < 0)
      {
        return i;
      }

      for (cgltf_size prim_index = 0; prim_index < out_mesh->primitives_count; ++prim_index)
      {
        i = cgltf_parse_json_primitive(options, tokens, i, json_chunk, &out_mesh->primitives[prim_index]);
        if (i < 0)
        {
          return i;
        }
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0)
    {
      i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_mesh->weights, &out_mesh->weights_count);
      if (i < 0)
      {
        return i;
      }

      i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_mesh->weights, (int)out_mesh->weights_count);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      ++i;

      out_mesh->extras.start_offset = tokens[i].start;
      out_mesh->extras.end_offset = tokens[i].end;

      if (tokens[i].type == JSMN_OBJECT)
      {
        int extras_size = tokens[i].size;
        ++i;

        for (int k = 0; k < extras_size; ++k)
        {
          CGLTF_CHECK_KEY(tokens[i]);

          if (cgltf_json_strcmp(tokens + i, json_chunk, "targetNames") == 0 && tokens[i + 1].type == JSMN_ARRAY)
          {
            i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_mesh->target_names, &out_mesh->target_names_count);
          }
          else
          {
            i = cgltf_skip_json(tokens, i + 1);
          }

          if (i < 0)
          {
            return i;
          }
        }
      }
      else
      {
        i = cgltf_skip_json(tokens, i);
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_mesh->extensions_count, &out_mesh->extensions);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_meshes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_mesh), (void**)&out_data->meshes, &out_data->meshes_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->meshes_count; ++j)
  {
    i = cgltf_parse_json_mesh(options, tokens, i, json_chunk, &out_data->meshes[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static cgltf_component_type cgltf_json_to_component_type(jsmntok_t const* tok, const uint8_t* json_chunk)
{
  int type = cgltf_json_to_int(tok, json_chunk);

  switch (type)
  {
  case 5120:
    return cgltf_component_type_r_8;
  case 5121:
    return cgltf_component_type_r_8u;
  case 5122:
    return cgltf_component_type_r_16;
  case 5123:
    return cgltf_component_type_r_16u;
  case 5125:
    return cgltf_component_type_r_32u;
  case 5126:
    return cgltf_component_type_r_32f;
  default:
    return cgltf_component_type_invalid;
  }
}

static int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor_sparse* out_sparse)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "count") == 0)
    {
      ++i;
      out_sparse->count = cgltf_json_to_size(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "indices") == 0)
    {
      ++i;
      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

      int indices_size = tokens[i].size;
      ++i;

      for (int k = 0; k < indices_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferView") == 0)
        {
          ++i;
          out_sparse->indices_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk));
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "byteOffset") == 0)
        {
          ++i;
          out_sparse->indices_byte_offset = cgltf_json_to_size(tokens + i, json_chunk);
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "componentType") == 0)
        {
          ++i;
          out_sparse->indices_component_type = cgltf_json_to_component_type(tokens + i, json_chunk);
          ++i;
        }
        else
        {
          i = cgltf_skip_json(tokens, i + 1);
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "values") == 0)
    {
      ++i;
      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

      int values_size = tokens[i].size;
      ++i;

      for (int k = 0; k < values_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferView") == 0)
        {
          ++i;
          out_sparse->values_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk));
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "byteOffset") == 0)
        {
          ++i;
          out_sparse->values_byte_offset = cgltf_json_to_size(tokens + i, json_chunk);
          ++i;
        }
        else
        {
          i = cgltf_skip_json(tokens, i + 1);
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_accessor(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor* out_accessor)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_accessor->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferView") == 0)
    {
      ++i;
      out_accessor->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "byteOffset") == 0)
    {
      ++i;
      out_accessor->offset =
        cgltf_json_to_size(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "componentType") == 0)
    {
      ++i;
      out_accessor->component_type = cgltf_json_to_component_type(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "normalized") == 0)
    {
      ++i;
      out_accessor->normalized = cgltf_json_to_bool(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "count") == 0)
    {
      ++i;
      out_accessor->count = cgltf_json_to_size(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "type") == 0)
    {
      ++i;
      if (cgltf_json_strcmp(tokens + i, json_chunk, "SCALAR") == 0)
      {
        out_accessor->type = cgltf_type_scalar;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "VEC2") == 0)
      {
        out_accessor->type = cgltf_type_vec2;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "VEC3") == 0)
      {
        out_accessor->type = cgltf_type_vec3;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "VEC4") == 0)
      {
        out_accessor->type = cgltf_type_vec4;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "MAT2") == 0)
      {
        out_accessor->type = cgltf_type_mat2;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "MAT3") == 0)
      {
        out_accessor->type = cgltf_type_mat3;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "MAT4") == 0)
      {
        out_accessor->type = cgltf_type_mat4;
      }
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "min") == 0)
    {
      ++i;
      out_accessor->has_min = 1;
      // note: we can't parse the precise number of elements since type may not have been computed yet
      int min_size = tokens[i].size > 16 ? 16 : tokens[i].size;
      i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->min, min_size);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "max") == 0)
    {
      ++i;
      out_accessor->has_max = 1;
      // note: we can't parse the precise number of elements since type may not have been computed yet
      int max_size = tokens[i].size > 16 ? 16 : tokens[i].size;
      i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->max, max_size);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "sparse") == 0)
    {
      out_accessor->is_sparse = 1;
      i = cgltf_parse_json_accessor_sparse(tokens, i + 1, json_chunk, &out_accessor->sparse);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_accessor->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_accessor->extensions_count, &out_accessor->extensions);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_texture_transform(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_transform* out_texture_transform)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "offset") == 0)
    {
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->offset, 2);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "rotation") == 0)
    {
      ++i;
      out_texture_transform->rotation = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0)
    {
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->scale, 2);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0)
    {
      ++i;
      out_texture_transform->has_texcoord = 1;
      out_texture_transform->texcoord = cgltf_json_to_int(tokens + i, json_chunk);
      ++i;
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_texture_view(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_view* out_texture_view)
{
  (void)options;

  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  out_texture_view->scale = 1.0f;
  cgltf_fill_float_array(out_texture_view->transform.scale, 2, 1.0f);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "index") == 0)
    {
      ++i;
      out_texture_view->texture = CGLTF_PTRINDEX(cgltf_texture, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0)
    {
      ++i;
      out_texture_view->texcoord = cgltf_json_to_int(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0)
    {
      ++i;
      out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "strength") == 0)
    {
      ++i;
      out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      ++i;

      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
      int extensions_size = tokens[i].size;

      ++i;

      for (int k = 0; k < extensions_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_texture_transform") == 0)
        {
          out_texture_view->has_transform = 1;
          i = cgltf_parse_json_texture_transform(tokens, i + 1, json_chunk, &out_texture_view->transform);
        }
        else
        {
          i = cgltf_skip_json(tokens, i + 1);
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_pbr_metallic_roughness(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_metallic_roughness* out_pbr)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "metallicFactor") == 0)
    {
      ++i;
      out_pbr->metallic_factor =
        cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "roughnessFactor") == 0)
    {
      ++i;
      out_pbr->roughness_factor =
        cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "baseColorFactor") == 0)
    {
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->base_color_factor, 4);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "baseColorTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->base_color_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "metallicRoughnessTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->metallic_roughness_texture);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_pbr_specular_glossiness(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_specular_glossiness* out_pbr)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "diffuseFactor") == 0)
    {
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->diffuse_factor, 4);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "specularFactor") == 0)
    {
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->specular_factor, 3);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "glossinessFactor") == 0)
    {
      ++i;
      out_pbr->glossiness_factor = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "diffuseTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->diffuse_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "specularGlossinessTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->specular_glossiness_texture);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_clearcoat(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_clearcoat* out_clearcoat)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "clearcoatFactor") == 0)
    {
      ++i;
      out_clearcoat->clearcoat_factor = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "clearcoatRoughnessFactor") == 0)
    {
      ++i;
      out_clearcoat->clearcoat_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "clearcoatTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "clearcoatRoughnessTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_roughness_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "clearcoatNormalTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_normal_texture);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_ior(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_ior* out_ior)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;

  // Default values
  out_ior->ior = 1.5f;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "ior") == 0)
    {
      ++i;
      out_ior->ior = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_specular(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_specular* out_specular)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;

  // Default values
  out_specular->specular_factor = 1.0f;
  cgltf_fill_float_array(out_specular->specular_color_factor, 3, 1.0f);

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "specularFactor") == 0)
    {
      ++i;
      out_specular->specular_factor = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "specularColorFactor") == 0)
    {
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_specular->specular_color_factor, 3);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "specularTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "specularColorTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_color_texture);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_transmission(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_transmission* out_transmission)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "transmissionFactor") == 0)
    {
      ++i;
      out_transmission->transmission_factor = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "transmissionTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_transmission->transmission_texture);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_volume(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_volume* out_volume)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "thicknessFactor") == 0)
    {
      ++i;
      out_volume->thickness_factor = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "thicknessTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_volume->thickness_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "attenuationColor") == 0)
    {
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_volume->attenuation_color, 3);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "attenuationDistance") == 0)
    {
      ++i;
      out_volume->attenuation_distance = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_sheen(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sheen* out_sheen)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "sheenColorFactor") == 0)
    {
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_sheen->sheen_color_factor, 3);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "sheenColorTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_color_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "sheenRoughnessFactor") == 0)
    {
      ++i;
      out_sheen->sheen_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "sheenRoughnessTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_roughness_texture);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_emissive_strength(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_emissive_strength* out_emissive_strength)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;

  // Default
  out_emissive_strength->emissive_strength = 1.f;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveStrength") == 0)
    {
      ++i;
      out_emissive_strength->emissive_strength = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_iridescence(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_iridescence* out_iridescence)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;

  // Default
  out_iridescence->iridescence_ior = 1.3f;
  out_iridescence->iridescence_thickness_min = 100.f;
  out_iridescence->iridescence_thickness_max = 400.f;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceFactor") == 0)
    {
      ++i;
      out_iridescence->iridescence_factor = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_iridescence->iridescence_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceIor") == 0)
    {
      ++i;
      out_iridescence->iridescence_ior = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessMinimum") == 0)
    {
      ++i;
      out_iridescence->iridescence_thickness_min = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessMaximum") == 0)
    {
      ++i;
      out_iridescence->iridescence_thickness_max = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_iridescence->iridescence_thickness_texture);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_diffuse_transmission(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_diffuse_transmission* out_diff_transmission)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;

  // Defaults
  cgltf_fill_float_array(out_diff_transmission->diffuse_transmission_color_factor, 3, 1.0f);
  out_diff_transmission->diffuse_transmission_factor = 0.f;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "diffuseTransmissionFactor") == 0)
    {
      ++i;
      out_diff_transmission->diffuse_transmission_factor = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "diffuseTransmissionTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_diff_transmission->diffuse_transmission_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "diffuseTransmissionColorFactor") == 0)
    {
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_diff_transmission->diffuse_transmission_color_factor, 3);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "diffuseTransmissionColorTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_diff_transmission->diffuse_transmission_color_texture);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_anisotropy(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_anisotropy* out_anisotropy)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;


  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "anisotropyStrength") == 0)
    {
      ++i;
      out_anisotropy->anisotropy_strength = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "anisotropyRotation") == 0)
    {
      ++i;
      out_anisotropy->anisotropy_rotation = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "anisotropyTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_anisotropy->anisotropy_texture);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_dispersion(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_dispersion* out_dispersion)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
  int size = tokens[i].size;
  ++i;


  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "dispersion") == 0)
    {
      ++i;
      out_dispersion->dispersion = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_image* out_image)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "uri") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->uri);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferView") == 0)
    {
      ++i;
      out_image->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "mimeType") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->mime_type);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_image->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_image->extensions_count, &out_image->extensions);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sampler* out_sampler)
{
  (void)options;
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  out_sampler->wrap_s = cgltf_wrap_mode_repeat;
  out_sampler->wrap_t = cgltf_wrap_mode_repeat;

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_sampler->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "magFilter") == 0)
    {
      ++i;
      out_sampler->mag_filter
        = (cgltf_filter_type)cgltf_json_to_int(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "minFilter") == 0)
    {
      ++i;
      out_sampler->min_filter
        = (cgltf_filter_type)cgltf_json_to_int(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapS") == 0)
    {
      ++i;
      out_sampler->wrap_s
        = (cgltf_wrap_mode)cgltf_json_to_int(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapT") == 0)
    {
      ++i;
      out_sampler->wrap_t
        = (cgltf_wrap_mode)cgltf_json_to_int(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_sampler->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture* out_texture)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_texture->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "sampler") == 0)
    {
      ++i;
      out_texture->sampler = CGLTF_PTRINDEX(cgltf_sampler, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0)
    {
      ++i;
      out_texture->image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_texture->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      ++i;

      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
      if (out_texture->extensions)
      {
        return CGLTF_ERROR_JSON;
      }

      int extensions_size = tokens[i].size;
      ++i;
      out_texture->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);
      out_texture->extensions_count = 0;

      if (!out_texture->extensions)
      {
        return CGLTF_ERROR_NOMEM;
      }

      for (int k = 0; k < extensions_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_texture_basisu") == 0)
        {
          out_texture->has_basisu = 1;
          ++i;
          CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
          int num_properties = tokens[i].size;
          ++i;

          for (int t = 0; t < num_properties; ++t)
          {
            CGLTF_CHECK_KEY(tokens[i]);

            if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0)
            {
              ++i;
              out_texture->basisu_image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk));
              ++i;
            }
            else
            {
              i = cgltf_skip_json(tokens, i + 1);
            }
            if (i < 0)
            {
              return i;
            }
          }
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "EXT_texture_webp") == 0)
        {
          out_texture->has_webp = 1;
          ++i;
          CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
          int num_properties = tokens[i].size;
          ++i;

          for (int t = 0; t < num_properties; ++t)
          {
            CGLTF_CHECK_KEY(tokens[i]);

            if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0)
            {
              ++i;
              out_texture->webp_image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk));
              ++i;
            }
            else
            {
              i = cgltf_skip_json(tokens, i + 1);
            }
            if (i < 0)
            {
              return i;
            }
          }
        }
        else
        {
          i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_texture->extensions[out_texture->extensions_count++]));
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material* out_material)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  cgltf_fill_float_array(out_material->pbr_metallic_roughness.base_color_factor, 4, 1.0f);
  out_material->pbr_metallic_roughness.metallic_factor = 1.0f;
  out_material->pbr_metallic_roughness.roughness_factor = 1.0f;

  cgltf_fill_float_array(out_material->pbr_specular_glossiness.diffuse_factor, 4, 1.0f);
  cgltf_fill_float_array(out_material->pbr_specular_glossiness.specular_factor, 3, 1.0f);
  out_material->pbr_specular_glossiness.glossiness_factor = 1.0f;

  cgltf_fill_float_array(out_material->volume.attenuation_color, 3, 1.0f);
  out_material->volume.attenuation_distance = FLT_MAX;

  out_material->alpha_cutoff = 0.5f;

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_material->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "pbrMetallicRoughness") == 0)
    {
      out_material->has_pbr_metallic_roughness = 1;
      i = cgltf_parse_json_pbr_metallic_roughness(options, tokens, i + 1, json_chunk, &out_material->pbr_metallic_roughness);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveFactor") == 0)
    {
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_material->emissive_factor, 3);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "normalTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk,
        &out_material->normal_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "occlusionTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk,
        &out_material->occlusion_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveTexture") == 0)
    {
      i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk,
        &out_material->emissive_texture);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaMode") == 0)
    {
      ++i;
      if (cgltf_json_strcmp(tokens + i, json_chunk, "OPAQUE") == 0)
      {
        out_material->alpha_mode = cgltf_alpha_mode_opaque;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "MASK") == 0)
      {
        out_material->alpha_mode = cgltf_alpha_mode_mask;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "BLEND") == 0)
      {
        out_material->alpha_mode = cgltf_alpha_mode_blend;
      }
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaCutoff") == 0)
    {
      ++i;
      out_material->alpha_cutoff = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "doubleSided") == 0)
    {
      ++i;
      out_material->double_sided =
        cgltf_json_to_bool(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_material->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      ++i;

      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
      if (out_material->extensions)
      {
        return CGLTF_ERROR_JSON;
      }

      int extensions_size = tokens[i].size;
      ++i;
      out_material->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);
      out_material->extensions_count = 0;

      if (!out_material->extensions)
      {
        return CGLTF_ERROR_NOMEM;
      }

      for (int k = 0; k < extensions_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_pbrSpecularGlossiness") == 0)
        {
          out_material->has_pbr_specular_glossiness = 1;
          i = cgltf_parse_json_pbr_specular_glossiness(options, tokens, i + 1, json_chunk, &out_material->pbr_specular_glossiness);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_unlit") == 0)
        {
          out_material->unlit = 1;
          i = cgltf_skip_json(tokens, i + 1);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_clearcoat") == 0)
        {
          out_material->has_clearcoat = 1;
          i = cgltf_parse_json_clearcoat(options, tokens, i + 1, json_chunk, &out_material->clearcoat);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_ior") == 0)
        {
          out_material->has_ior = 1;
          i = cgltf_parse_json_ior(tokens, i + 1, json_chunk, &out_material->ior);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_specular") == 0)
        {
          out_material->has_specular = 1;
          i = cgltf_parse_json_specular(options, tokens, i + 1, json_chunk, &out_material->specular);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_transmission") == 0)
        {
          out_material->has_transmission = 1;
          i = cgltf_parse_json_transmission(options, tokens, i + 1, json_chunk, &out_material->transmission);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_volume") == 0)
        {
          out_material->has_volume = 1;
          i = cgltf_parse_json_volume(options, tokens, i + 1, json_chunk, &out_material->volume);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_sheen") == 0)
        {
          out_material->has_sheen = 1;
          i = cgltf_parse_json_sheen(options, tokens, i + 1, json_chunk, &out_material->sheen);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_emissive_strength") == 0)
        {
          out_material->has_emissive_strength = 1;
          i = cgltf_parse_json_emissive_strength(tokens, i + 1, json_chunk, &out_material->emissive_strength);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_iridescence") == 0)
        {
          out_material->has_iridescence = 1;
          i = cgltf_parse_json_iridescence(options, tokens, i + 1, json_chunk, &out_material->iridescence);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_diffuse_transmission") == 0)
        {
          out_material->has_diffuse_transmission = 1;
          i = cgltf_parse_json_diffuse_transmission(options, tokens, i + 1, json_chunk, &out_material->diffuse_transmission);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_anisotropy") == 0)
        {
          out_material->has_anisotropy = 1;
          i = cgltf_parse_json_anisotropy(options, tokens, i + 1, json_chunk, &out_material->anisotropy);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_dispersion") == 0)
        {
          out_material->has_dispersion = 1;
          i = cgltf_parse_json_dispersion(tokens, i + 1, json_chunk, &out_material->dispersion);
        }
        else
        {
          i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_material->extensions[out_material->extensions_count++]));
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_accessors(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_accessor), (void**)&out_data->accessors, &out_data->accessors_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->accessors_count; ++j)
  {
    i = cgltf_parse_json_accessor(options, tokens, i, json_chunk, &out_data->accessors[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_materials(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material), (void**)&out_data->materials, &out_data->materials_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->materials_count; ++j)
  {
    i = cgltf_parse_json_material(options, tokens, i, json_chunk, &out_data->materials[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_images(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_image), (void**)&out_data->images, &out_data->images_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->images_count; ++j)
  {
    i = cgltf_parse_json_image(options, tokens, i, json_chunk, &out_data->images[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_textures(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_texture), (void**)&out_data->textures, &out_data->textures_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->textures_count; ++j)
  {
    i = cgltf_parse_json_texture(options, tokens, i, json_chunk, &out_data->textures[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_samplers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_sampler), (void**)&out_data->samplers, &out_data->samplers_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->samplers_count; ++j)
  {
    i = cgltf_parse_json_sampler(options, tokens, i, json_chunk, &out_data->samplers[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_meshopt_compression(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_meshopt_compression* out_meshopt_compression)
{
  (void)options;
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "buffer") == 0)
    {
      ++i;
      out_meshopt_compression->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "byteOffset") == 0)
    {
      ++i;
      out_meshopt_compression->offset = cgltf_json_to_size(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "byteLength") == 0)
    {
      ++i;
      out_meshopt_compression->size = cgltf_json_to_size(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "byteStride") == 0)
    {
      ++i;
      out_meshopt_compression->stride = cgltf_json_to_size(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "count") == 0)
    {
      ++i;
      out_meshopt_compression->count = cgltf_json_to_size(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "mode") == 0)
    {
      ++i;
      if (cgltf_json_strcmp(tokens + i, json_chunk, "ATTRIBUTES") == 0)
      {
        out_meshopt_compression->mode = cgltf_meshopt_compression_mode_attributes;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "TRIANGLES") == 0)
      {
        out_meshopt_compression->mode = cgltf_meshopt_compression_mode_triangles;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "INDICES") == 0)
      {
        out_meshopt_compression->mode = cgltf_meshopt_compression_mode_indices;
      }
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "filter") == 0)
    {
      ++i;
      if (cgltf_json_strcmp(tokens + i, json_chunk, "NONE") == 0)
      {
        out_meshopt_compression->filter = cgltf_meshopt_compression_filter_none;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "OCTAHEDRAL") == 0)
      {
        out_meshopt_compression->filter = cgltf_meshopt_compression_filter_octahedral;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "QUATERNION") == 0)
      {
        out_meshopt_compression->filter = cgltf_meshopt_compression_filter_quaternion;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "EXPONENTIAL") == 0)
      {
        out_meshopt_compression->filter = cgltf_meshopt_compression_filter_exponential;
      }
      ++i;
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_buffer_view(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer_view* out_buffer_view)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer_view->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "buffer") == 0)
    {
      ++i;
      out_buffer_view->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "byteOffset") == 0)
    {
      ++i;
      out_buffer_view->offset =
        cgltf_json_to_size(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "byteLength") == 0)
    {
      ++i;
      out_buffer_view->size =
        cgltf_json_to_size(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "byteStride") == 0)
    {
      ++i;
      out_buffer_view->stride =
        cgltf_json_to_size(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "target") == 0)
    {
      ++i;
      int type = cgltf_json_to_int(tokens + i, json_chunk);
      switch (type)
      {
      case 34962:
        type = cgltf_buffer_view_type_vertices;
        break;
      case 34963:
        type = cgltf_buffer_view_type_indices;
        break;
      default:
        type = cgltf_buffer_view_type_invalid;
        break;
      }
      out_buffer_view->type = (cgltf_buffer_view_type)type;
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_buffer_view->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      ++i;

      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
      if (out_buffer_view->extensions)
      {
        return CGLTF_ERROR_JSON;
      }

      int extensions_size = tokens[i].size;
      out_buffer_view->extensions_count = 0;
      out_buffer_view->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);

      if (!out_buffer_view->extensions)
      {
        return CGLTF_ERROR_NOMEM;
      }

      ++i;
      for (int k = 0; k < extensions_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "EXT_meshopt_compression") == 0)
        {
          out_buffer_view->has_meshopt_compression = 1;
          i = cgltf_parse_json_meshopt_compression(options, tokens, i + 1, json_chunk, &out_buffer_view->meshopt_compression);
        }
        else
        {
          i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_buffer_view->extensions[out_buffer_view->extensions_count++]));
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_buffer_views(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer_view), (void**)&out_data->buffer_views, &out_data->buffer_views_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->buffer_views_count; ++j)
  {
    i = cgltf_parse_json_buffer_view(options, tokens, i, json_chunk, &out_data->buffer_views[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer* out_buffer)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "byteLength") == 0)
    {
      ++i;
      out_buffer->size =
        cgltf_json_to_size(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "uri") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->uri);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_buffer->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_buffer->extensions_count, &out_buffer->extensions);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_buffers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer), (void**)&out_data->buffers, &out_data->buffers_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->buffers_count; ++j)
  {
    i = cgltf_parse_json_buffer(options, tokens, i, json_chunk, &out_data->buffers[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_skin(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_skin* out_skin)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_skin->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "joints") == 0)
    {
      i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_skin->joints, &out_skin->joints_count);
      if (i < 0)
      {
        return i;
      }

      for (cgltf_size k = 0; k < out_skin->joints_count; ++k)
      {
        out_skin->joints[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk));
        ++i;
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "skeleton") == 0)
    {
      ++i;
      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);
      out_skin->skeleton = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "inverseBindMatrices") == 0)
    {
      ++i;
      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);
      out_skin->inverse_bind_matrices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_skin->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_skin->extensions_count, &out_skin->extensions);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_skins(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_skin), (void**)&out_data->skins, &out_data->skins_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->skins_count; ++j)
  {
    i = cgltf_parse_json_skin(options, tokens, i, json_chunk, &out_data->skins[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_camera* out_camera)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_camera->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "perspective") == 0)
    {
      ++i;

      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

      int data_size = tokens[i].size;
      ++i;

      if (out_camera->type != cgltf_camera_type_invalid)
      {
        return CGLTF_ERROR_JSON;
      }

      out_camera->type = cgltf_camera_type_perspective;

      for (int k = 0; k < data_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "aspectRatio") == 0)
        {
          ++i;
          out_camera->data.perspective.has_aspect_ratio = 1;
          out_camera->data.perspective.aspect_ratio = cgltf_json_to_float(tokens + i, json_chunk);
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "yfov") == 0)
        {
          ++i;
          out_camera->data.perspective.yfov = cgltf_json_to_float(tokens + i, json_chunk);
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "zfar") == 0)
        {
          ++i;
          out_camera->data.perspective.has_zfar = 1;
          out_camera->data.perspective.zfar = cgltf_json_to_float(tokens + i, json_chunk);
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "znear") == 0)
        {
          ++i;
          out_camera->data.perspective.znear = cgltf_json_to_float(tokens + i, json_chunk);
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
        {
          i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->data.perspective.extras);
        }
        else
        {
          i = cgltf_skip_json(tokens, i + 1);
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "orthographic") == 0)
    {
      ++i;

      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

      int data_size = tokens[i].size;
      ++i;

      if (out_camera->type != cgltf_camera_type_invalid)
      {
        return CGLTF_ERROR_JSON;
      }

      out_camera->type = cgltf_camera_type_orthographic;

      for (int k = 0; k < data_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "xmag") == 0)
        {
          ++i;
          out_camera->data.orthographic.xmag = cgltf_json_to_float(tokens + i, json_chunk);
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "ymag") == 0)
        {
          ++i;
          out_camera->data.orthographic.ymag = cgltf_json_to_float(tokens + i, json_chunk);
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "zfar") == 0)
        {
          ++i;
          out_camera->data.orthographic.zfar = cgltf_json_to_float(tokens + i, json_chunk);
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "znear") == 0)
        {
          ++i;
          out_camera->data.orthographic.znear = cgltf_json_to_float(tokens + i, json_chunk);
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
        {
          i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->data.orthographic.extras);
        }
        else
        {
          i = cgltf_skip_json(tokens, i + 1);
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_camera->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_camera->extensions_count, &out_camera->extensions);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_cameras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_camera), (void**)&out_data->cameras, &out_data->cameras_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->cameras_count; ++j)
  {
    i = cgltf_parse_json_camera(options, tokens, i, json_chunk, &out_data->cameras[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_light(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_light* out_light)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  out_light->color[0] = 1.f;
  out_light->color[1] = 1.f;
  out_light->color[2] = 1.f;
  out_light->intensity = 1.f;

  out_light->spot_inner_cone_angle = 0.f;
  out_light->spot_outer_cone_angle = 3.1415926535f / 4.0f;

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_light->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "color") == 0)
    {
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_light->color, 3);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "intensity") == 0)
    {
      ++i;
      out_light->intensity = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "type") == 0)
    {
      ++i;
      if (cgltf_json_strcmp(tokens + i, json_chunk, "directional") == 0)
      {
        out_light->type = cgltf_light_type_directional;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "point") == 0)
      {
        out_light->type = cgltf_light_type_point;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "spot") == 0)
      {
        out_light->type = cgltf_light_type_spot;
      }
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "range") == 0)
    {
      ++i;
      out_light->range = cgltf_json_to_float(tokens + i, json_chunk);
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "spot") == 0)
    {
      ++i;

      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

      int data_size = tokens[i].size;
      ++i;

      for (int k = 0; k < data_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "innerConeAngle") == 0)
        {
          ++i;
          out_light->spot_inner_cone_angle = cgltf_json_to_float(tokens + i, json_chunk);
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "outerConeAngle") == 0)
        {
          ++i;
          out_light->spot_outer_cone_angle = cgltf_json_to_float(tokens + i, json_chunk);
          ++i;
        }
        else
        {
          i = cgltf_skip_json(tokens, i + 1);
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_light->extras);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_lights(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_light), (void**)&out_data->lights, &out_data->lights_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->lights_count; ++j)
  {
    i = cgltf_parse_json_light(options, tokens, i, json_chunk, &out_data->lights[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_node* out_node)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  out_node->rotation[3] = 1.0f;
  out_node->scale[0] = 1.0f;
  out_node->scale[1] = 1.0f;
  out_node->scale[2] = 1.0f;
  out_node->matrix[0] = 1.0f;
  out_node->matrix[5] = 1.0f;
  out_node->matrix[10] = 1.0f;
  out_node->matrix[15] = 1.0f;

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_node->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "children") == 0)
    {
      i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_node->children, &out_node->children_count);
      if (i < 0)
      {
        return i;
      }

      for (cgltf_size k = 0; k < out_node->children_count; ++k)
      {
        out_node->children[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk));
        ++i;
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "mesh") == 0)
    {
      ++i;
      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);
      out_node->mesh = CGLTF_PTRINDEX(cgltf_mesh, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "skin") == 0)
    {
      ++i;
      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);
      out_node->skin = CGLTF_PTRINDEX(cgltf_skin, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "camera") == 0)
    {
      ++i;
      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);
      out_node->camera = CGLTF_PTRINDEX(cgltf_camera, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "translation") == 0)
    {
      out_node->has_translation = 1;
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->translation, 3);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "rotation") == 0)
    {
      out_node->has_rotation = 1;
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->rotation, 4);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0)
    {
      out_node->has_scale = 1;
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->scale, 3);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "matrix") == 0)
    {
      out_node->has_matrix = 1;
      i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->matrix, 16);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0)
    {
      i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_node->weights, &out_node->weights_count);
      if (i < 0)
      {
        return i;
      }

      i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_node->weights, (int)out_node->weights_count);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_node->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      ++i;

      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
      if (out_node->extensions)
      {
        return CGLTF_ERROR_JSON;
      }

      int extensions_size = tokens[i].size;
      out_node->extensions_count = 0;
      out_node->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);

      if (!out_node->extensions)
      {
        return CGLTF_ERROR_NOMEM;
      }

      ++i;

      for (int k = 0; k < extensions_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_lights_punctual") == 0)
        {
          ++i;

          CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

          int data_size = tokens[i].size;
          ++i;

          for (int m = 0; m < data_size; ++m)
          {
            CGLTF_CHECK_KEY(tokens[i]);

            if (cgltf_json_strcmp(tokens + i, json_chunk, "light") == 0)
            {
              ++i;
              CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE);
              out_node->light = CGLTF_PTRINDEX(cgltf_light, cgltf_json_to_int(tokens + i, json_chunk));
              ++i;
            }
            else
            {
              i = cgltf_skip_json(tokens, i + 1);
            }

            if (i < 0)
            {
              return i;
            }
          }
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "EXT_mesh_gpu_instancing") == 0)
        {
          out_node->has_mesh_gpu_instancing = 1;
          i = cgltf_parse_json_mesh_gpu_instancing(options, tokens, i + 1, json_chunk, &out_node->mesh_gpu_instancing);
        }
        else
        {
          i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_node->extensions[out_node->extensions_count++]));
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_nodes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_node), (void**)&out_data->nodes, &out_data->nodes_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->nodes_count; ++j)
  {
    i = cgltf_parse_json_node(options, tokens, i, json_chunk, &out_data->nodes[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_scene(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_scene* out_scene)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_scene->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "nodes") == 0)
    {
      i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_scene->nodes, &out_scene->nodes_count);
      if (i < 0)
      {
        return i;
      }

      for (cgltf_size k = 0; k < out_scene->nodes_count; ++k)
      {
        out_scene->nodes[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk));
        ++i;
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_scene->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_scene->extensions_count, &out_scene->extensions);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_scenes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_scene), (void**)&out_data->scenes, &out_data->scenes_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->scenes_count; ++j)
  {
    i = cgltf_parse_json_scene(options, tokens, i, json_chunk, &out_data->scenes[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_animation_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_sampler* out_sampler)
{
  (void)options;
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "input") == 0)
    {
      ++i;
      out_sampler->input = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "output") == 0)
    {
      ++i;
      out_sampler->output = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "interpolation") == 0)
    {
      ++i;
      if (cgltf_json_strcmp(tokens + i, json_chunk, "LINEAR") == 0)
      {
        out_sampler->interpolation = cgltf_interpolation_type_linear;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "STEP") == 0)
      {
        out_sampler->interpolation = cgltf_interpolation_type_step;
      }
      else if (cgltf_json_strcmp(tokens + i, json_chunk, "CUBICSPLINE") == 0)
      {
        out_sampler->interpolation = cgltf_interpolation_type_cubic_spline;
      }
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_sampler->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_animation_channel(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_channel* out_channel)
{
  (void)options;
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "sampler") == 0)
    {
      ++i;
      out_channel->sampler = CGLTF_PTRINDEX(cgltf_animation_sampler, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "target") == 0)
    {
      ++i;

      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

      int target_size = tokens[i].size;
      ++i;

      for (int k = 0; k < target_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "node") == 0)
        {
          ++i;
          out_channel->target_node = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk));
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "path") == 0)
        {
          ++i;
          if (cgltf_json_strcmp(tokens + i, json_chunk, "translation") == 0)
          {
            out_channel->target_path = cgltf_animation_path_type_translation;
          }
          else if (cgltf_json_strcmp(tokens + i, json_chunk, "rotation") == 0)
          {
            out_channel->target_path = cgltf_animation_path_type_rotation;
          }
          else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0)
          {
            out_channel->target_path = cgltf_animation_path_type_scale;
          }
          else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0)
          {
            out_channel->target_path = cgltf_animation_path_type_weights;
          }
          ++i;
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
        {
          i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_channel->extras);
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
        {
          i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_channel->extensions_count, &out_channel->extensions);
        }
        else
        {
          i = cgltf_skip_json(tokens, i + 1);
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_animation(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation* out_animation)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_animation->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "samplers") == 0)
    {
      i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_sampler), (void**)&out_animation->samplers, &out_animation->samplers_count);
      if (i < 0)
      {
        return i;
      }

      for (cgltf_size k = 0; k < out_animation->samplers_count; ++k)
      {
        i = cgltf_parse_json_animation_sampler(options, tokens, i, json_chunk, &out_animation->samplers[k]);
        if (i < 0)
        {
          return i;
        }
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "channels") == 0)
    {
      i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_channel), (void**)&out_animation->channels, &out_animation->channels_count);
      if (i < 0)
      {
        return i;
      }

      for (cgltf_size k = 0; k < out_animation->channels_count; ++k)
      {
        i = cgltf_parse_json_animation_channel(options, tokens, i, json_chunk, &out_animation->channels[k]);
        if (i < 0)
        {
          return i;
        }
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_animation->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_animation->extensions_count, &out_animation->extensions);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_animations(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_animation), (void**)&out_data->animations, &out_data->animations_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->animations_count; ++j)
  {
    i = cgltf_parse_json_animation(options, tokens, i, json_chunk, &out_data->animations[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_variant(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material_variant* out_variant)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_variant->name);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_variant->extras);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

static int cgltf_parse_json_variants(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material_variant), (void**)&out_data->variants, &out_data->variants_count);
  if (i < 0)
  {
    return i;
  }

  for (cgltf_size j = 0; j < out_data->variants_count; ++j)
  {
    i = cgltf_parse_json_variant(options, tokens, i, json_chunk, &out_data->variants[j]);
    if (i < 0)
    {
      return i;
    }
  }
  return i;
}

static int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_asset* out_asset)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "copyright") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->copyright);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "generator") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->generator);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "version") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->version);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "minVersion") == 0)
    {
      i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->min_version);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_asset->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_asset->extensions_count, &out_asset->extensions);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  if (out_asset->version && CGLTF_ATOF(out_asset->version) < 2)
  {
    return CGLTF_ERROR_LEGACY;
  }

  return i;
}

cgltf_size cgltf_num_components(cgltf_type type) {
  switch (type)
  {
  case cgltf_type_vec2:
    return 2;
  case cgltf_type_vec3:
    return 3;
  case cgltf_type_vec4:
    return 4;
  case cgltf_type_mat2:
    return 4;
  case cgltf_type_mat3:
    return 9;
  case cgltf_type_mat4:
    return 16;
  case cgltf_type_invalid:
  case cgltf_type_scalar:
  default:
    return 1;
  }
}

cgltf_size cgltf_component_size(cgltf_component_type component_type) {
  switch (component_type)
  {
  case cgltf_component_type_r_8:
  case cgltf_component_type_r_8u:
    return 1;
  case cgltf_component_type_r_16:
  case cgltf_component_type_r_16u:
    return 2;
  case cgltf_component_type_r_32u:
  case cgltf_component_type_r_32f:
    return 4;
  case cgltf_component_type_invalid:
  default:
    return 0;
  }
}

cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type)
{
  cgltf_size component_size = cgltf_component_size(component_type);
  if (type == cgltf_type_mat2 && component_size == 1)
  {
    return 8 * component_size;
  }
  else if (type == cgltf_type_mat3 && (component_size == 1 || component_size == 2))
  {
    return 12 * component_size;
  }
  return component_size * cgltf_num_components(type);
}

static int cgltf_fixup_pointers(cgltf_data* out_data);

static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data)
{
  CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

  int size = tokens[i].size;
  ++i;

  for (int j = 0; j < size; ++j)
  {
    CGLTF_CHECK_KEY(tokens[i]);

    if (cgltf_json_strcmp(tokens + i, json_chunk, "asset") == 0)
    {
      i = cgltf_parse_json_asset(options, tokens, i + 1, json_chunk, &out_data->asset);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "meshes") == 0)
    {
      i = cgltf_parse_json_meshes(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "accessors") == 0)
    {
      i = cgltf_parse_json_accessors(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferViews") == 0)
    {
      i = cgltf_parse_json_buffer_views(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "buffers") == 0)
    {
      i = cgltf_parse_json_buffers(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "materials") == 0)
    {
      i = cgltf_parse_json_materials(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "images") == 0)
    {
      i = cgltf_parse_json_images(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "textures") == 0)
    {
      i = cgltf_parse_json_textures(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "samplers") == 0)
    {
      i = cgltf_parse_json_samplers(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "skins") == 0)
    {
      i = cgltf_parse_json_skins(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "cameras") == 0)
    {
      i = cgltf_parse_json_cameras(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "nodes") == 0)
    {
      i = cgltf_parse_json_nodes(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "scenes") == 0)
    {
      i = cgltf_parse_json_scenes(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "scene") == 0)
    {
      ++i;
      out_data->scene = CGLTF_PTRINDEX(cgltf_scene, cgltf_json_to_int(tokens + i, json_chunk));
      ++i;
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "animations") == 0)
    {
      i = cgltf_parse_json_animations(options, tokens, i + 1, json_chunk, out_data);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
    {
      i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &out_data->extras);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0)
    {
      ++i;

      CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
      if (out_data->data_extensions)
      {
        return CGLTF_ERROR_JSON;
      }

      int extensions_size = tokens[i].size;
      out_data->data_extensions_count = 0;
      out_data->data_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size);

      if (!out_data->data_extensions)
      {
        return CGLTF_ERROR_NOMEM;
      }

      ++i;

      for (int k = 0; k < extensions_size; ++k)
      {
        CGLTF_CHECK_KEY(tokens[i]);

        if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_lights_punctual") == 0)
        {
          ++i;

          CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

          int data_size = tokens[i].size;
          ++i;

          for (int m = 0; m < data_size; ++m)
          {
            CGLTF_CHECK_KEY(tokens[i]);

            if (cgltf_json_strcmp(tokens + i, json_chunk, "lights") == 0)
            {
              i = cgltf_parse_json_lights(options, tokens, i + 1, json_chunk, out_data);
            }
            else
            {
              i = cgltf_skip_json(tokens, i + 1);
            }

            if (i < 0)
            {
              return i;
            }
          }
        }
        else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_variants") == 0)
        {
          ++i;

          CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);

          int data_size = tokens[i].size;
          ++i;

          for (int m = 0; m < data_size; ++m)
          {
            CGLTF_CHECK_KEY(tokens[i]);

            if (cgltf_json_strcmp(tokens + i, json_chunk, "variants") == 0)
            {
              i = cgltf_parse_json_variants(options, tokens, i + 1, json_chunk, out_data);
            }
            else
            {
              i = cgltf_skip_json(tokens, i + 1);
            }

            if (i < 0)
            {
              return i;
            }
          }
        }
        else
        {
          i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_data->data_extensions[out_data->data_extensions_count++]));
        }

        if (i < 0)
        {
          return i;
        }
      }
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsUsed") == 0)
    {
      i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_used, &out_data->extensions_used_count);
    }
    else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsRequired") == 0)
    {
      i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_required, &out_data->extensions_required_count);
    }
    else
    {
      i = cgltf_skip_json(tokens, i + 1);
    }

    if (i < 0)
    {
      return i;
    }
  }

  return i;
}

cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data)
{
  jsmn_parser parser = { 0, 0, 0 };

  if (options->json_token_count == 0)
  {
    int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, NULL, 0);

    if (token_count <= 0)
    {
      return cgltf_result_invalid_json;
    }

    options->json_token_count = token_count;
  }

  jsmntok_t* tokens = (jsmntok_t*)options->memory.alloc_func(options->memory.user_data, sizeof(jsmntok_t) * (options->json_token_count + 1));

  if (!tokens)
  {
    return cgltf_result_out_of_memory;
  }

  jsmn_init(&parser);

  int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, tokens, options->json_token_count);

  if (token_count <= 0)
  {
    options->memory.free_func(options->memory.user_data, tokens);
    return cgltf_result_invalid_json;
  }

  // this makes sure that we always have an UNDEFINED token at the end of the stream
  // for invalid JSON inputs this makes sure we don't perform out of bound reads of token data
  tokens[token_count].type = JSMN_UNDEFINED;

  cgltf_data* data = (cgltf_data*)options->memory.alloc_func(options->memory.user_data, sizeof(cgltf_data));

  if (!data)
  {
    options->memory.free_func(options->memory.user_data, tokens);
    return cgltf_result_out_of_memory;
  }

  memset(data, 0, sizeof(cgltf_data));
  data->memory = options->memory;
  data->file = options->file;

  int i = cgltf_parse_json_root(options, tokens, 0, json_chunk, data);

  options->memory.free_func(options->memory.user_data, tokens);

  if (i < 0)
  {
    cgltf_free(data);

    switch (i)
    {
    case CGLTF_ERROR_NOMEM: return cgltf_result_out_of_memory;
    case CGLTF_ERROR_LEGACY: return cgltf_result_legacy_gltf;
    default: return cgltf_result_invalid_gltf;
    }
  }

  if (cgltf_fixup_pointers(data) < 0)
  {
    cgltf_free(data);
    return cgltf_result_invalid_gltf;
  }

  data->json = (const char*)json_chunk;
  data->json_size = size;

  *out_data = data;

  return cgltf_result_success;
}

static int cgltf_fixup_pointers(cgltf_data* data)
{
  for (cgltf_size i = 0; i < data->meshes_count; ++i)
  {
    for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j)
    {
      CGLTF_PTRFIXUP(data->meshes[i].primitives[j].indices, data->accessors, data->accessors_count);
      CGLTF_PTRFIXUP(data->meshes[i].primitives[j].material, data->materials, data->materials_count);

      for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k)
      {
        CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].attributes[k].data, data->accessors, data->accessors_count);
      }

      for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k)
      {
        for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m)
        {
          CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].targets[k].attributes[m].data, data->accessors, data->accessors_count);
        }
      }

      if (data->meshes[i].primitives[j].has_draco_mesh_compression)
      {
        CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.buffer_view, data->buffer_views, data->buffer_views_count);
        for (cgltf_size m = 0; m < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++m)
        {
          CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.attributes[m].data, data->accessors, data->accessors_count);
        }
      }

      for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k)
      {
        CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].mappings[k].material, data->materials, data->materials_count);
      }
    }
  }

  for (cgltf_size i = 0; i < data->accessors_count; ++i)
  {
    CGLTF_PTRFIXUP(data->accessors[i].buffer_view, data->buffer_views, data->buffer_views_count);

    if (data->accessors[i].is_sparse)
    {
      CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.indices_buffer_view, data->buffer_views, data->buffer_views_count);
      CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.values_buffer_view, data->buffer_views, data->buffer_views_count);
    }

    if (data->accessors[i].buffer_view)
    {
      data->accessors[i].stride = data->accessors[i].buffer_view->stride;
    }

    if (data->accessors[i].stride == 0)
    {
      data->accessors[i].stride = cgltf_calc_size(data->accessors[i].type, data->accessors[i].component_type);
    }
  }

  for (cgltf_size i = 0; i < data->textures_count; ++i)
  {
    CGLTF_PTRFIXUP(data->textures[i].image, data->images, data->images_count);
    CGLTF_PTRFIXUP(data->textures[i].basisu_image, data->images, data->images_count);
    CGLTF_PTRFIXUP(data->textures[i].webp_image, data->images, data->images_count);
    CGLTF_PTRFIXUP(data->textures[i].sampler, data->samplers, data->samplers_count);
  }

  for (cgltf_size i = 0; i < data->images_count; ++i)
  {
    CGLTF_PTRFIXUP(data->images[i].buffer_view, data->buffer_views, data->buffer_views_count);
  }

  for (cgltf_size i = 0; i < data->materials_count; ++i)
  {
    CGLTF_PTRFIXUP(data->materials[i].normal_texture.texture, data->textures, data->textures_count);
    CGLTF_PTRFIXUP(data->materials[i].emissive_texture.texture, data->textures, data->textures_count);
    CGLTF_PTRFIXUP(data->materials[i].occlusion_texture.texture, data->textures, data->textures_count);

    CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.base_color_texture.texture, data->textures, data->textures_count);
    CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture, data->textures, data->textures_count);

    CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.diffuse_texture.texture, data->textures, data->textures_count);
    CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.texture, data->textures, data->textures_count);

    CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_texture.texture, data->textures, data->textures_count);
    CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_roughness_texture.texture, data->textures, data->textures_count);
    CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_normal_texture.texture, data->textures, data->textures_count);

    CGLTF_PTRFIXUP(data->materials[i].specular.specular_texture.texture, data->textures, data->textures_count);
    CGLTF_PTRFIXUP(data->materials[i].specular.specular_color_texture.texture, data->textures, data->textures_count);

    CGLTF_PTRFIXUP(data->materials[i].transmission.transmission_texture.texture, data->textures, data->textures_count);

    CGLTF_PTRFIXUP(data->materials[i].volume.thickness_texture.texture, data->textures, data->textures_count);

    CGLTF_PTRFIXUP(data->materials[i].sheen.sheen_color_texture.texture, data->textures, data->textures_count);
    CGLTF_PTRFIXUP(data->materials[i].sheen.sheen_roughness_texture.texture, data->textures, data->textures_count);

    CGLTF_PTRFIXUP(data->materials[i].iridescence.iridescence_texture.texture, data->textures, data->textures_count);
    CGLTF_PTRFIXUP(data->materials[i].iridescence.iridescence_thickness_texture.texture, data->textures, data->textures_count);

    CGLTF_PTRFIXUP(data->materials[i].diffuse_transmission.diffuse_transmission_texture.texture, data->textures, data->textures_count);
    CGLTF_PTRFIXUP(data->materials[i].diffuse_transmission.diffuse_transmission_color_texture.texture, data->textures, data->textures_count);

    CGLTF_PTRFIXUP(data->materials[i].anisotropy.anisotropy_texture.texture, data->textures, data->textures_count);
  }

  for (cgltf_size i = 0; i < data->buffer_views_count; ++i)
  {
    CGLTF_PTRFIXUP_REQ(data->buffer_views[i].buffer, data->buffers, data->buffers_count);

    if (data->buffer_views[i].has_meshopt_compression)
    {
      CGLTF_PTRFIXUP_REQ(data->buffer_views[i].meshopt_compression.buffer, data->buffers, data->buffers_count);
    }
  }

  for (cgltf_size i = 0; i < data->skins_count; ++i)
  {
    for (cgltf_size j = 0; j < data->skins[i].joints_count; ++j)
    {
      CGLTF_PTRFIXUP_REQ(data->skins[i].joints[j], data->nodes, data->nodes_count);
    }

    CGLTF_PTRFIXUP(data->skins[i].skeleton, data->nodes, data->nodes_count);
    CGLTF_PTRFIXUP(data->skins[i].inverse_bind_matrices, data->accessors, data->accessors_count);
  }

  for (cgltf_size i = 0; i < data->nodes_count; ++i)
  {
    for (cgltf_size j = 0; j < data->nodes[i].children_count; ++j)
    {
      CGLTF_PTRFIXUP_REQ(data->nodes[i].children[j], data->nodes, data->nodes_count);

      if (data->nodes[i].children[j]->parent)
      {
        return CGLTF_ERROR_JSON;
      }

      data->nodes[i].children[j]->parent = &data->nodes[i];
    }

    CGLTF_PTRFIXUP(data->nodes[i].mesh, data->meshes, data->meshes_count);
    CGLTF_PTRFIXUP(data->nodes[i].skin, data->skins, data->skins_count);
    CGLTF_PTRFIXUP(data->nodes[i].camera, data->cameras, data->cameras_count);
    CGLTF_PTRFIXUP(data->nodes[i].light, data->lights, data->lights_count);

    if (data->nodes[i].has_mesh_gpu_instancing)
    {
      for (cgltf_size m = 0; m < data->nodes[i].mesh_gpu_instancing.attributes_count; ++m)
      {
        CGLTF_PTRFIXUP_REQ(data->nodes[i].mesh_gpu_instancing.attributes[m].data, data->accessors, data->accessors_count);
      }
    }
  }

  for (cgltf_size i = 0; i < data->scenes_count; ++i)
  {
    for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j)
    {
      CGLTF_PTRFIXUP_REQ(data->scenes[i].nodes[j], data->nodes, data->nodes_count);

      if (data->scenes[i].nodes[j]->parent)
      {
        return CGLTF_ERROR_JSON;
      }
    }
  }

  CGLTF_PTRFIXUP(data->scene, data->scenes, data->scenes_count);

  for (cgltf_size i = 0; i < data->animations_count; ++i)
  {
    for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j)
    {
      CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].input, data->accessors, data->accessors_count);
      CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].output, data->accessors, data->accessors_count);
    }

    for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j)
    {
      CGLTF_PTRFIXUP_REQ(data->animations[i].channels[j].sampler, data->animations[i].samplers, data->animations[i].samplers_count);
      CGLTF_PTRFIXUP(data->animations[i].channels[j].target_node, data->nodes, data->nodes_count);
    }
  }

  return 0;
}

/*
 * -- jsmn.c start --
 * Source: https://github.com/zserge/jsmn
 * License: MIT
 *
 * Copyright (c) 2010 Serge A. Zaitsev

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

 /**
  * Allocates a fresh unused token from the token pull.
  */
static jsmntok_t* jsmn_alloc_token(jsmn_parser* parser,
  jsmntok_t* tokens, size_t num_tokens) {
  jsmntok_t* tok;
  if (parser->toknext >= num_tokens) {
    return NULL;
  }
  tok = &tokens[parser->toknext++];
  tok->start = tok->end = -1;
  tok->size = 0;
#ifdef JSMN_PARENT_LINKS
  tok->parent = -1;
#endif
  return tok;
}

/**
 * Fills token type and boundaries.
 */
static void jsmn_fill_token(jsmntok_t* token, jsmntype_t type,
  ptrdiff_t start, ptrdiff_t end) {
  token->type = type;
  token->start = start;
  token->end = end;
  token->size = 0;
}

/**
 * Fills next available token with JSON primitive.
 */
static int jsmn_parse_primitive(jsmn_parser* parser, const char* js,
  size_t len, jsmntok_t* tokens, size_t num_tokens) {
  jsmntok_t* token;
  ptrdiff_t start;

  start = parser->pos;

  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
    switch (js[parser->pos]) {
#ifndef JSMN_STRICT
      /* In strict mode primitive must be followed by "," or "}" or "]" */
    case ':':
#endif
    case '\t': case '\r': case '\n': case ' ':
    case ',': case ']': case '}':
      goto found;
    }
    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
      parser->pos = start;
      return JSMN_ERROR_INVAL;
    }
  }
#ifdef JSMN_STRICT
  /* In strict mode primitive must be followed by a comma/object/array */
  parser->pos = start;
  return JSMN_ERROR_PART;
#endif

found:
  if (tokens == NULL) {
    parser->pos--;
    return 0;
  }
  token = jsmn_alloc_token(parser, tokens, num_tokens);
  if (token == NULL) {
    parser->pos = start;
    return JSMN_ERROR_NOMEM;
  }
  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
#ifdef JSMN_PARENT_LINKS
  token->parent = parser->toksuper;
#endif
  parser->pos--;
  return 0;
}

/**
 * Fills next token with JSON string.
 */
static int jsmn_parse_string(jsmn_parser* parser, const char* js,
  size_t len, jsmntok_t* tokens, size_t num_tokens) {
  jsmntok_t* token;

  ptrdiff_t start = parser->pos;

  parser->pos++;

  /* Skip starting quote */
  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
    char c = js[parser->pos];

    /* Quote: end of string */
    if (c == '\"') {
      if (tokens == NULL) {
        return 0;
      }
      token = jsmn_alloc_token(parser, tokens, num_tokens);
      if (token == NULL) {
        parser->pos = start;
        return JSMN_ERROR_NOMEM;
      }
      jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
#ifdef JSMN_PARENT_LINKS
      token->parent = parser->toksuper;
#endif
      return 0;
    }

    /* Backslash: Quoted symbol expected */
    if (c == '\\' && parser->pos + 1 < len) {
      int i;
      parser->pos++;
      switch (js[parser->pos]) {
        /* Allowed escaped symbols */
      case '\"': case '/': case '\\': case 'b':
      case 'f': case 'r': case 'n': case 't':
        break;
        /* Allows escaped symbol \uXXXX */
      case 'u':
        parser->pos++;
        for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
          /* If it isn't a hex character we have an error */
          if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
            (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
            (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
            parser->pos = start;
            return JSMN_ERROR_INVAL;
          }
          parser->pos++;
        }
        parser->pos--;
        break;
        /* Unexpected symbol */
      default:
        parser->pos = start;
        return JSMN_ERROR_INVAL;
      }
    }
  }
  parser->pos = start;
  return JSMN_ERROR_PART;
}

/**
 * Parse JSON string and fill tokens.
 */
static int jsmn_parse(jsmn_parser* parser, const char* js, size_t len,
  jsmntok_t* tokens, size_t num_tokens) {
  int r;
  int i;
  jsmntok_t* token;
  int count = parser->toknext;

  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
    char c;
    jsmntype_t type;

    c = js[parser->pos];
    switch (c) {
    case '{': case '[':
      count++;
      if (tokens == NULL) {
        break;
      }
      token = jsmn_alloc_token(parser, tokens, num_tokens);
      if (token == NULL)
        return JSMN_ERROR_NOMEM;
      if (parser->toksuper != -1) {
        tokens[parser->toksuper].size++;
#ifdef JSMN_PARENT_LINKS
        token->parent = parser->toksuper;
#endif
      }
      token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
      token->start = parser->pos;
      parser->toksuper = parser->toknext - 1;
      break;
    case '}': case ']':
      if (tokens == NULL)
        break;
      type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
      if (parser->toknext < 1) {
        return JSMN_ERROR_INVAL;
      }
      token = &tokens[parser->toknext - 1];
      for (;;) {
        if (token->start != -1 && token->end == -1) {
          if (token->type != type) {
            return JSMN_ERROR_INVAL;
          }
          token->end = parser->pos + 1;
          parser->toksuper = token->parent;
          break;
        }
        if (token->parent == -1) {
          if (token->type != type || parser->toksuper == -1) {
            return JSMN_ERROR_INVAL;
          }
          break;
        }
        token = &tokens[token->parent];
      }
#else
      for (i = parser->toknext - 1; i >= 0; i--) {
        token = &tokens[i];
        if (token->start != -1 && token->end == -1) {
          if (token->type != type) {
            return JSMN_ERROR_INVAL;
          }
          parser->toksuper = -1;
          token->end = parser->pos + 1;
          break;
        }
      }
      /* Error if unmatched closing bracket */
      if (i == -1) return JSMN_ERROR_INVAL;
      for (; i >= 0; i--) {
        token = &tokens[i];
        if (token->start != -1 && token->end == -1) {
          parser->toksuper = i;
          break;
        }
      }
#endif
      break;
    case '\"':
      r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
      if (r < 0) return r;
      count++;
      if (parser->toksuper != -1 && tokens != NULL)
        tokens[parser->toksuper].size++;
      break;
    case '\t': case '\r': case '\n': case ' ':
      break;
    case ':':
      parser->toksuper = parser->toknext - 1;
      break;
    case ',':
      if (tokens != NULL && parser->toksuper != -1 &&
        tokens[parser->toksuper].type != JSMN_ARRAY &&
        tokens[parser->toksuper].type != JSMN_OBJECT) {
#ifdef JSMN_PARENT_LINKS
        parser->toksuper = tokens[parser->toksuper].parent;
#else
        for (i = parser->toknext - 1; i >= 0; i--) {
          if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
            if (tokens[i].start != -1 && tokens[i].end == -1) {
              parser->toksuper = i;
              break;
            }
          }
        }
#endif
      }
      break;
#ifdef JSMN_STRICT
      /* In strict mode primitives are: numbers and booleans */
    case '-': case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
    case 't': case 'f': case 'n':
      /* And they must not be keys of the object */
      if (tokens != NULL && parser->toksuper != -1) {
        jsmntok_t* t = &tokens[parser->toksuper];
        if (t->type == JSMN_OBJECT ||
          (t->type == JSMN_STRING && t->size != 0)) {
          return JSMN_ERROR_INVAL;
        }
      }
#else
      /* In non-strict mode every unquoted value is a primitive */
    default:
#endif
      r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
      if (r < 0) return r;
      count++;
      if (parser->toksuper != -1 && tokens != NULL)
        tokens[parser->toksuper].size++;
      break;

#ifdef JSMN_STRICT
      /* Unexpected char in strict mode */
    default:
      return JSMN_ERROR_INVAL;
#endif
    }
  }

  if (tokens != NULL) {
    for (i = parser->toknext - 1; i >= 0; i--) {
      /* Unmatched opened object or array */
      if (tokens[i].start != -1 && tokens[i].end == -1) {
        return JSMN_ERROR_PART;
      }
    }
  }

  return count;
}

/**
 * Creates a new parser based over a given  buffer with an array of tokens
 * available.
 */
static void jsmn_init(jsmn_parser* parser) {
  parser->pos = 0;
  parser->toknext = 0;
  parser->toksuper = -1;
}
/*
 * -- jsmn.c end --
 */

#endif /* #ifdef CGLTF_IMPLEMENTATION */

 /* cgltf is distributed under MIT license:
  *
  * Copyright (c) 2018-2021 Johannes Kuhlmann

  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:

  * The above copyright notice and this permission notice shall be included in all
  * copies or substantial portions of the Software.

  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  * SOFTWARE.
  */
  //}
#endif

int main(int argc, char* argv[])
{
  if (argc != 2)
  {
    puts(R"(Usage: ./resPacker pathToFile.ext
This program is used to streamline development of Shr3D by doing file transformations.
Example: It transform the raw pixel data of a PNG into a C array.
The following extensions are handled differently:
-PNG files will output the raw Pixel data.
-OBJ files will output the vertex + uv data.
-GLB files will output .stage file used for 3d environments in Shr3D.)");
    return 0;
  }

  FILE* inFile = fopen(argv[1], "rb");
  if (inFile == nullptr)
  {
    puts(R"(The input file does not exist.)");
    return -1;
  }

  const size_t argv1Len = strlen(argv[1]);
  const char* dotExtension = &argv[1][argv1Len - 4];

  if (strcmp(".obj", dotExtension) == 0)
  {
    const fastObjMesh* m = decodeOBJ(argv[1]);

    if (m->group_count != 1)
    {
      puts("Error: Expected one model in the obj file.");
      return -3;
    }

#if 1
    {
      char outFilepath[512];
      strcpy(outFilepath, argv[1]);
      {
        outFilepath[argv1Len] = '.';
        outFilepath[argv1Len + 1] = 'c';
        outFilepath[argv1Len + 2] = '\0';
      }

      FILE* outFile = fopen(outFilepath, "wb");

      fprintf(outFile, "const f32 vertecies[%u] = {\n", m->groups[0].face_count * 15);

      unsigned int idx = 0;
      for (unsigned int i = 0; i < m->groups[0].face_count - 1; ++i)
      {
        const unsigned int faceVerticies = m->face_vertices[m->groups[0].face_offset + i];
        if (faceVerticies != 3)
        {
          puts("Error: found face that has more than 3 vertecies. Did you forget to triangulate the faces on the obj export?");
          return -2;
        }
        {
          const fastObjIndex mi0 = m->indices[m->groups[0].index_offset + idx++];
          const fastObjIndex mi1 = m->indices[m->groups[0].index_offset + idx++];
          const fastObjIndex mi2 = m->indices[m->groups[0].index_offset + idx++];
          fprintf(outFile, "  %9f_f32,%9f_f32,%9f_f32,%9f_f32,%9f_f32,\n  %9f_f32,%9f_f32,%9f_f32,%9f_f32,%9f_f32,\n  %9f_f32,%9f_f32,%9f_f32,%9f_f32,%9f_f32,\n",
            m->positions[3 * mi0.p], m->positions[3 * mi0.p + 1], m->positions[3 * mi0.p + 2], m->texcoords[2 * mi0.t], m->texcoords[2 * mi0.t + 1],
            m->positions[3 * mi1.p], m->positions[3 * mi1.p + 1], m->positions[3 * mi1.p + 2], m->texcoords[2 * mi1.t], m->texcoords[2 * mi1.t + 1],
            m->positions[3 * mi2.p], m->positions[3 * mi2.p + 1], m->positions[3 * mi2.p + 2], m->texcoords[2 * mi2.t], m->texcoords[2 * mi2.t + 1]);
        }
      }
      const fastObjIndex mi0 = m->indices[m->groups[0].index_offset + idx++];
      const fastObjIndex mi1 = m->indices[m->groups[0].index_offset + idx++];
      const fastObjIndex mi2 = m->indices[m->groups[0].index_offset + idx++];
      fprintf(outFile, "  %9f_f32,%9f_f32,%9f_f32,%9f_f32,%9f_f32,\n  %9f_f32,%9f_f32,%9f_f32,%9f_f32,%9f_f32,\n  %9f_f32,%9f_f32,%9f_f32,%9f_f32,%9f_f32\n};\n",
        m->positions[3 * mi0.p], m->positions[3 * mi0.p + 1], m->positions[3 * mi0.p + 2], m->texcoords[2 * mi0.t], m->texcoords[2 * mi0.t + 1],
        m->positions[3 * mi1.p], m->positions[3 * mi1.p + 1], m->positions[3 * mi1.p + 2], m->texcoords[2 * mi1.t], m->texcoords[2 * mi1.t + 1],
        m->positions[3 * mi2.p], m->positions[3 * mi2.p + 1], m->positions[3 * mi2.p + 2], m->texcoords[2 * mi2.t], m->texcoords[2 * mi2.t + 1]);

      fclose(outFile);
    }
#endif

#if 1
    {
      char outFilepath[512];
      strcpy(outFilepath, argv[1]);
      {
        outFilepath[argv1Len] = '.';
        outFilepath[argv1Len + 1] = 'n';
        outFilepath[argv1Len + 2] = 'o';
        outFilepath[argv1Len + 3] = 'n';
        outFilepath[argv1Len + 4] = '-';
        outFilepath[argv1Len + 5] = 'i';
        outFilepath[argv1Len + 6] = 'n';
        outFilepath[argv1Len + 7] = 'd';
        outFilepath[argv1Len + 8] = 'e';
        outFilepath[argv1Len + 9] = 'x';
        outFilepath[argv1Len + 10] = 'e';
        outFilepath[argv1Len + 11] = 'd';
        outFilepath[argv1Len + 12] = '.';
        outFilepath[argv1Len + 13] = '3';
        outFilepath[argv1Len + 14] = 'd';
        outFilepath[argv1Len + 15] = '\0';
      }

      FILE* outFile = fopen(outFilepath, "wb");

      // write header
      {
        const uint32_t magic = 3199882682;
        fwrite(&magic, sizeof(magic), 1, outFile);
      }

      // write 3d file without indexed triangles
      {
        const uint32_t version = 0;
        fwrite(&version, sizeof(version), 1, outFile);
      }
      {
        const uint32_t vertexCount = 0;
        fwrite(&vertexCount, sizeof(vertexCount), 1, outFile);
      }
      {
        const uint32_t triangleCount = 0;
        fwrite(&triangleCount, sizeof(triangleCount), 1, outFile);
      }

      unsigned int idx = 0;
      for (unsigned int j = 0; j < m->groups[0].face_count; ++j)
      {
        unsigned int faceVerticies = m->face_vertices[m->groups[0].face_offset + j];
        if (faceVerticies != 3)
        {
          puts("Error: found face that has more than 3 vertecies. Did you forget to triangulate the faces on the obj export?");
          return -2;
        }
        for (unsigned int k = 0; k < 3; k++)
        {
          fastObjIndex mi = m->indices[m->groups[0].index_offset + idx];

          fwrite(&m->positions[3 * mi.p], sizeof(float), 3, outFile);
          fwrite(&m->texcoords[2 * mi.t], sizeof(float), 2, outFile);

          idx++;
        }
      }

      fseek(outFile, 8, SEEK_SET);
      fwrite(&idx, sizeof(idx), 1, outFile);
      fclose(outFile);
    }
#endif

#if 1
    {
      struct Vertex
      {
        float x, y, z, u, v;

        bool operator< (const Vertex& o) const
        {
          return std::tie(x, y, z, u, v) < std::tie(o.x, o.y, o.z, o.u, o.v);
        }
      };

      std::map<Vertex, unsigned int> uniqueVertices;
      std::vector<unsigned int> triangles;
      std::vector<Vertex> uniqueVerticesList;

      unsigned int idx = 0;
      for (unsigned int i = 0; i < m->groups[0].face_count; ++i)
      {
        unsigned int faceVerticies = m->face_vertices[m->groups[0].face_offset + i];
        if (faceVerticies != 3)
        {
          puts("Error: found face that has more than 3 vertecies. Did you forget to triangulate the faces on the obj export?");
          return -2;
        }
        for (unsigned int k = 0; k < 3; ++k)
        {
          fastObjIndex mi = m->indices[m->groups[0].index_offset + idx];

          const Vertex vertex{
            .x = m->positions[3 * mi.p],
            .y = m->positions[3 * mi.p + 1],
            .z = m->positions[3 * mi.p + 2],
            .u = m->texcoords[2 * mi.t],
            .v = m->texcoords[2 * mi.t + 1]
          };

          if (uniqueVertices.count(vertex) == 0)
          {
            const unsigned int vertIndex = unsigned int(uniqueVertices.size());

            uniqueVertices.insert({ vertex, vertIndex });
            uniqueVerticesList.push_back(vertex);
            triangles.push_back(vertIndex);
          }
          else
          {
            triangles.push_back(uniqueVertices[vertex]);
          }
          ++idx;
        }
      }

      char outFilepath[512];
      strcpy(outFilepath, argv[1]);
      {
        outFilepath[argv1Len] = '.';
        outFilepath[argv1Len + 1] = 'i';
        outFilepath[argv1Len + 2] = 'n';
        outFilepath[argv1Len + 3] = 'd';
        outFilepath[argv1Len + 4] = 'e';
        outFilepath[argv1Len + 5] = 'x';
        outFilepath[argv1Len + 6] = 'e';
        outFilepath[argv1Len + 7] = 'd';
        outFilepath[argv1Len + 8] = '.';
        outFilepath[argv1Len + 9] = 'c';
        outFilepath[argv1Len + 10] = '\0';
      }
      FILE* outFile = fopen(outFilepath, "wb");
      fprintf(outFile, "const f32 uniqueVertecies[%u] = {\n", uniqueVerticesList.size() * 5);

      for (size_t i = 0; i < uniqueVerticesList.size() - 1; ++i)
      {
        const Vertex& v = uniqueVerticesList[i];
        fprintf(outFile, "  %9f_f32,%9f_f32,%9f_f32,%9f_f32,%9f_f32,\n", v.x, v.y, v.z, v.u, v.v);
      }
      { // loop writes a ',' that we don't need on the last vertex
        const Vertex& v = uniqueVerticesList[uniqueVerticesList.size() - 1];
        fprintf(outFile, "  %9f_f32,%9f_f32,%9f_f32,%9f_f32,%9f_f32\n};\n\n", v.x, v.y, v.z, v.u, v.v);
      }

      fprintf(outFile, "const u32 triangles[%u] = {\n", triangles.size());
      for (size_t i = 0; i < triangles.size() - 3; i += 3)
      {
        fprintf(outFile, "  %5u,%5u,%5u,\n", triangles[i], triangles[i + 1], triangles[i + 2]);
      }
      {
        fprintf(outFile, "  %5u,%5u,%5u\n};\n", triangles[triangles.size() - 3], triangles[triangles.size() - 2], triangles[triangles.size() - 1]);
      }

      fclose(outFile);
    }
#endif

#if 1
    {
      char outFilepath[512];
      strcpy(outFilepath, argv[1]);
      {
        outFilepath[argv1Len] = '.';
        outFilepath[argv1Len + 1] = '3';
        outFilepath[argv1Len + 2] = 'd';
        outFilepath[argv1Len + 3] = '\0';
      }

      FILE* outFile = fopen(outFilepath, "wb");

      // write header
      {
        const uint32_t magic = 3199882682;
        fwrite(&magic, sizeof(magic), 1, outFile);
      }

      // write 3d file with indexed triangles
      {
        const uint32_t version = 1;
        fwrite(&version, sizeof(version), 1, outFile);
      }
      {
        const uint32_t vertexCount = 0;
        fwrite(&vertexCount, sizeof(vertexCount), 1, outFile);
      }
      {
        const uint32_t triangleCount = 0;
        fwrite(&triangleCount, sizeof(triangleCount), 1, outFile);
      }

      struct Vertex
      {
        float x, y, z, u, v;

        bool operator< (const Vertex& o) const
        {
          return std::tie(x, y, z, u, v) < std::tie(o.x, o.y, o.z, o.u, o.v);
        }
      };

      std::map<Vertex, unsigned int> uniqueVertices;
      std::vector<unsigned int> triangles;

      unsigned int idx = 0;
      for (unsigned int i = 0; i < m->groups[0].face_count; ++i)
      {
        unsigned int faceVerticies = m->face_vertices[m->groups[0].face_offset + i];
        if (faceVerticies != 3)
        {
          puts("Error: found face that has more than 3 vertecies. Did you forget to triangulate the faces on the obj export?");
          return -2;
        }
        for (unsigned int k = 0; k < 3; ++k)
        {
          fastObjIndex mi = m->indices[m->groups[0].index_offset + idx];

          const Vertex vertex{
            .x = m->positions[3 * mi.p],
            .y = m->positions[3 * mi.p + 1],
            .z = m->positions[3 * mi.p + 2],
            .u = m->texcoords[2 * mi.t],
            .v = m->texcoords[2 * mi.t + 1]
          };

          if (uniqueVertices.count(vertex) == 0)
          {
            const unsigned int vertIndex = unsigned int(uniqueVertices.size());

            uniqueVertices.insert({ vertex, vertIndex });

            fwrite(&vertex, sizeof(vertex), 1, outFile);

            triangles.push_back(vertIndex);
          }
          else
          {
            triangles.push_back(uniqueVertices[vertex]);
          }
          ++idx;
        }
      }

      fwrite(triangles.data(), sizeof(float), triangles.size(), outFile);

      fseek(outFile, 8, SEEK_SET);
      const unsigned int vertexCount = unsigned int(uniqueVertices.size());
      fwrite(&vertexCount, sizeof(vertexCount), 1, outFile);
      fwrite(&m->groups[0].face_count, sizeof(m->groups[0].face_count), 1, outFile); // triangle count
      fclose(outFile);
    }
#endif
  }
#if 1
  else if (strcmp(".txt", dotExtension) == 0) // meshes.txt
  {
    unsigned accumVertexIndex = 0;

    char line[512];
    char filePath[512];
    while (fgets(line, sizeof(line), inFile))
    {
      const size_t len = strlen(line);
      line[len - 1] = '\0';

      const size_t meshesPath = strlen(argv[1]) - 4;

      strcpy(filePath, argv[1]);
      const size_t filePathLen = strlen(filePath);

      for (int i = 0; i < len; ++i)
      {
        filePath[filePathLen - 10 + i] = line[i];
      }

      FILE* file = fopen(filePath, "rb");

      struct Vertex
      {
        float x, y, z, u, v;

        bool operator< (const Vertex& o) const
        {
          return std::tie(x, y, z, u, v) < std::tie(o.x, o.y, o.z, o.u, o.v);
        }
      } vertex{};

      std::map<Vertex, unsigned int> uniqueVertices;
      std::vector<Vertex> uniqueVerticesList;
      std::vector<unsigned int> triangles;

      while (5 == fscanf(file, "%f_f32,%f_f32,%f_f32,%f_f32,%f_f32,\n", &vertex.x, &vertex.y, &vertex.z, &vertex.u, &vertex.v))
      {
        if (!uniqueVertices.contains(vertex))
        {
          const unsigned int vertIndex = unsigned int(uniqueVertices.size());

          uniqueVertices.insert({ vertex, vertIndex });
          uniqueVerticesList.push_back(vertex);
        }

        triangles.push_back(uniqueVertices[vertex]);
      }

      fclose(file);

      {
        char outFilepath[512];

        strcpy(outFilepath, argv[1]);
        strcpy(&outFilepath[meshesPath], "/");
        strcpy(&outFilepath[meshesPath + sizeof("/") - 2], &filePath[meshesPath + sizeof("/non-indexed/") - 2]);

        FILE* outFile = fopen(outFilepath, "wb");

        for (size_t i = 0; i < uniqueVerticesList.size(); ++i)
        {
          const Vertex& v = uniqueVerticesList[i];
          fprintf(outFile, "%9ff,%9ff,%9ff,%9ff,%9ff,\n", v.x, v.y, v.z, v.u, v.v);
        }

        fclose(outFile);
      }
#if 1
      {
        char outFilepath[512];

        strcpy(outFilepath, argv[1]);
        strcpy(&outFilepath[meshesPath], "/indexed-zero-based/");
        strcpy(&outFilepath[meshesPath + sizeof("/indexed-zero-based/") - 2], &filePath[meshesPath + sizeof("/non-indexed/") - 2]);

        const size_t outFilepathLen2 = strlen(outFilepath) - 8;
        strcpy(&outFilepath[outFilepathLen2], "indicesZeroBased");

        FILE* outFile = fopen(outFilepath, "wb");

        for (size_t i = 0; i < triangles.size(); i += 3)
        {
          fprintf(outFile, "%5u,%5u,%5u,\n", triangles[i], triangles[i + 1], triangles[i + 2]);
        }

        fclose(outFile);
      }
#endif
      {
        char outFilepath[512];

        strcpy(outFilepath, argv[1]);
        strcpy(&outFilepath[meshesPath], "/");
        strcpy(&outFilepath[meshesPath + sizeof("/") - 2], &filePath[meshesPath + sizeof("/non-indexed/") - 2]);

        const size_t outFilepathLen2 = strlen(outFilepath) - 8;
        strcpy(&outFilepath[outFilepathLen2], "indices");

        FILE* outFile = fopen(outFilepath, "wb");

        for (size_t i = 0; i < triangles.size(); i += 3)
        {
          fprintf(outFile, "%5u,%5u,%5u,\n", accumVertexIndex + triangles[i], accumVertexIndex + triangles[i + 1], accumVertexIndex + triangles[i + 2]);
        }

        fclose(outFile);

        accumVertexIndex += unsigned(uniqueVerticesList.size());
      }
    }
  }
#endif
#if 1
  else if (strcmp(".glb", dotExtension) == 0)
  {
    char outFilepath[512];
    strcpy(outFilepath, argv[1]);
    {
      outFilepath[argv1Len] = '.';
      outFilepath[argv1Len + 1] = 's';
      outFilepath[argv1Len + 2] = 't';
      outFilepath[argv1Len + 3] = 'a';
      outFilepath[argv1Len + 4] = 'g';
      outFilepath[argv1Len + 5] = 'e';
      outFilepath[argv1Len + 6] = '\0';
    }


    fseek(inFile, 0, SEEK_END);

    std::vector<unsigned char> fileData(ftell(inFile));
    rewind(inFile);
    fread(fileData.data(), fileData.size(), 1, inFile);
    fclose(inFile);

    cgltf_options options{};
    cgltf_data* gltf = nullptr;
    const cgltf_result result = cgltf_parse(&options, fileData.data(), fileData.size(), &gltf);
    if (result != cgltf_result_success)
    {
      puts(R"(Could not read GLTF.)");
      return -3;
    }

    const cgltf_result result2 = cgltf_load_buffers(&options, gltf, "");
    if (result2 != cgltf_result_success)
    {
      puts(R"(Could not read GLTF buffers.)");
      return -3;
    }

    if (gltf->scenes_count != 1)
    {
      puts(R"(Expected scene count to be 1.)");
      return -3;
    }

    FILE* outFile = fopen(outFilepath, "wb");

    struct Header
    {
      char magic[4] = { 'S', '3', 'D', 'S' };
      uint32_t version = 2;
      uint32_t objectCount;
      uint32_t totalVertexDataSize;
      uint32_t totalIndexDataSize;

      uint32_t vertexDataOffset;
      uint32_t indexDataOffset;
    };

    Header header{
      .objectCount = static_cast<uint32_t>(gltf->scenes[0].nodes_count)
    };

    fwrite(&header, sizeof(Header), 1, outFile);

    struct ObjectHeader
    {
      float translation[3];
      float rotation[4];
      float scale[3];

      uint32_t indexCount;
      uint32_t startIndex;

      uint32_t textureDataOffset;
      uint32_t textureDataSize;
    };

    uint32_t vertexCount = 0;

    std::vector<ObjectHeader> objectHeaders(gltf->scenes[0].nodes_count);

    fwrite(objectHeaders.data(), sizeof(ObjectHeader), gltf->scenes[0].nodes_count, outFile);

    header.vertexDataOffset = ftell(outFile);

    { // vertices and indices
      uint32_t runningStartIndex = 0;
      uint32_t runningVerts = 0;
      std::vector<std::vector<unsigned int>> trianglesPerObject(gltf->scenes[0].nodes_count);

      for (cgltf_size i = 0; i < gltf->scenes[0].nodes_count; ++i)
      {
        cgltf_node* node = gltf->scenes[0].nodes[i];

        node->name;

        objectHeaders[i].translation[0] = node->translation[0];
        objectHeaders[i].translation[1] = node->translation[1];
        objectHeaders[i].translation[2] = node->translation[2];
        objectHeaders[i].rotation[0] = node->rotation[0];
        objectHeaders[i].rotation[1] = node->rotation[1];
        objectHeaders[i].rotation[2] = node->rotation[2];
        objectHeaders[i].rotation[3] = node->rotation[3];
        objectHeaders[i].scale[0] = node->scale[0];
        objectHeaders[i].scale[1] = node->scale[1];
        objectHeaders[i].scale[2] = node->scale[2];

        for (cgltf_size j = 0; j < node->mesh->primitives_count; ++j)
        {
          const cgltf_primitive& primitive = node->mesh->primitives[j];

          primitive.material->name;

          // Access positions
          const cgltf_accessor* position_accessor = nullptr;
          const cgltf_accessor* texcoord_accessor = nullptr;
          for (cgltf_size k = 0; k < primitive.attributes_count; ++k)
          {
            if (primitive.attributes[k].type == cgltf_attribute_type_position)
            {
              position_accessor = primitive.attributes[k].data;
              //break;
            }
            if (primitive.attributes[k].type == cgltf_attribute_type_texcoord)
            {
              texcoord_accessor = primitive.attributes[k].data;
              //break;
            }
          }

          if (position_accessor == nullptr)
          {
            puts(R"(Expected position_accessor)");
            return -3;
          }
          if (texcoord_accessor == nullptr)
          {
            puts(R"(Expected texcoord_accessor)");
            return -3;
          }
          if (position_accessor->count != texcoord_accessor->count)
          {
            puts(R"(Expected position_accessor->count == texcoord_accessor->count)");
            return -3;
          }

          const float* position_data = reinterpret_cast<const float*>(reinterpret_cast<const uint8_t*>(position_accessor->buffer_view->buffer->data) + position_accessor->buffer_view->offset);
          const float* texcoord_data = reinterpret_cast<const float*>(reinterpret_cast<const uint8_t*>(texcoord_accessor->buffer_view->buffer->data) + texcoord_accessor->buffer_view->offset);

          struct Vertex
          {
            float x, y, z, u, v;

            bool operator< (const Vertex& o) const
            {
              return std::tie(x, y, z, u, v) < std::tie(o.x, o.y, o.z, o.u, o.v);
            }
          };

          std::map<Vertex, unsigned int> uniqueVertices;
          std::vector<Vertex> uniqueVerticesList;

          {
            const uint8_t* buffer = reinterpret_cast<const uint8_t*>(primitive.indices->buffer_view->buffer->data) + primitive.indices->offset + primitive.indices->buffer_view->offset;

            for (cgltf_size k = 0; k < primitive.indices->count; ++k)
            {
              uint32_t index;
              switch (primitive.indices->component_type)
              {
              case cgltf_component_type_r_8u:
                index = buffer[k];
                break;
              case cgltf_component_type_r_16u:
                index = reinterpret_cast<const uint16_t*>(buffer)[k];
                break;
              case cgltf_component_type_r_32u:
                index = reinterpret_cast<const uint32_t*>(buffer)[k];
                break;
              }

              const Vertex vertex{
                .x = position_data[3 * index + 0],
                .y = position_data[3 * index + 1],
                .z = position_data[3 * index + 2],
                .u = texcoord_data[2 * index + 0],
                .v = texcoord_data[2 * index + 1]
              };

              if (uniqueVertices.count(vertex) == 0)
              {
                const unsigned int vertIndex = unsigned int(uniqueVertices.size());

                uniqueVertices.insert({ vertex, vertIndex });

                uniqueVerticesList.push_back(vertex);

                trianglesPerObject[i].push_back(vertIndex + runningVerts);
              }
              else
              {
                trianglesPerObject[i].push_back(uniqueVertices[vertex] + runningVerts);
              }
            }
          }

          header.totalVertexDataSize += uint32_t(uniqueVertices.size()) * 5 * sizeof(float);
          objectHeaders[i].indexCount = uint32_t(primitive.indices->count);
          header.totalIndexDataSize += uint32_t(primitive.indices->count) * sizeof(uint32_t);

          fwrite(uniqueVerticesList.data(), sizeof(Vertex), uniqueVerticesList.size(), outFile);

          objectHeaders[i].startIndex = runningStartIndex;
          runningStartIndex += uint32_t(trianglesPerObject[i].size());
          runningVerts += uint32_t(uniqueVertices.size());
        }
      }

      header.indexDataOffset = ftell(outFile);

      for (size_t i = 0; i < trianglesPerObject.size(); ++i)
      {
        fwrite(trianglesPerObject[i].data(), sizeof(uint32_t), trianglesPerObject[i].size(), outFile);
      }
    }

    for (cgltf_size i = 0; i < gltf->scenes[0].nodes_count; ++i)
    { // texture
      cgltf_node* node = gltf->scenes[0].nodes[i];

      for (cgltf_size k = 0; k < node->mesh->primitives_count; ++k)
      {
        cgltf_texture* texture = node->mesh->primitives[k].material->emissive_texture.texture;
        texture->name;

        if (texture == nullptr)
        {
          puts(R"(No texture found. Make sure the .glb export is "Limited to Selected Objects")");
          return -3;
        }

        if (strcmp("image/png", texture->image->mime_type) != 0)
        {
          puts(R"(Expected png texture)");
          return -3;
        }

        {
          FILE* outFileFPng = fopen("temp.png", "wb");
          fwrite(reinterpret_cast<const uint8_t*>(texture->image->buffer_view->buffer->data) + texture->image->buffer_view->offset, texture->image->buffer_view->size, 1, outFileFPng);
          fclose(outFileFPng);
        }

        system("..\\texture\\bin\\texconv.exe -y -m 1 temp.png");

        {
          FILE* compressonatorCheck = fopen("../texture/bin/compressonator/compressonatorcli.exe", "r");
          if (compressonatorCheck == nullptr)
          {
            puts(R"(../texture/bin/compressonator/compressonatorcli.exe not found. Fallback to low quality textures...)");

            system("bin\\texconv -y -f BC1_UNORM -m 1 temp.dds");
          }
          else
          {
            fclose(compressonatorCheck);

            system("..\\texture\\bin\\compressonator\\compressonatorcli.exe temp.dds temp.dds -fd BC1 -NumThreads 8");
          }
        }

        FILE* inFileDds = fopen("temp.dds", "rb");
        fseek(inFileDds, 0, SEEK_END);

        std::vector<uint8_t> textureData(ftell(inFileDds));
        rewind(inFileDds);

        fread(textureData.data(), textureData.size(), 1, inFileDds);
        fclose(inFileDds);

        objectHeaders[i].textureDataOffset = ftell(outFile);
        objectHeaders[i].textureDataSize = uint32_t(textureData.size());

        fwrite(textureData.data(), sizeof(uint8_t), textureData.size(), outFile);
      }
    }
    { // rewrite headers
      fseek(inFile, 0, SEEK_SET);
      fwrite(&header, sizeof(Header), 1, outFile);
      fwrite(objectHeaders.data(), sizeof(ObjectHeader), objectHeaders.size(), outFile);
    }
    fclose(outFile);

    { // Android ASTC
      FILE* inDdsFile = fopen(outFilepath, "rb");

      const uint32_t beginTextureSection = objectHeaders[0].textureDataOffset;

      std::vector<unsigned char> fileData2(beginTextureSection);
      fread(fileData2.data(), beginTextureSection, 1, inFile);

      fclose(inDdsFile);

      char outAstcFile[512];
      strcpy(outAstcFile, argv[1]);
      {
        outAstcFile[argv1Len] = '.';
        outAstcFile[argv1Len + 1] = 's';
        outAstcFile[argv1Len + 2] = 't';
        outAstcFile[argv1Len + 3] = 'a';
        outAstcFile[argv1Len + 4] = 'g';
        outAstcFile[argv1Len + 5] = 'e';
        outAstcFile[argv1Len + 6] = '_';
        outAstcFile[argv1Len + 7] = 'a';
        outAstcFile[argv1Len + 8] = '\0';
      }

      FILE* outATSCFile = fopen(outAstcFile, "wb");

      fwrite(fileData2.data(), fileData2.size(), 1, outATSCFile);

      for (cgltf_size i = 0; i < gltf->scenes[0].nodes_count; ++i)
      { // texture
        cgltf_node* node = gltf->scenes[0].nodes[i];

        for (cgltf_size k = 0; k < node->mesh->primitives_count; ++k)
        {
          cgltf_texture* texture = node->mesh->primitives[k].material->emissive_texture.texture;
          texture->name;

          {
            FILE* outFileFPng = fopen("temp.png", "wb");
            fwrite(reinterpret_cast<const uint8_t*>(texture->image->buffer_view->buffer->data) + texture->image->buffer_view->offset, texture->image->buffer_view->size, 1, outFileFPng);
            fclose(outFileFPng);
          }

          system("..\\texture\\bin\\astcenc-avx2.exe -cl temp.png temp.astc 8x6 -thorough");

          FILE* inFileAstc = fopen("temp.astc", "rb");
          fseek(inFileAstc, 0, SEEK_END);

          std::vector<uint8_t> textureData(ftell(inFileAstc));
          rewind(inFileAstc);

          fread(textureData.data(), textureData.size(), 1, inFileAstc);
          fclose(inFileAstc);

          objectHeaders[i].textureDataOffset = ftell(outATSCFile);
          objectHeaders[i].textureDataSize = uint32_t(textureData.size());

          fwrite(textureData.data(), sizeof(uint8_t), textureData.size(), outATSCFile);
        }
      }

      fseek(outATSCFile, sizeof(Header), SEEK_SET);
      fwrite(objectHeaders.data(), sizeof(ObjectHeader), objectHeaders.size(), outATSCFile);

      fclose(outATSCFile);
    }
  }
#endif
  else
  {
    char outFilepath[512];
    strcpy(outFilepath, argv[1]);
    {
      outFilepath[argv1Len] = '.';
      outFilepath[argv1Len + 1] = 'c';
      outFilepath[argv1Len + 2] = '\0';
    }

    FILE* outFile = fopen(outFilepath, "wb");

    fseek(inFile, 0, SEEK_END);

    std::vector<unsigned char> fileData(ftell(inFile));
    rewind(inFile);

    fread(fileData.data(), fileData.size(), 1, inFile);
    fclose(inFile);

    if (strcmp(".png", dotExtension) == 0)
    {
      std::vector<unsigned char> rgbPixels;
      unsigned long width;
      unsigned long height;
      decodePNG(rgbPixels, width, height, fileData.data(), fileData.size(), false);

#if 1 // reencode png as fpng
      {
        std::vector<unsigned char> fpngData;
        Png::fpng_encode_image_to_memory(rgbPixels.data(), width, height, 3, fpngData);

        char outFilepathFPng[512];
        strcpy(outFilepathFPng, argv[1]);
        {
          outFilepathFPng[argv1Len - 4] = '.';
          outFilepathFPng[argv1Len - 3] = 'f';
          outFilepathFPng[argv1Len - 2] = '.';
          outFilepathFPng[argv1Len - 1] = 'p';
          outFilepathFPng[argv1Len] = 'n';
          outFilepathFPng[argv1Len + 1] = 'g';
          outFilepathFPng[argv1Len + 2] = '\0';
        }

        FILE* outFileFPng = fopen(outFilepathFPng, "wb");
        fwrite(fpngData.data(), fpngData.size(), 1, outFileFPng);
        fclose(outFileFPng);
      }
#endif

      fprintf(outFile, "const u8 pngFile[%zu] = {", rgbPixels.size());

      for (size_t i = 0; i < rgbPixels.size(); ++i)
      {
        if (i % 12 == 0)
        {
          if (i != 0)
            fputc(',', outFile);
          fputs("\n  ", outFile);
          fprintf(outFile, "0x%02x", rgbPixels.at(i));
        }
        else
        {
          fprintf(outFile, ", 0x%02x", rgbPixels.at(i));
        }
      }
      fprintf(outFile, "\n};\n");
    }
    else
    {
      const char* sep = strrchr(argv[1], '/');
      if (sep == nullptr)
        sep = strrchr(argv[1], '\\');

      if (sep == nullptr)
        fprintf(outFile, "const u8 %s[%zu] = {", argv[1], fileData.size());
      else
        fprintf(outFile, "const u8 %s[%zu] = {", &sep[1], fileData.size());

      for (size_t i = 0; i < fileData.size(); ++i)
      {
        if (i % 12 == 0)
        {
          if (i != 0)
            fputc(',', outFile);
          fputs("\n  ", outFile);
          fprintf(outFile, "0x%02x", fileData.at(i));
        }
        else
        {
          fprintf(outFile, ", 0x%02x", fileData.at(i));
        }
      }
      fprintf(outFile, "\n};\n");
    }
    fclose(outFile);
  }


  return 0;
}
