1 /// TinyGLTF is a library for loading JSON serialized (embedded) GLTF models. 2 module tinygltf; 3 4 import std.stdio; 5 import std.string; 6 import std.file; 7 import std.json; 8 import core.stdcpp.array; 9 import std.conv; 10 import std.algorithm.iteration; 11 import std.base64; 12 13 /// OpenGL rendering mode POINTS. 14 enum TINYGLTF_MODE_POINTS = (0); 15 /// OpenGL rendering mode LINE. 16 enum TINYGLTF_MODE_LINE = (1); 17 /// OpenGL rendering mode LINE LOOP. 18 enum TINYGLTF_MODE_LINE_LOOP = (2); 19 /// OpenGL rendering mode LINE STRIP. 20 enum TINYGLTF_MODE_LINE_STRIP = (3); 21 /// OpenGL rendering mode TRIANGLES. 22 enum TINYGLTF_MODE_TRIANGLES = (4); 23 /// OpenGL rendering mode TRIANGLE STRIP. 24 enum TINYGLTF_MODE_TRIANGLE_STRIP = (5); 25 /// OpenGL rendering mode TRIANGLE FAN. 26 enum TINYGLTF_MODE_TRIANGLE_FAN = (6); 27 28 /// GLTF data component type BYTE. 29 enum TINYGLTF_COMPONENT_TYPE_BYTE = (5120); 30 /// GLTF data component type UNSIGNED BYTE. 31 enum TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE = (5121); 32 /// GLTF data component type SHORT. 33 enum TINYGLTF_COMPONENT_TYPE_SHORT = (5122); 34 /// GLTF data component type UNSIGNED SHORT. 35 enum TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT = (5123); 36 /// GLTF data component type INTEGER. 37 enum TINYGLTF_COMPONENT_TYPE_INT = (5124); 38 /// GLTF data component type UNSIGNED INTEGER. 39 enum TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT = (5125); 40 /// GLTF data component type FLOAT. 41 enum TINYGLTF_COMPONENT_TYPE_FLOAT = (5126); 42 /** 43 OpenGL double type. Note that some of glTF 2.0 validator does not; 44 support double type even the schema seems allow any value of 45 integer: 46 https://github.com/KhronosGroup/glTF/blob/b9884a2fd45130b4d673dd6c8a706ee21ee5c5f7/specification/2.0/schema/accessor.schema.json#L22 47 */ 48 enum TINYGLTF_COMPONENT_TYPE_DOUBLE = (5130); 49 50 /// OpenGL texture filtering mode NEAREST. 51 enum TINYGLTF_TEXTURE_FILTER_NEAREST = (9728); 52 /// OpenGL texture filtering mode LINEAR. 53 enum TINYGLTF_TEXTURE_FILTER_LINEAR = (9729); 54 /// OpenGL texture filtering mode NEAREST MIPMAP NEAREST. 55 enum TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST = (9984); 56 /// OpenGL texture filtering mode LINEAR MIPMAP NEAREST. 57 enum TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST = (9985); 58 /// OpenGL texture filtering mode NEAREST MIPMAP LINEAR. 59 enum TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR = (9986); 60 /// OpenGL texture filtering mode LINEAR MIPMAP LINEAR. 61 enum TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR = (9987); 62 63 /// OpenGL texture wrap mode REPEAT. 64 enum TINYGLTF_TEXTURE_WRAP_REPEAT = (10497); 65 /// OpenGL texture wrap mode CLAMP TO EDGE. 66 enum TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE = (33071); 67 /// OpenGL texture wrap mode MIRRORED REPEAT. 68 enum TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT = (33648); 69 70 // Redeclarations of the above for technique.parameters. 71 /// GLTF data component type BYTE. 72 enum TINYGLTF_PARAMETER_TYPE_BYTE = (5120); 73 /// GLTF data component type UNSIGNED BYTE. 74 enum TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE = (5121); 75 /// GLTF data component type SHORT. 76 enum TINYGLTF_PARAMETER_TYPE_SHORT = (5122); 77 /// GLTF data component type UNSIGNED SHORT. 78 enum TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT = (5123); 79 /// GLTF data component type INTEGER. 80 enum TINYGLTF_PARAMETER_TYPE_INT = (5124); 81 /// GLTF data component type UNSIGNED INTEGER. 82 enum TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT = (5125); 83 /// GLTF data component type FLOAT. 84 enum TINYGLTF_PARAMETER_TYPE_FLOAT = (5126); 85 86 /// GLTF float Vector 2 parameter type. 87 enum TINYGLTF_PARAMETER_TYPE_FLOAT_VEC2 = (35664); 88 /// GLTF float Vector 3 parameter type. 89 enum TINYGLTF_PARAMETER_TYPE_FLOAT_VEC3 = (35665); 90 /// GLTF float Vector 4 parameter type. 91 enum TINYGLTF_PARAMETER_TYPE_FLOAT_VEC4 = (35666); 92 93 /// GLTF integer Vector 2 parameter type. 94 enum TINYGLTF_PARAMETER_TYPE_INT_VEC2 = (35667); 95 /// GLTF integer Vector 3 parameter type. 96 enum TINYGLTF_PARAMETER_TYPE_INT_VEC3 = (35668); 97 /// GLTF integer Vector 4 parameter type. 98 enum TINYGLTF_PARAMETER_TYPE_INT_VEC4 = (35669); 99 100 /// GLTF Boolean parameter type. 101 enum TINYGLTF_PARAMETER_TYPE_BOOL = (35670); 102 /// GLTF Boolean Vector 2 parameter type. 103 enum TINYGLTF_PARAMETER_TYPE_BOOL_VEC2 = (35671); 104 /// GLTF Boolean Vector 3 parameter type. 105 enum TINYGLTF_PARAMETER_TYPE_BOOL_VEC3 = (35672); 106 /// GLTF Boolean Vector 4 parameter type. 107 enum TINYGLTF_PARAMETER_TYPE_BOOL_VEC4 = (35673); 108 109 /// GLTF float Matrix 2x2 parameter type. 110 enum TINYGLTF_PARAMETER_TYPE_FLOAT_MAT2 = (35674); 111 /// GLTF float Matrix 3x3 parameter type. 112 enum TINYGLTF_PARAMETER_TYPE_FLOAT_MAT3 = (35675); 113 /// GLTF float Matrix 4x4 parameter type. 114 enum TINYGLTF_PARAMETER_TYPE_FLOAT_MAT4 = (35676); 115 116 // End parameter types 117 118 /// GLTF Vector 2 data type. 119 enum TINYGLTF_TYPE_VEC2 = (2); 120 /// GLTF Vector 3 data type. 121 enum TINYGLTF_TYPE_VEC3 = (3); 122 /// GLTF Vector 4 data type. 123 enum TINYGLTF_TYPE_VEC4 = (4); 124 125 /// GLTF Matrix 2x2 data type. 126 enum TINYGLTF_TYPE_MAT2 = (32 + 2); 127 /// GLTF Matrix 3x3 data type. 128 enum TINYGLTF_TYPE_MAT3 = (32 + 3); 129 /// GLTF Matrix 4x4 data type. 130 enum TINYGLTF_TYPE_MAT4 = (32 + 4); 131 132 /// GLTF Scalar data type. 133 enum TINYGLTF_TYPE_SCALAR = (64 + 1); 134 /// GLTF Vector data type. 135 enum TINYGLTF_TYPE_VECTOR = (64 + 4); 136 /// GLTF Matrix data type. 137 enum TINYGLTF_TYPE_MATRIX = (64 + 16); 138 139 /// Enumerators for Values to define which type they are. 140 enum Type { 141 NULL_TYPE, 142 REAL_TYPE, 143 INT_TYPE, 144 BOOL_TYPE, 145 STRING_TYPE, 146 ARRAY_TYPE, 147 BINARY_TYPE, 148 OBJECT_TYPE 149 } 150 151 /// Null type enumerator. D type nothing/null. 152 alias NULL_TYPE = Type.NULL_TYPE; 153 /// Double type enumerator. D type double. 154 alias REAL_TYPE = Type.REAL_TYPE; 155 /// Integer type enumerator. D type int. 156 alias INT_TYPE = Type.INT_TYPE; 157 /// Boolean type enumerator. D type bool. 158 alias BOOL_TYPE = Type.BOOL_TYPE; 159 /// String type enumerator. D type string. 160 alias STRING_TYPE = Type.STRING_TYPE; 161 /// Array type enumerator. D type Value[]. 162 alias ARRAY_TYPE = Type.ARRAY_TYPE; 163 /// Binary type enumerator. D type ubyte[]. 164 alias BINARY_TYPE = Type.BINARY_TYPE; 165 /// Object type enumerator. D type Value[[str][ing]]. 166 alias OBJECT_TYPE = Type.OBJECT_TYPE; 167 168 /// Gets the component size in byte size. (Integer) 169 pragma(inline, true) private int getComponentSizeInBytes(uint componentType) { 170 if (componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { 171 return 1; 172 } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { 173 return 1; 174 } else if (componentType == TINYGLTF_COMPONENT_TYPE_SHORT) { 175 return 2; 176 } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { 177 return 2; 178 } else if (componentType == TINYGLTF_COMPONENT_TYPE_INT) { 179 return 4; 180 } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { 181 return 4; 182 } else if (componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { 183 return 4; 184 } else if (componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { 185 return 8; 186 } else { 187 // Unknown component type 188 return -1; 189 } 190 } 191 192 /// Gets the number of components in a type. (For example vec3 has 3 components.) 193 pragma(inline, true) private int getNumComponentsInType(uint ty) { 194 if (ty == TINYGLTF_TYPE_SCALAR) { 195 return 1; 196 } else if (ty == TINYGLTF_TYPE_VEC2) { 197 return 2; 198 } else if (ty == TINYGLTF_TYPE_VEC3) { 199 return 3; 200 } else if (ty == TINYGLTF_TYPE_VEC4) { 201 return 4; 202 } else if (ty == TINYGLTF_TYPE_MAT2) { 203 return 4; 204 } else if (ty == TINYGLTF_TYPE_MAT3) { 205 return 9; 206 } else if (ty == TINYGLTF_TYPE_MAT4) { 207 return 16; 208 } else { 209 // Unknown component type 210 return -1; 211 } 212 } 213 214 //* Translation Note: This whole thing is duck typed 215 /// Simple class to represent a JSON object. 216 class Value { 217 218 public: 219 // The value becomes whatever it is constructed with 220 221 /// Construct as a NULL type 222 this() { 223 this.type_ = NULL_TYPE; 224 this.int_value_ = 0; 225 this.real_value_ = 0.0; 226 this.boolean_value_ = false; 227 } 228 /// Construct as a BOOL (boolean) type. 229 this(bool b) { 230 this.boolean_value_ = b; 231 this.type_ = BOOL_TYPE; 232 } 233 /// Construct as a INT (integer) type. 234 this(int i) { 235 this.int_value_ = i; 236 this.real_value_ = i; 237 this.type_ = INT_TYPE; 238 } 239 /// Construct as a REAL (double) type. 240 this(double n) { 241 this.real_value_ = n; 242 this.type_ = REAL_TYPE; 243 } 244 /// Construct as a STRING (string) type. 245 this(string s) { 246 this.string_value_ = s; 247 this.type_ = STRING_TYPE; 248 } 249 /// Construct as a BINARY (ubyte[]) type. 250 this(ubyte[] v) { 251 this.binary_value_ = v; 252 this.type_ = BINARY_TYPE; 253 } 254 /// Construct as an ARRAY (Value[]) type. 255 this(Value[] a) { 256 this.array_value_ = a; 257 this.type_ = ARRAY_TYPE; 258 } 259 /// Construct as an OBJECT (Value[string]) type. 260 this(Value[string] o) { 261 this.object_value_ = o; 262 this.type_ = OBJECT_TYPE; 263 } 264 /// Get what Type this Value is. 265 Type type(){ 266 return this.type_; 267 } 268 /// Gets if it's a bool. 269 bool isBool() { 270 return (this.type_ == BOOL_TYPE); 271 } 272 /// Gets if it's an integer. 273 bool isInt() { 274 return (this.type_ == INT_TYPE); 275 } 276 /// Gets if it's a number. 277 bool isNumber() { 278 return (this.type_ == REAL_TYPE) || (this.type_ == INT_TYPE); 279 } 280 /// Gets if it's a double. 281 bool isReal() { 282 return (this.type_ == REAL_TYPE); 283 } 284 /// Gets if it's a string. 285 bool isString() { 286 return (this.type_ == STRING_TYPE); 287 } 288 /// Gets if it's a binary. 289 bool isBinary() { 290 return (this.type_ == BINARY_TYPE); 291 } 292 /// Gets if it's an array. 293 bool isArray() { 294 return (this.type_ == ARRAY_TYPE); 295 } 296 /// Gets if it's an object. 297 bool isObject() { 298 return (this.type_ == OBJECT_TYPE); 299 } 300 301 /// Use this function if you want to have number value as double. 302 double getNumberAsDouble() { 303 if (this.type_ == INT_TYPE) { 304 return cast(double)this.int_value_; 305 } else { 306 return this.real_value_; 307 } 308 } 309 310 // TODO(syoyo): Support int value larger than 32 bits 311 /// Use this function if you want to have number value as int. 312 int getNumberAsInt() { 313 if (this.type_ == REAL_TYPE) { 314 return cast(int)this.real_value_; 315 } else { 316 return this.int_value_; 317 } 318 } 319 320 /// Lookup value from an array. 321 Value get(int idx) { 322 static Value null_value; 323 assert(this.isArray()); 324 assert(idx >= 0); 325 return (idx < this.array_value_.length) ? array_value_[idx] : null_value; 326 } 327 328 /// Lookup value from a key-value pair. 329 Value get(const string key) { 330 static Value null_value; 331 assert(this.isArray()); 332 assert(this.isObject()); 333 return object_value_.get(key, null_value); 334 } 335 336 /// Get the length of the array if this is an array. 337 size_t arrayLen() { 338 if (!this.isArray()) 339 return 0; 340 return this.array_value_.length; 341 } 342 343 /// Valid only for object type. 344 bool has(const string key) { 345 if (!this.isObject()) 346 return false; 347 return (key in this.object_value_) !is null; 348 } 349 350 /// List keys 351 string[] keys() { 352 /// Clone in memory 353 string[] tempKeys; 354 foreach (k,v; this.object_value_) { 355 tempKeys ~= k; 356 } 357 return tempKeys; 358 } 359 360 size_t size() { 361 return (this.isArray() ? this.arrayLen() : keys().length); 362 } 363 364 //* Translation note: This is the more D way to do this than the weird mixin in C 365 mixin(TINYGLTF_VALUE_GET("bool", "boolean_value_")); 366 mixin(TINYGLTF_VALUE_GET("double", "real_value_")); 367 mixin(TINYGLTF_VALUE_GET("int", "int_value_")); 368 mixin(TINYGLTF_VALUE_GET("string", "string_value_")); 369 mixin(TINYGLTF_VALUE_GET("ubyteArray", "binary_value_", "ubyte[]")); 370 mixin(TINYGLTF_VALUE_GET("Array", "array_value_", "Value[]")); 371 mixin(TINYGLTF_VALUE_GET("Object", "object_value_", "Value[string]")); 372 373 protected: 374 375 Type type_ = NULL_TYPE; 376 377 int int_value_ = 0; 378 double real_value_ = 0.0; 379 string string_value_; 380 ubyte[] binary_value_; 381 Value[] array_value_; 382 Value[string] object_value_; 383 bool boolean_value_ = false; 384 385 } 386 387 // Generate mixins so I don't have to type a bunch of things. 388 private string TINYGLTF_VALUE_GET(string ctype, string var, string returnType = "") { 389 if (returnType == "") { 390 returnType = ctype; 391 } 392 const string fancyCType = capitalize(ctype); 393 return "\n" ~ 394 returnType ~ " Get" ~ fancyCType ~ "() {\n" ~ 395 "return this." ~ var ~ ";\n" ~ 396 "}"; 397 } 398 399 /** 400 Holds animation channels. Animations channels are the root of the animation in the model. 401 An animation channel combines an animation sampler with a target property being animated. 402 */ 403 class AnimationChannel { 404 /// Required. Points to the index of the AnimationSampler. 405 int sampler = -1; 406 /** 407 REQUIRED. The index of the node to target (alternative target should be provided by extension). 408 Extensions are not supported in this translation so this is required. 409 This is the joint if you're wondering. 410 */ 411 int target_node = -1; 412 413 /** 414 REQUIRED with standard values of ["translation", "rotation", "scale", "weights"] 415 */ 416 string target_path; 417 418 this(int sampler = -1, int target_node = -1) { 419 this.sampler = sampler; 420 this.target_node = target_node; 421 } 422 } 423 424 /** 425 An animation sampler combines timestamps with a sequence of output values and defines an interpolation algorithm. 426 */ 427 class AnimationSampler { 428 /// REQUIRED. The index of an accessor containing keyframe timestamps. 429 int input = -1; 430 /// REQUIRED. The index of an accessor, containing keyframe output values. 431 int output = -1; 432 /** 433 Interpolation algorithm. 434 "LINEAR", "STEP","CUBICSPLINE" or user defined string. 435 Default: "LINEAR". 436 */ 437 string interpolation = "LINEAR"; 438 439 this(int input = -1, int output = -1, string interpolation = "LINEAR"){ 440 this.input = input; 441 this.output = output; 442 this.interpolation = interpolation; 443 } 444 } 445 446 /** 447 A keyframe animation. 448 */ 449 class Animation { 450 /// The animation name. 451 string name; 452 /** 453 An array of animation channels. An animation channel combines an animation sampler with a target property being animated. 454 Different channels of the same animation MUST NOT have the same targets. 455 */ 456 AnimationChannel[] channels; 457 /** 458 An array of animation samplers. 459 An animation sampler combines timestamps with a sequence of output values and defines an interpolation algorithm. 460 */ 461 AnimationSampler[] samplers; 462 463 this() {} 464 } 465 466 /** 467 Joints and matrices defining a skin. 468 */ 469 class Skin { 470 /// Name of the skin. 471 string name; 472 /// REQUIRED. The index of the accessor containing the floating-point 4x4 inverse-bind matrices. 473 int inverseBindMatrices = -1; 474 /// The index of the node used as a skeleton root. 475 int skeleton = -1; 476 /// Indices of skeleton nodes, used as joints in this skin. 477 int[] joints; 478 479 this() {} 480 } 481 482 /** 483 Texture sampler properties for filtering and wrapping modes. 484 */ 485 class Sampler { 486 /// The name of this sampler. 487 string name; 488 // glTF 2.0 spec does not define default value for `minFilter` and 489 // `magFilter`. Set -1 in TinyGLTF(issue #186) 490 491 /** 492 OPTIONAL. -1 is no filter defined. ["NEAREST", "LINEAR", 493 "NEAREST_MIPMAP_NEAREST", "LINEAR_MIPMAP_NEAREST", 494 "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_LINEAR"] 495 */ 496 int minFilter = -1; 497 498 /// OPTIONAL. -1 is no filter defined. ["NEAREST", "LINEAR"] 499 int magFilter = -1; 500 501 /// ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default "REPEAT" 502 int wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT; 503 504 /// ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default "REPEAT" 505 int wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT; 506 507 this(int minFilter = -1, int magFilter = -1, int wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT, int wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT) { 508 this.minFilter = minFilter; 509 this.magFilter = magFilter; 510 this.wrapS = wrapS; 511 this.wrapT = wrapT; 512 } 513 } 514 515 /** 516 A view into a buffer generally representing a subset of the buffer. 517 */ 518 class BufferView { 519 /// The name of the bufferView. 520 string name; 521 /// REQUIRED. The index of the buffer. 522 int buffer = -1; 523 /** 524 The offset into the buffer in bytes. 525 Minimum 0, default 0. 526 */ 527 size_t byteOffset = 0; 528 /** 529 The length of the bufferView in bytes. 530 REQUIRED. Minimum 1. 0 is invalid. 531 */ 532 size_t byteLength = 0; 533 /** 534 The stride, in bytes. 535 Minimum 4. Maximum 252 (multiple of 4). Default 0 is understood to be tightly packed. 536 */ 537 size_t byteStride = 0; 538 /** 539 The hint representing the intended GPU buffer type to use with this buffer view. 540 ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] for vertex indices or attribs. Could be 0 for other data. 541 */ 542 int target = 0; 543 544 this(int buffer = -1, int byteOffset = 0, int byteLength = 0, int byteStride = 0, int target = 0) { 545 this.buffer = buffer; 546 this.byteOffset = byteOffset; 547 this.byteLength = byteLength; 548 this.byteStride = byteStride; 549 this.target = target; 550 } 551 } 552 553 /** 554 A typed view into a buffer view that contains raw binary data. 555 */ 556 class Accessor { 557 /** 558 The index of the bufferView. 559 REQUIRED here since sparse accessor are not supported. 560 */ 561 int bufferView = -1; 562 /// The name of the Accessor. 563 string name; 564 /** 565 The offset relative to the start of the buffer view in bytes. 566 */ 567 size_t byteOffset = 0; 568 /** 569 Specifies whether integer data values are normalized before usage. 570 OPTIONAL. 571 */ 572 bool normalized = false; 573 /** 574 The datatype of the accessor’s components. 575 REQUIRED. One of TINYGLTF_COMPONENT_TYPE_*** 576 */ 577 int componentType = -1; 578 /** 579 REQUIRED. The number of elements referenced by this accessor. 580 */ 581 int count = 0; 582 /** 583 Specifies if the accessor’s elements are scalars, vectors, or matrices. 584 REQUIRED. One of TINYGLTF_TYPE_*** 585 */ 586 int type = -1; 587 /** 588 Minimum value of each component in this accessor. 589 OPTIONAL. Integer value is promoted to double. 590 */ 591 double[] minValues; 592 /** 593 Maximum value of each component in this accessor. 594 OPTIONAL. Integer value is promoted to double. 595 */ 596 double[] maxValues; 597 598 /** 599 Utility function to compute byteStride for a given bufferView object. 600 Returns -1 upon invalid glTF value or parameter configuration. 601 */ 602 int byteStride(const BufferView bufferViewObject) const { 603 if (bufferViewObject.byteStride == 0) { 604 // Assume data is tightly packed. 605 int componentSizeInBytes = getComponentSizeInBytes(componentType); 606 607 if (componentSizeInBytes <= 0) { 608 return -1; 609 } 610 611 int numComponents = getNumComponentsInType(type); 612 613 if (numComponents <= 0) { 614 return -1; 615 } 616 617 return componentSizeInBytes * numComponents; 618 619 } else { 620 // Check if byteStride is a multiple of the size of the accessor's component 621 // type. 622 int componentSizeInBytes = getComponentSizeInBytes(componentType); 623 624 if (componentSizeInBytes <= 0) { 625 return -1; 626 } 627 628 if ((bufferViewObject.byteStride % componentSizeInBytes) != 0) { 629 return -1; 630 } 631 return cast(int)bufferViewObject.byteStride; 632 } 633 634 // unreachable return 0; 635 // return 0; 636 } 637 638 this(int bufferView = -1, int byteOffset = 0, bool normalized = false, int componentType = -1, int count = 0, int type = -1) { 639 this.bufferView = bufferView; 640 this.byteOffset = byteOffset; 641 this.normalized = normalized; 642 this.componentType = componentType; 643 this.count = count; 644 this.type = type; 645 } 646 } 647 648 /** 649 Geometry to be rendered with the given material. 650 */ 651 class Primitive { 652 /** 653 REQUIRED. A plain JSON object, where each key corresponds to a mesh attribute semantic and each 654 value is the index of the accessor containing attribute’s data. 655 */ 656 int[string] attributes; 657 658 /// The index of the material to apply to this primitive when rendering. 659 int material = -1; 660 /// The index of the accessor that contains the vertex indices. 661 int indices = -1; 662 /** 663 The topology type of primitives to render. 664 One of TINYGLTF_MODE_*** 665 */ 666 int mode = -1; 667 668 /** 669 An array of morph targets. Each target is an associative array with attributes in 670 ["POSITION, "NORMAL", "TANGENT"] pointing to their corresponding accessors. 671 */ 672 int[string][] targets; 673 674 675 this(int material = -1, int indices = -1, int mode = -1) { 676 this.material = material; 677 this.indices = indices; 678 this.mode = mode; 679 } 680 } 681 682 /** 683 A set of primitives to be rendered. 684 Its global transform is defined by a node that references it. 685 */ 686 class Mesh { 687 /// The name of the mesh. 688 string name; 689 /// An array of primitives, each defining geometry to be rendered. 690 Primitive[] primitives; 691 /** 692 Array of weights to be applied to the morph targets. 693 The number of array elements MUST match the number of morph targets. 694 */ 695 double[] weights; 696 697 this() {} 698 } 699 /** 700 A node in the node hierarchy. 701 When the node contains skin, all mesh.primitives MUST contain JOINTS_0 and WEIGHTS_0 attributes. 702 A node MAY have either a matrix or any combination of translation/rotation/scale (TRS) properties. 703 TRS properties are converted to matrices and postmultiplied in the T * R * S order to compose the transformation matrix. 704 First the scale is applied to the vertices, then the rotation, and then the translation. 705 If none are provided, the transform is the identity. 706 When a node is targeted for animation (referenced by an animation.channel.target), matrix MUST NOT be present. 707 */ 708 class Node { 709 710 public: 711 /// The index of the camera referenced by this node. 712 int camera = -1; 713 /// The name of this node. 714 string name; 715 /// The index of the skin referenced by this node. 716 int skin = -1; 717 /// The index of the mesh in this node. 718 int mesh = -1; 719 /// The indices of this node’s children. 720 int[] children; 721 /** 722 The node’s unit quaternion rotation in the order (x, y, z, w), where w is the scalar. 723 Length must be 0 or 4. 724 */ 725 double[] rotation; 726 /** 727 The node’s non-uniform scale, given as the scaling factors along the x, y, and z axes. 728 Length must be 0 or 3. 729 */ 730 double[] scale; 731 /** 732 The node’s translation along the x, y, and z axes. 733 Length must be 0 or 3. 734 */ 735 double[] translation; 736 /** 737 A floating-point 4x4 transformation matrix stored in column-major order. 738 Length must be 0 or 16. 739 */ 740 double[] matrix; 741 /** 742 The weights of the instantiated morph target. 743 The number of array elements MUST match the number of morph targets of the referenced mesh. 744 When defined, mesh MUST also be defined. 745 */ 746 double[] weights; 747 748 this(int camera = -1, int skin = -1, int mesh = -1) { 749 this.camera = camera; 750 this.skin = skin; 751 this.mesh = mesh; 752 } 753 } 754 755 /** 756 A buffer points to binary geometry, animation, or skins. 757 */ 758 class Buffer { 759 /// The name of the buffer 760 string name; 761 /** 762 The raw data in ubytes. 763 This is decoded from the URI. 764 */ 765 ubyte[] data; 766 /// The length of the buffer in bytes. 767 int byteLength = -1; 768 769 this() {} 770 } 771 772 /** 773 Metadata about the glTF asset. 774 */ 775 class Asset { 776 /// REQUIRED. The glTF version in the form of <major>.<minor> that this asset targets. 777 string version_ = "2.0"; 778 /// Tool that generated this glTF model. Useful for debugging. 779 string generator; 780 /** 781 The minimum glTF version in the form of <major>.<minor> that this asset targets. 782 This property MUST NOT be greater than the asset version. 783 */ 784 string minVersion; 785 /// A copyright message suitable for display to credit the content creator. 786 string copyright; 787 788 this() {} 789 } 790 791 /** 792 Model is the container used to store all the decoded JSON data. 793 It loads all the data automatically through it's methods. 794 */ 795 class Model { 796 /// Accessors in the model. 797 Accessor[] accessors; 798 /// Animations in the model. 799 Animation[] animations; 800 /// Buffers in the model. 801 Buffer[] buffers; 802 /// BufferViews in the model. 803 BufferView[] bufferViews; 804 /// Meshes in the model. 805 Mesh[] meshes; 806 /// Nodes in the model. 807 Node[] nodes; 808 /// Skins in the model. 809 Skin[] skins; 810 /// Samplers in the model. 811 Sampler[] samplers; 812 813 /// The asset info of the model. 814 Asset asset; 815 816 // Takes in a raw string so you can do whatever they want with your file location. 817 this(string fileLocation, bool debugInfo = true) { 818 //* Model can work with it's internal fields so we don't have to chain them 819 this.debugInfo = debugInfo; 820 this.fileLocation = fileLocation; 821 this.asset = new Asset(); 822 if (debugInfo) { 823 writeln("\nMODEL " ~ fileLocation ~ " INITIALIZED\n"); 824 } 825 } 826 827 /** 828 Automatically loads, decodes, and stores all the implemented JSON data into the model's arrays. 829 Returns loading success. 830 */ 831 bool loadFile(){ 832 if (!this.fileExists()) { 833 writeDebug( 834 "I'm very sorry, but the file:\n" ~ 835 this.fileLocation ~ "\n" ~ 836 "does not exist on the drive. Perhaps you are polling the wrong directory?\n" 837 ); 838 return false; 839 } 840 841 // Turn the raw disk data into a usable JSON object with the std.json library. 842 // Can throw an exception, which we catch and return as false. 843 // Going to be extra nice and throw in a link to the khronos verifier straight in the terminal. 844 if (!this.loadJson()) { 845 writeDebug( 846 "I'm very sorry, but the file:\n"~ 847 this.fileLocation ~ "\n" ~ 848 "appears to be corrupted, please double-check this model with the Khronos GLTF validator.\n" ~ 849 "Link: https://github.khronos.org/glTF-Validator/\n" 850 ); 851 return false; 852 } 853 854 // Now it has to iterate the JSON object and store the data. 855 this.collectJSONInfo(); 856 857 return true; 858 } 859 860 private: 861 862 string fileLocation; 863 bool debugInfo = false; 864 JSONValue jsonData; 865 866 void collectJSONInfo() { 867 // This might look a bit complicated, but we're just iterating the tree of data 868 // We start off with a set of keys and values, accessor, bufferViews, etc 869 // Then we need to go down them because they're packed pretty tight 870 foreach (key,value; this.jsonData.objectNoRef) { 871 872 //! Don't remove this until everything is accounted for 873 // writeln(key); 874 875 //TODO: surround this with try catch, return false on failure along with debug info on which one failed 876 877 // Key's could be corrupted, so we need a default catch all 878 //* key is a string, value is a JSONValue object 879 switch (key) { 880 case "accessors": { 881 this.grabAccessorsData(value); 882 break; 883 } 884 case "bufferViews": { 885 this.grabBufferViewsData(value); 886 break; 887 } 888 case "buffers": { 889 this.grabBuffersData(value); 890 break; 891 } 892 case "meshes": { 893 this.grabMeshesData(value); 894 break; 895 } 896 case "nodes": { 897 this.grabNodesData(value); 898 break; 899 } 900 case "asset": { 901 this.grabAssetData(value); 902 break; 903 } 904 case "skins": { 905 this.grabSkinsData(value); 906 break; 907 } 908 case "animations": { 909 this.grabAnimationsData(value); 910 break; 911 } 912 default: // Unknown 913 } 914 } 915 } 916 917 void grabAnimationsData(JSONValue jsonObject) { 918 919 //* Implementation Note: We are accessing into each animation, like "run", "jump", etc in blender. 920 //* This is why each pass creates a new animation object in the array. 921 922 //* This is explicit to help code-d and to be more readable for control flow 923 //* Key is integer(size_t), value is JSON value 924 foreach (size_t key, JSONValue value; jsonObject.array) { 925 926 // We are assembling this animation 927 Animation animationObject = new Animation(); 928 929 // Now parse the string 930 931 //* Key is string, value is JSON value 932 foreach (string arrayKey, JSONValue arrayValue; value.object) { 933 switch (arrayKey) { 934 // AnimationSampler[] 935 case "samplers": { 936 assert(arrayValue.type == JSONType.array); 937 this.grabAnimationSamplers(animationObject, arrayValue); 938 break; 939 } 940 // AnimationChannel[] 941 case "channels": { 942 assert(arrayValue.type == JSONType.array); 943 this.grabAnimationChannels(animationObject, arrayValue); 944 break; 945 } 946 // String 947 case "name": { 948 assert(arrayValue.type == JSONType..string); 949 animationObject.name = arrayValue.str; 950 break; 951 } 952 default: // Unknown 953 } 954 } 955 956 this.animations ~= animationObject; 957 } 958 } 959 960 void grabAnimationChannels(Animation animationObject, JSONValue jsonObject) { 961 962 //* This is explicit to help code-d and to be more readable for control flow 963 //* Key is integer(size_t), value is JSON value 964 foreach (size_t key, JSONValue value; jsonObject.array) { 965 966 // We are assembling this animation channel 967 AnimationChannel animationChannelObject = new AnimationChannel(); 968 969 // Now parse the string 970 971 //* Key is string, value is JSON value 972 foreach (string arrayKey, JSONValue arrayValue; value.object) { 973 switch (arrayKey) { 974 // Integer 975 case "sampler": { 976 assert(arrayValue.type == JSONType.integer); 977 animationChannelObject.sampler = cast(int)arrayValue.integer; 978 break; 979 } 980 // JSON Value 981 case "target": { 982 assert(arrayValue.type == JSONType.object); 983 this.grabAnimationChannelTarget(animationChannelObject, arrayValue); 984 break; 985 } 986 default: 987 } 988 } 989 990 animationObject.channels ~= animationChannelObject; 991 } 992 } 993 994 void grabAnimationChannelTarget(AnimationChannel animationChannelObject, JSONValue jsonObject) { 995 //* This is explicit to help code-d and to be more readable for control flow 996 //* Key is string, value is JSON value 997 foreach (string key, JSONValue value; jsonObject.object) { 998 switch (key) { 999 // Integer 1000 case "node": { 1001 assert(value.type == JSONType.integer); 1002 animationChannelObject.target_node = cast(int)value.integer; 1003 break; 1004 } 1005 // String 1006 case "path": { 1007 assert(value.type == JSONType..string); 1008 animationChannelObject.target_path = value.str; 1009 break; 1010 } 1011 default: // Unknown 1012 } 1013 } 1014 } 1015 1016 void grabAnimationSamplers(Animation animationObject, JSONValue jsonObject) { 1017 1018 //* This is explicit to help code-d and to be more readable for control flow 1019 //* Key is integer(size_t), value is JSON value 1020 foreach (size_t key, JSONValue value; jsonObject.array) { 1021 1022 // We are assembling this animation sampler 1023 AnimationSampler animationSamplerObject = new AnimationSampler(); 1024 1025 // Now parse the string 1026 1027 //* Key is string, value is JSON value 1028 foreach (string arrayKey, JSONValue arrayValue; value.object) { 1029 switch (arrayKey) { 1030 // Integer 1031 case "input": { 1032 assert(arrayValue.type == JSONType.integer); 1033 animationSamplerObject.input = cast(int)arrayValue.integer; 1034 break; 1035 } 1036 // String 1037 case "interpolation": { 1038 assert(arrayValue.type == JSONType..string); 1039 animationSamplerObject.interpolation = arrayValue.str; 1040 break; 1041 } 1042 // Integer 1043 case "output": { 1044 assert(arrayValue.type == JSONType.integer); 1045 animationSamplerObject.output = cast(int)arrayValue.integer; 1046 break; 1047 } 1048 default: // Unknown 1049 } 1050 } 1051 1052 animationObject.samplers ~= animationSamplerObject; 1053 } 1054 } 1055 1056 void grabSkinsData(JSONValue jsonObject) { 1057 1058 //* This is explicit to help code-d and to be more readable for control flow 1059 //* Key is integer(size_t), value is JSON value 1060 foreach (size_t key, JSONValue value; jsonObject.array) { 1061 1062 // We are assembling this skin 1063 Skin skinObject = new Skin(); 1064 1065 // Now parse the string 1066 1067 //* Key is string, value is JSON value 1068 foreach (string arrayKey, JSONValue arrayValue; value.object) { 1069 switch (arrayKey) { 1070 // String 1071 case "name": { 1072 assert(arrayValue.type == JSONType..string); 1073 skinObject.name = arrayValue.str; 1074 break; 1075 } 1076 // Integer[] 1077 case "joints": { 1078 assert(arrayValue.type == JSONType.array); 1079 foreach(size_t k, JSONValue v; arrayValue.array){ 1080 assert(v.type == JSONType.integer); 1081 skinObject.joints ~= cast(int)v.integer; 1082 } 1083 break; 1084 } 1085 // Integer - Points to Accessor 1086 case "inverseBindMatrices": { 1087 assert(arrayValue.type == JSONType.integer); 1088 skinObject.inverseBindMatrices = cast(int)arrayValue.integer; 1089 break; 1090 } 1091 // Integer - Points to root node (root joint) 1092 case "skeleton": { 1093 assert(arrayValue.type == JSONType.integer); 1094 skinObject.skeleton = cast(int)arrayValue.integer; 1095 break; 1096 } 1097 default: // Unknown 1098 } 1099 } 1100 1101 this.skins ~= skinObject; 1102 } 1103 } 1104 1105 void grabNodesData(JSONValue jsonObject) { 1106 1107 //* This is explicit to help code-d and to be more readable for control flow 1108 //* Key is integer(size_t), value is JSON value 1109 foreach (size_t key, JSONValue value; jsonObject.array) { 1110 1111 // We are assembling this node 1112 Node nodeObject = new Node(); 1113 1114 // Now parse the string 1115 1116 //* Key is string, value is JSON value 1117 foreach (string arrayKey, JSONValue arrayValue; value.object) { 1118 switch (arrayKey) { 1119 // Integer 1120 case "camera": { 1121 assert(arrayValue.type == JSONType.integer); 1122 nodeObject.camera = cast(int)arrayValue.integer; 1123 break; 1124 } 1125 // Integer[] 1126 case "children": { 1127 assert(arrayValue.type == JSONType.array); 1128 foreach(size_t k, JSONValue v; arrayValue.array){ 1129 assert(v.type == JSONType.integer); 1130 nodeObject.children ~= cast(int)v.integer; 1131 } 1132 break; 1133 } 1134 // Integer 1135 case "skin": { 1136 assert(arrayValue.type == JSONType.integer); 1137 nodeObject.skin = cast(int)arrayValue.integer; 1138 break; 1139 } 1140 // Double[16] (matrix4) 1141 case "matrix": { 1142 assert(arrayValue.type == JSONType.array); 1143 foreach(size_t k, JSONValue v; arrayValue.array){ 1144 nodeObject.matrix ~= grabDouble(v); 1145 } 1146 break; 1147 } 1148 // Integer 1149 case "mesh": { 1150 assert(arrayValue.type == JSONType.integer); 1151 nodeObject.mesh = cast(int)arrayValue.integer; 1152 break; 1153 } 1154 // Double[4] (quaternion) 1155 case "rotation": { 1156 assert(arrayValue.type == JSONType.array); 1157 foreach(size_t k, JSONValue v; arrayValue.array){ 1158 nodeObject.rotation ~= this.grabDouble(v); 1159 } 1160 break; 1161 } 1162 // Double[3] (vector3) 1163 case "scale": { 1164 assert(arrayValue.type == JSONType.array); 1165 foreach(size_t k, JSONValue v; arrayValue.array){ 1166 nodeObject.scale ~= this.grabDouble(v); 1167 } 1168 break; 1169 } 1170 // Double[3] (vector3) 1171 case "translation": { 1172 assert(arrayValue.type == JSONType.array); 1173 foreach(size_t k, JSONValue v; arrayValue.array){ 1174 nodeObject.translation ~= this.grabDouble(v); 1175 } 1176 break; 1177 } 1178 // Double[] 1179 case "weights": { 1180 assert(arrayValue.type == JSONType.array); 1181 foreach(size_t k, JSONValue v; arrayValue.array){ 1182 nodeObject.weights ~= this.grabDouble(v); 1183 } 1184 break; 1185 } 1186 // String 1187 case "name": { 1188 assert(arrayValue.type == JSONType..string); 1189 nodeObject.name = arrayValue.str; 1190 break; 1191 } 1192 default: // Unknown 1193 1194 } 1195 } 1196 1197 this.nodes ~= nodeObject; 1198 } 1199 } 1200 1201 void grabMeshesData(JSONValue jsonObject) { 1202 1203 //* This is explicit to help code-d and to be more readable for control flow 1204 //* Key is integer(size_t), value is JSON value 1205 foreach (size_t key, JSONValue value; jsonObject.array) { 1206 1207 //* Implementation note: Meshes are a special type. 1208 //* They contain a vector of Primitive objects. 1209 1210 // We are assembling this mesh 1211 Mesh meshObject = new Mesh(); 1212 1213 // Now parse the string 1214 1215 //* Key is string, value is JSON value 1216 foreach (string arrayKey, JSONValue arrayValue; value.object) { 1217 switch (arrayKey) { 1218 // Json Object 1219 case "primitives": { 1220 assert(arrayValue.type == JSONType.array); 1221 // Goes to a primitive assembler because it's complex. 1222 // This returns an array of primitives, automatically assigns it. 1223 meshObject.primitives = this.grabPrimitiveData(arrayValue); 1224 break; 1225 } 1226 // Double[] 1227 case "weights": { 1228 assert(arrayValue.type == JSONType.array); 1229 foreach(size_t k, JSONValue v; arrayValue.array){ 1230 meshObject.weights ~= this.grabDouble(v); 1231 } 1232 break; 1233 } 1234 // String 1235 case "name": { 1236 assert(arrayValue.type == JSONType..string); 1237 meshObject.name = arrayValue.str; 1238 break; 1239 } 1240 default: 1241 } 1242 } 1243 this.meshes ~= meshObject; 1244 } 1245 } 1246 1247 Primitive[] grabPrimitiveData(JSONValue jsonObject) { 1248 1249 // This is assembling an array of primitives 1250 Primitive[] returningPrimitives; 1251 1252 //* This is explicit to help code-d and to be more readable for control flow 1253 //* Key is integer(size_t), value is JSON value 1254 foreach (size_t key, JSONValue value; jsonObject.array) { 1255 1256 // We are assembling this primitive 1257 Primitive primitiveObject = new Primitive(); 1258 1259 // Now parse the string 1260 1261 //* Key is string, value is JSON value 1262 foreach (string arrayKey, JSONValue arrayValue; value.object) { 1263 switch (arrayKey) { 1264 // Integer[String] Associative Array 1265 case "attributes": { 1266 assert(arrayValue.type == JSONType.object); 1267 foreach (string attributeKey, JSONValue attributeValue; arrayValue) { 1268 assert(attributeValue.type == JSONType.integer); 1269 primitiveObject.attributes[attributeKey] = cast(int)attributeValue.integer; 1270 } 1271 break; 1272 } 1273 // Integer 1274 case "indices": { 1275 assert(arrayValue.type == JSONType.integer); 1276 primitiveObject.indices = cast(int)arrayValue.integer; 1277 break; 1278 } 1279 // Integer 1280 case "material": { 1281 assert(arrayValue.type == JSONType.integer); 1282 primitiveObject.material = cast(int)arrayValue.integer; 1283 break; 1284 } 1285 // Integer 1286 case "mode": { 1287 assert(arrayValue.type == JSONType.integer); 1288 primitiveObject.mode = cast(int)arrayValue.integer; 1289 break; 1290 } 1291 // Integer[] 1292 case "targets": { 1293 // TODO 1294 break; 1295 } 1296 default: // Unknown 1297 } 1298 } 1299 returningPrimitives ~= primitiveObject; 1300 } 1301 return returningPrimitives; 1302 } 1303 1304 void grabBuffersData(JSONValue jsonObject) { 1305 1306 //* This is explicit to help code-d and to be more readable for control flow 1307 //* Key is integer(size_t), value is JSON value 1308 foreach (size_t key, JSONValue value; jsonObject.array) { 1309 1310 // We are assembling this buffer 1311 Buffer bufferObject = new Buffer(); 1312 1313 // Now parse the string 1314 1315 //* Key is string, value is JSON value 1316 foreach (string arrayKey, JSONValue arrayValue; value.object) { 1317 switch (arrayKey) { 1318 // String - REQUIRED to be a string of data 1319 case "uri": { 1320 assert(arrayValue.type == JSONType..string); 1321 // Needs to strip out this header info 1322 string data = arrayValue.str.replace("data:application/octet-stream;base64,", ""); 1323 // If it's a bin, fail state 1324 assert(data.length != arrayValue.str.length); 1325 // Now decode it 1326 bufferObject.data = Base64.decode(data); 1327 break; 1328 } 1329 // Integer 1330 case "byteLength": { 1331 assert(arrayValue.type == JSONType.integer); 1332 bufferObject.byteLength = cast(int)arrayValue.integer; 1333 break; 1334 } 1335 // String 1336 case "name": { 1337 assert(arrayValue.type == JSONType..string); 1338 bufferObject.name = arrayValue.str; 1339 break; 1340 } 1341 default: // Unknown 1342 } 1343 } 1344 this.buffers ~= bufferObject; 1345 } 1346 } 1347 1348 void grabBufferViewsData(JSONValue jsonObject) { 1349 1350 //* This is explicit to help code-d and to be more readable for control flow 1351 //* Key is integer(size_t), value is JSON value 1352 foreach (size_t key, JSONValue value; jsonObject.array) { 1353 1354 // We are assembling this bufferView 1355 BufferView bufferViewObject = new BufferView(); 1356 1357 // Now parse the string 1358 1359 //* Key is string, value is JSON value 1360 foreach (string arrayKey, JSONValue arrayValue; value.object) { 1361 switch (arrayKey) { 1362 // Integer 1363 case "byteOffset": { 1364 assert(arrayValue.type == JSONType.integer); 1365 bufferViewObject.byteOffset = cast(int)arrayValue.integer; 1366 break; 1367 } 1368 // Integer, alias to TINYGLTF_TARGET_ 1369 case "target": { 1370 assert(arrayValue.type == JSONType.integer); 1371 bufferViewObject.target = cast(int)arrayValue.integer; 1372 break; 1373 } 1374 // Integer 1375 case "buffer": { 1376 assert(arrayValue.type == JSONType.integer); 1377 bufferViewObject.buffer = cast(int)arrayValue.integer; 1378 break; 1379 } 1380 // Integer 1381 case "byteLength": { 1382 assert(arrayValue.type == JSONType.integer); 1383 bufferViewObject.byteLength = cast(int)arrayValue.integer; 1384 break; 1385 } 1386 // String 1387 case "name": { 1388 assert(arrayValue.type == JSONType..string); 1389 bufferViewObject.name = arrayValue.str; 1390 break; 1391 } 1392 default: // Unknown 1393 } 1394 } 1395 this.bufferViews ~= bufferViewObject; 1396 } 1397 } 1398 1399 void grabAccessorsData(JSONValue jsonObject) { 1400 1401 //* This is explicit to help code-d and to be more readable for control flow 1402 //* Key is integer(size_t), value is JSON value 1403 foreach (size_t key, JSONValue value; jsonObject.array) { 1404 1405 // We are assembling this accessor 1406 Accessor accessorObject = new Accessor(); 1407 1408 // Now parse the string 1409 1410 //* Key is string, value is JSON value 1411 foreach (string arrayKey, JSONValue arrayValue; value.object) { 1412 1413 switch (arrayKey) { 1414 // Integer 1415 case "bufferView": { 1416 assert(arrayValue.type() == JSONType.integer); 1417 accessorObject.bufferView = cast(int)arrayValue.integer; 1418 break; 1419 } 1420 // Integer 1421 case "byteOffset": { 1422 assert(arrayValue.type() == JSONType.integer); 1423 accessorObject.byteOffset = cast(int)arrayValue.integer; 1424 break; 1425 1426 } 1427 // Integer, alias to TINYGLTF_COMPONENT_TYPE_ 1428 case "componentType": { 1429 assert(arrayValue.type() == JSONType.integer); 1430 accessorObject.componentType = cast(int)arrayValue.integer; 1431 break; 1432 } 1433 // Integer 1434 case "count": { 1435 assert(arrayValue.type() == JSONType.integer); 1436 accessorObject.count = cast(int)arrayValue.integer; 1437 break; 1438 } 1439 // Double[] 1440 case "min": { 1441 assert(arrayValue.type() == JSONType.array); 1442 foreach (k,JSONValue v; arrayValue.array) { 1443 accessorObject.minValues ~= this.grabDouble(v); 1444 } 1445 break; 1446 } 1447 // Double[] 1448 case "max": { 1449 assert(arrayValue.type() == JSONType.array); 1450 foreach (k,JSONValue v; arrayValue.array) { 1451 accessorObject.maxValues ~= this.grabDouble(v); 1452 } 1453 break; 1454 } 1455 // String 1456 case "type": { 1457 assert(arrayValue.type == JSONType..string); 1458 // Assign the integral value of the enum 1459 switch (arrayValue.str) { 1460 case "VEC2": { 1461 accessorObject.type = TINYGLTF_TYPE_VEC2; 1462 break; 1463 } 1464 case "VEC3": { 1465 accessorObject.type = TINYGLTF_TYPE_VEC3; 1466 break; 1467 } 1468 case "VEC4": { 1469 accessorObject.type = TINYGLTF_TYPE_VEC4; 1470 break; 1471 } 1472 case "MAT2": { 1473 accessorObject.type = TINYGLTF_TYPE_MAT2; 1474 break; 1475 } 1476 case "MAT3": { 1477 accessorObject.type = TINYGLTF_TYPE_MAT3; 1478 break; 1479 } 1480 case "MAT4": { 1481 accessorObject.type = TINYGLTF_TYPE_MAT4; 1482 break; 1483 } 1484 case "SCALAR": { 1485 accessorObject.type = TINYGLTF_TYPE_SCALAR; 1486 break; 1487 } 1488 case "VECTOR": { 1489 accessorObject.type = TINYGLTF_TYPE_VECTOR; 1490 break; 1491 } 1492 case "MATRIX": { 1493 accessorObject.type = TINYGLTF_TYPE_MATRIX; 1494 break; 1495 } 1496 default: // Unknown 1497 } 1498 break; 1499 } 1500 case "name": { 1501 assert(arrayValue.type == JSONType..string); 1502 accessorObject.name = arrayValue.str; 1503 break; 1504 } 1505 default: // UNKNOWN 1506 } 1507 } 1508 1509 // Finally dump the accessor in 1510 this.accessors ~= accessorObject; 1511 } 1512 } 1513 1514 void grabAssetData(JSONValue jsonObject) { 1515 1516 //* This is explicit to help code-d and to be more readable for control flow 1517 1518 //* Implementation note: There is only one asset so this looks a bit different 1519 1520 // We are assembling this asset 1521 Asset assetObject = new Asset(); 1522 1523 // Now parse the string 1524 1525 //* Key is string, value is JSON value 1526 foreach (string arrayKey, JSONValue arrayValue; jsonObject.object) { 1527 switch (arrayKey) { 1528 case "copyright": { 1529 assert(arrayValue.type == JSONType..string); 1530 assetObject.copyright = arrayValue.str; 1531 break; 1532 } 1533 case "generator": { 1534 assert(arrayValue.type == JSONType..string); 1535 assetObject.generator = arrayValue.str; 1536 break; 1537 } 1538 case "version": { 1539 assert(arrayValue.type == JSONType..string); 1540 assetObject.version_ = arrayValue.str; 1541 break; 1542 } 1543 case "minVersion": { 1544 assert(arrayValue.type == JSONType..string); 1545 assetObject.minVersion = arrayValue.str; 1546 break; 1547 } 1548 default: // Unknown 1549 } 1550 } 1551 this.asset = assetObject; 1552 } 1553 1554 //* This is just a passthrough to keep it looking neat :) 1555 bool fileExists() { 1556 return exists(this.fileLocation); 1557 } 1558 1559 // Returns parsing the JSON success; 1560 bool loadJson() { 1561 void[] rawData; 1562 string jsonString; 1563 1564 try { 1565 rawData = read(this.fileLocation); 1566 } catch (Exception e) { 1567 return false; 1568 } 1569 1570 try { 1571 jsonString = cast(string)rawData; 1572 } catch (Exception e) { 1573 return false; 1574 } 1575 1576 try { 1577 this.jsonData = parseJSON(jsonString); 1578 } catch (Exception e) { 1579 return false; 1580 } 1581 1582 return true; 1583 } 1584 1585 // std.json thinks that 1.0 and 0.0 is integer so we have to work with it 1586 static double grabDouble(JSONValue input) { 1587 if (input.type == JSONType.float_) { 1588 return input.floating; 1589 } else if (input.type == JSONType.integer) { 1590 return cast(double)input.integer; 1591 } 1592 // Something went HORRIBLY wrong with the model. 1593 throw new Exception("THIS MODEL HAS A DIFFERENT TYPE THAT'S NOT INTEGRAL AS A DOUBLE!"); 1594 } 1595 1596 1597 //*===================== DEBUGGING TOOLS ============================ 1598 void writeDebugHeader() { 1599 writeln( 1600 "=========================\n" ~ 1601 "DEBUG INFO\n" ~ 1602 "=========================\n" 1603 ); 1604 } 1605 void writeDebug(string input) { 1606 if (!this.debugInfo) { 1607 return; 1608 } 1609 writeDebugHeader(); 1610 writeln(input); 1611 writeDebugFooter(); 1612 } 1613 void writeDebugFooter() { 1614 writeln("=========================\n"); 1615 } 1616 } 1617 1618 1619 unittest { 1620 // Test fail state and disabling debug info 1621 Model failedModel = new Model("This is a failure test.", false); 1622 assert(failedModel !is null); 1623 assert(failedModel.loadFile() == false); 1624 1625 writeln("\nFAILURE PASS!\n"); 1626 1627 // Now test loading state again with a known model 1628 Model successModel = new Model("models/cube_embedded/cube.gltf"); 1629 assert(successModel !is null); 1630 assert(successModel.loadFile() == true); 1631 1632 writeln("\nSUCCESS PASS\n"); 1633 1634 // Now test a corrupted model. 1635 Model corruptedModel = new Model("models/missing_brace/json_missing_brace.gltf"); 1636 assert(corruptedModel.loadFile() == false); 1637 1638 writeln("\nCORRUPTED PASS\n"); 1639 1640 // Now bring Minetest Sam onboard. 1641 Model sam = new Model("models/sam/sam.gltf"); 1642 assert(sam.loadFile() == true); 1643 1644 writeln("\nSAM PASSED :)\n"); 1645 1646 }