// This file is part of gltfpack; see gltfpack.h for version/license details #include "gltfpack.h" #include #include #include #include static const char* componentType(cgltf_component_type type) { switch (type) { case cgltf_component_type_r_8: return "5120"; case cgltf_component_type_r_8u: return "5121"; case cgltf_component_type_r_16: return "5122"; case cgltf_component_type_r_16u: return "5123"; case cgltf_component_type_r_32u: return "5125"; case cgltf_component_type_r_32f: return "5126"; default: return "0"; } } static const char* shapeType(cgltf_type type) { switch (type) { case cgltf_type_scalar: return "SCALAR"; case cgltf_type_vec2: return "VEC2"; case cgltf_type_vec3: return "VEC3"; case cgltf_type_vec4: return "VEC4"; case cgltf_type_mat2: return "MAT2"; case cgltf_type_mat3: return "MAT3"; case cgltf_type_mat4: return "MAT4"; default: return ""; } } const char* attributeType(cgltf_attribute_type type) { switch (type) { case cgltf_attribute_type_position: return "POSITION"; case cgltf_attribute_type_normal: return "NORMAL"; case cgltf_attribute_type_tangent: return "TANGENT"; case cgltf_attribute_type_texcoord: return "TEXCOORD"; case cgltf_attribute_type_color: return "COLOR"; case cgltf_attribute_type_joints: return "JOINTS"; case cgltf_attribute_type_weights: return "WEIGHTS"; default: return "ATTRIBUTE"; } } const char* animationPath(cgltf_animation_path_type type) { switch (type) { case cgltf_animation_path_type_translation: return "translation"; case cgltf_animation_path_type_rotation: return "rotation"; case cgltf_animation_path_type_scale: return "scale"; case cgltf_animation_path_type_weights: return "weights"; default: return ""; } } static const char* lightType(cgltf_light_type type) { switch (type) { case cgltf_light_type_directional: return "directional"; case cgltf_light_type_point: return "point"; case cgltf_light_type_spot: return "spot"; default: return ""; } } static const char* alphaMode(cgltf_alpha_mode mode) { switch (mode) { case cgltf_alpha_mode_opaque: return "OPAQUE"; case cgltf_alpha_mode_mask: return "MASK"; case cgltf_alpha_mode_blend: return "BLEND"; default: return ""; } } static const char* compressionMode(BufferView::Compression mode) { switch (mode) { case BufferView::Compression_Attribute: return "ATTRIBUTES"; case BufferView::Compression_Index: return "TRIANGLES"; case BufferView::Compression_IndexSequence: return "INDICES"; default: return ""; } } static const char* compressionFilter(StreamFormat::Filter filter) { switch (filter) { case StreamFormat::Filter_None: return "NONE"; case StreamFormat::Filter_Oct: return "OCTAHEDRAL"; case StreamFormat::Filter_Quat: return "QUATERNION"; case StreamFormat::Filter_Exp: return "EXPONENTIAL"; default: return ""; } } static void writeTextureInfo(std::string& json, const cgltf_data* data, const cgltf_texture_view& view, const QuantizationTexture* qt, const char* scale = NULL) { assert(view.texture); bool has_transform = false; cgltf_texture_transform transform = {}; transform.scale[0] = transform.scale[1] = 1.f; if (hasValidTransform(view)) { transform = view.transform; has_transform = true; } if (qt) { transform.offset[0] += qt->offset[0]; transform.offset[1] += qt->offset[1]; transform.scale[0] *= qt->scale[0] / float((1 << qt->bits) - 1) * (qt->normalized ? 65535.f : 1.f); transform.scale[1] *= qt->scale[1] / float((1 << qt->bits) - 1) * (qt->normalized ? 65535.f : 1.f); has_transform = true; } append(json, "{\"index\":"); append(json, size_t(view.texture - data->textures)); if (view.texcoord != 0) { append(json, ",\"texCoord\":"); append(json, size_t(view.texcoord)); } if (scale && view.scale != 1) { append(json, ",\""); append(json, scale); append(json, "\":"); append(json, view.scale); } if (has_transform) { append(json, ",\"extensions\":{\"KHR_texture_transform\":{"); append(json, "\"offset\":["); append(json, transform.offset[0]); append(json, ","); append(json, transform.offset[1]); append(json, "],\"scale\":["); append(json, transform.scale[0]); append(json, ","); append(json, transform.scale[1]); append(json, "]"); if (transform.rotation != 0.f) { append(json, ",\"rotation\":"); append(json, transform.rotation); } append(json, "}}"); } append(json, "}"); } static const float white[4] = {1, 1, 1, 1}; static const float black[4] = {0, 0, 0, 0}; static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_pbr_metallic_roughness& pbr, const QuantizationTexture* qt) { comma(json); append(json, "\"pbrMetallicRoughness\":{"); if (memcmp(pbr.base_color_factor, white, 16) != 0) { comma(json); append(json, "\"baseColorFactor\":["); append(json, pbr.base_color_factor[0]); append(json, ","); append(json, pbr.base_color_factor[1]); append(json, ","); append(json, pbr.base_color_factor[2]); append(json, ","); append(json, pbr.base_color_factor[3]); append(json, "]"); } if (pbr.base_color_texture.texture) { comma(json); append(json, "\"baseColorTexture\":"); writeTextureInfo(json, data, pbr.base_color_texture, qt); } if (pbr.metallic_factor != 1) { comma(json); append(json, "\"metallicFactor\":"); append(json, pbr.metallic_factor); } if (pbr.roughness_factor != 1) { comma(json); append(json, "\"roughnessFactor\":"); append(json, pbr.roughness_factor); } if (pbr.metallic_roughness_texture.texture) { comma(json); append(json, "\"metallicRoughnessTexture\":"); writeTextureInfo(json, data, pbr.metallic_roughness_texture, qt); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_pbr_specular_glossiness& pbr, const QuantizationTexture* qt) { comma(json); append(json, "\"KHR_materials_pbrSpecularGlossiness\":{"); if (pbr.diffuse_texture.texture) { comma(json); append(json, "\"diffuseTexture\":"); writeTextureInfo(json, data, pbr.diffuse_texture, qt); } if (pbr.specular_glossiness_texture.texture) { comma(json); append(json, "\"specularGlossinessTexture\":"); writeTextureInfo(json, data, pbr.specular_glossiness_texture, qt); } if (memcmp(pbr.diffuse_factor, white, 16) != 0) { comma(json); append(json, "\"diffuseFactor\":["); append(json, pbr.diffuse_factor[0]); append(json, ","); append(json, pbr.diffuse_factor[1]); append(json, ","); append(json, pbr.diffuse_factor[2]); append(json, ","); append(json, pbr.diffuse_factor[3]); append(json, "]"); } if (memcmp(pbr.specular_factor, white, 12) != 0) { comma(json); append(json, "\"specularFactor\":["); append(json, pbr.specular_factor[0]); append(json, ","); append(json, pbr.specular_factor[1]); append(json, ","); append(json, pbr.specular_factor[2]); append(json, "]"); } if (pbr.glossiness_factor != 1) { comma(json); append(json, "\"glossinessFactor\":"); append(json, pbr.glossiness_factor); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_clearcoat& cc, const QuantizationTexture* qt) { comma(json); append(json, "\"KHR_materials_clearcoat\":{"); if (cc.clearcoat_texture.texture) { comma(json); append(json, "\"clearcoatTexture\":"); writeTextureInfo(json, data, cc.clearcoat_texture, qt); } if (cc.clearcoat_roughness_texture.texture) { comma(json); append(json, "\"clearcoatRoughnessTexture\":"); writeTextureInfo(json, data, cc.clearcoat_roughness_texture, qt); } if (cc.clearcoat_normal_texture.texture) { comma(json); append(json, "\"clearcoatNormalTexture\":"); writeTextureInfo(json, data, cc.clearcoat_normal_texture, qt, "scale"); } if (cc.clearcoat_factor != 0) { comma(json); append(json, "\"clearcoatFactor\":"); append(json, cc.clearcoat_factor); } if (cc.clearcoat_factor != 0) { comma(json); append(json, "\"clearcoatRoughnessFactor\":"); append(json, cc.clearcoat_roughness_factor); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_transmission& tm, const QuantizationTexture* qt) { comma(json); append(json, "\"KHR_materials_transmission\":{"); if (tm.transmission_texture.texture) { comma(json); append(json, "\"transmissionTexture\":"); writeTextureInfo(json, data, tm.transmission_texture, qt); } if (tm.transmission_factor != 0) { comma(json); append(json, "\"transmissionFactor\":"); append(json, tm.transmission_factor); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_ior& tm, const QuantizationTexture* qt) { (void)data; (void)qt; comma(json); append(json, "\"KHR_materials_ior\":{"); append(json, "\"ior\":"); append(json, tm.ior); append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_specular& tm, const QuantizationTexture* qt) { comma(json); append(json, "\"KHR_materials_specular\":{"); if (tm.specular_texture.texture) { comma(json); append(json, "\"specularTexture\":"); writeTextureInfo(json, data, tm.specular_texture, qt); } if (tm.specular_color_texture.texture) { comma(json); append(json, "\"specularColorTexture\":"); writeTextureInfo(json, data, tm.specular_color_texture, qt); } if (tm.specular_factor != 1) { comma(json); append(json, "\"specularFactor\":"); append(json, tm.specular_factor); } if (memcmp(tm.specular_color_factor, white, 12) != 0) { comma(json); append(json, "\"specularColorFactor\":["); append(json, tm.specular_color_factor[0]); append(json, ","); append(json, tm.specular_color_factor[1]); append(json, ","); append(json, tm.specular_color_factor[2]); append(json, "]"); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_sheen& tm, const QuantizationTexture* qt) { comma(json); append(json, "\"KHR_materials_sheen\":{"); if (tm.sheen_color_texture.texture) { comma(json); append(json, "\"sheenColorTexture\":"); writeTextureInfo(json, data, tm.sheen_color_texture, qt); } if (tm.sheen_roughness_texture.texture) { comma(json); append(json, "\"sheenRoughnessTexture\":"); writeTextureInfo(json, data, tm.sheen_roughness_texture, qt); } if (memcmp(tm.sheen_color_factor, black, 12) != 0) { comma(json); append(json, "\"sheenColorFactor\":["); append(json, tm.sheen_color_factor[0]); append(json, ","); append(json, tm.sheen_color_factor[1]); append(json, ","); append(json, tm.sheen_color_factor[2]); append(json, "]"); } if (tm.sheen_roughness_factor != 0) { comma(json); append(json, "\"sheenRoughnessFactor\":"); append(json, tm.sheen_roughness_factor); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_volume& tm, const QuantizationPosition* qp, const QuantizationTexture* qt) { comma(json); append(json, "\"KHR_materials_volume\":{"); if (tm.thickness_texture.texture) { comma(json); append(json, "\"thicknessTexture\":"); writeTextureInfo(json, data, tm.thickness_texture, qt); } if (tm.thickness_factor != 0) { // thickness is in mesh coordinate space which is rescaled by quantization float node_scale = qp ? qp->scale / float((1 << qp->bits) - 1) * (qp->normalized ? 65535.f : 1.f) : 1.f; comma(json); append(json, "\"thicknessFactor\":"); append(json, tm.thickness_factor / node_scale); } if (memcmp(tm.attenuation_color, white, 12) != 0) { comma(json); append(json, "\"attenuationColor\":["); append(json, tm.attenuation_color[0]); append(json, ","); append(json, tm.attenuation_color[1]); append(json, ","); append(json, tm.attenuation_color[2]); append(json, "]"); } if (tm.attenuation_distance != FLT_MAX) { comma(json); append(json, "\"attenuationDistance\":"); append(json, tm.attenuation_distance); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_emissive_strength& tm) { (void)data; comma(json); append(json, "\"KHR_materials_emissive_strength\":{"); if (tm.emissive_strength != 1) { comma(json); append(json, "\"emissiveStrength\":"); append(json, tm.emissive_strength); } append(json, "}"); } static void writeMaterialComponent(std::string& json, const cgltf_data* data, const cgltf_iridescence& tm, const QuantizationTexture* qt) { comma(json); append(json, "\"KHR_materials_iridescence\":{"); if (tm.iridescence_factor != 0) { comma(json); append(json, "\"iridescenceFactor\":"); append(json, tm.iridescence_factor); } if (tm.iridescence_texture.texture) { comma(json); append(json, "\"iridescenceTexture\":"); writeTextureInfo(json, data, tm.iridescence_texture, qt); } if (tm.iridescence_ior != 1.3f) { comma(json); append(json, "\"iridescenceIor\":"); append(json, tm.iridescence_ior); } if (tm.iridescence_thickness_min != 100.f) { comma(json); append(json, "\"iridescenceThicknessMinimum\":"); append(json, tm.iridescence_thickness_min); } if (tm.iridescence_thickness_max != 400.f) { comma(json); append(json, "\"iridescenceThicknessMaximum\":"); append(json, tm.iridescence_thickness_max); } if (tm.iridescence_thickness_texture.texture) { comma(json); append(json, "\"iridescenceThicknessTexture\":"); writeTextureInfo(json, data, tm.iridescence_thickness_texture, qt); } append(json, "}"); } void writeMaterial(std::string& json, const cgltf_data* data, const cgltf_material& material, const QuantizationPosition* qp, const QuantizationTexture* qt) { if (material.name && *material.name) { comma(json); append(json, "\"name\":\""); append(json, material.name); append(json, "\""); } if (material.has_pbr_metallic_roughness) { writeMaterialComponent(json, data, material.pbr_metallic_roughness, qt); } if (material.normal_texture.texture) { comma(json); append(json, "\"normalTexture\":"); writeTextureInfo(json, data, material.normal_texture, qt, "scale"); } if (material.occlusion_texture.texture) { comma(json); append(json, "\"occlusionTexture\":"); writeTextureInfo(json, data, material.occlusion_texture, qt, "strength"); } if (material.emissive_texture.texture) { comma(json); append(json, "\"emissiveTexture\":"); writeTextureInfo(json, data, material.emissive_texture, qt); } if (memcmp(material.emissive_factor, black, 12) != 0) { comma(json); append(json, "\"emissiveFactor\":["); append(json, material.emissive_factor[0]); append(json, ","); append(json, material.emissive_factor[1]); append(json, ","); append(json, material.emissive_factor[2]); append(json, "]"); } if (material.alpha_mode != cgltf_alpha_mode_opaque) { comma(json); append(json, "\"alphaMode\":\""); append(json, alphaMode(material.alpha_mode)); append(json, "\""); } if (material.alpha_cutoff != 0.5f) { comma(json); append(json, "\"alphaCutoff\":"); append(json, material.alpha_cutoff); } if (material.double_sided) { comma(json); append(json, "\"doubleSided\":true"); } if (material.has_pbr_specular_glossiness || material.has_clearcoat || material.has_transmission || material.has_ior || material.has_specular || material.has_sheen || material.has_volume || material.has_emissive_strength || material.has_iridescence || material.unlit) { comma(json); append(json, "\"extensions\":{"); if (material.has_pbr_specular_glossiness) { writeMaterialComponent(json, data, material.pbr_specular_glossiness, qt); } if (material.has_clearcoat) { writeMaterialComponent(json, data, material.clearcoat, qt); } if (material.has_transmission) { writeMaterialComponent(json, data, material.transmission, qt); } if (material.has_ior) { writeMaterialComponent(json, data, material.ior, qt); } if (material.has_specular) { writeMaterialComponent(json, data, material.specular, qt); } if (material.has_sheen) { writeMaterialComponent(json, data, material.sheen, qt); } if (material.has_volume) { writeMaterialComponent(json, data, material.volume, qp, qt); } if (material.has_emissive_strength) { writeMaterialComponent(json, data, material.emissive_strength); } if (material.has_iridescence) { writeMaterialComponent(json, data, material.iridescence, qt); } if (material.unlit) { comma(json); append(json, "\"KHR_materials_unlit\":{}"); } append(json, "}"); } } size_t getBufferView(std::vector& views, BufferView::Kind kind, StreamFormat::Filter filter, BufferView::Compression compression, size_t stride, int variant) { if (variant >= 0) { for (size_t i = 0; i < views.size(); ++i) { BufferView& v = views[i]; if (v.kind == kind && v.filter == filter && v.compression == compression && v.stride == stride && v.variant == variant) return i; } } BufferView view = {kind, filter, compression, stride, variant}; views.push_back(view); return views.size() - 1; } void writeBufferView(std::string& json, BufferView::Kind kind, StreamFormat::Filter filter, size_t count, size_t stride, size_t bin_offset, size_t bin_size, BufferView::Compression compression, size_t compressed_offset, size_t compressed_size) { assert(bin_size == count * stride); // when compression is enabled, we store uncompressed data in buffer 1 and compressed data in buffer 0 // when compression is disabled, we store uncompressed data in buffer 0 size_t buffer = compression != BufferView::Compression_None ? 1 : 0; append(json, "{\"buffer\":"); append(json, buffer); append(json, ",\"byteOffset\":"); append(json, bin_offset); append(json, ",\"byteLength\":"); append(json, bin_size); if (kind == BufferView::Kind_Vertex) { append(json, ",\"byteStride\":"); append(json, stride); } if (kind == BufferView::Kind_Vertex || kind == BufferView::Kind_Index) { append(json, ",\"target\":"); append(json, (kind == BufferView::Kind_Vertex) ? "34962" : "34963"); } if (compression != BufferView::Compression_None) { append(json, ",\"extensions\":{"); append(json, "\"EXT_meshopt_compression\":{"); append(json, "\"buffer\":0"); append(json, ",\"byteOffset\":"); append(json, size_t(compressed_offset)); append(json, ",\"byteLength\":"); append(json, size_t(compressed_size)); append(json, ",\"byteStride\":"); append(json, stride); append(json, ",\"mode\":\""); append(json, compressionMode(compression)); append(json, "\""); if (filter != StreamFormat::Filter_None) { append(json, ",\"filter\":\""); append(json, compressionFilter(filter)); append(json, "\""); } append(json, ",\"count\":"); append(json, count); append(json, "}}"); } append(json, "}"); } static void writeAccessor(std::string& json, size_t view, size_t offset, cgltf_type type, cgltf_component_type component_type, bool normalized, size_t count, const float* min = 0, const float* max = 0, size_t numminmax = 0) { append(json, "{\"bufferView\":"); append(json, view); append(json, ",\"byteOffset\":"); append(json, offset); append(json, ",\"componentType\":"); append(json, componentType(component_type)); append(json, ",\"count\":"); append(json, count); append(json, ",\"type\":\""); append(json, shapeType(type)); append(json, "\""); if (normalized) { append(json, ",\"normalized\":true"); } if (min && max) { assert(numminmax); append(json, ",\"min\":["); for (size_t k = 0; k < numminmax; ++k) { comma(json); append(json, min[k]); } append(json, "],\"max\":["); for (size_t k = 0; k < numminmax; ++k) { comma(json); append(json, max[k]); } append(json, "]"); } append(json, "}"); } static void writeEmbeddedImage(std::string& json, std::vector& views, const char* data, size_t size, const char* mime_type, TextureKind kind) { size_t view = getBufferView(views, BufferView::Kind_Image, StreamFormat::Filter_None, BufferView::Compression_None, 1, -1 - kind); assert(views[view].data.empty()); views[view].data.assign(data, size); append(json, "\"bufferView\":"); append(json, view); append(json, ",\"mimeType\":\""); append(json, mime_type); append(json, "\""); } static std::string decodeUri(const char* uri) { std::string result = uri; if (!result.empty()) { cgltf_decode_uri(&result[0]); result.resize(strlen(result.c_str())); } return result; } void writeSampler(std::string& json, const cgltf_sampler& sampler) { if (sampler.mag_filter != 0) { comma(json); append(json, "\"magFilter\":"); append(json, size_t(sampler.mag_filter)); } if (sampler.min_filter != 0) { comma(json); append(json, "\"minFilter\":"); append(json, size_t(sampler.min_filter)); } if (sampler.wrap_s != 10497) { comma(json); append(json, "\"wrapS\":"); append(json, size_t(sampler.wrap_s)); } if (sampler.wrap_t != 10497) { comma(json); append(json, "\"wrapT\":"); append(json, size_t(sampler.wrap_t)); } } void writeImage(std::string& json, std::vector& views, const cgltf_image& image, const ImageInfo& info, size_t index, const char* input_path, const Settings& settings) { bool dataUri = image.uri && strncmp(image.uri, "data:", 5) == 0; if (image.uri && !dataUri && !settings.texture_embed) { // fast-path: we don't need to read the image to memory append(json, "\"uri\":\""); append(json, image.uri); append(json, "\""); return; } std::string img_data; std::string mime_type; if (!readImage(image, input_path, img_data, mime_type)) { fprintf(stderr, "Warning: unable to read image %d (%s), skipping\n", int(index), image.uri ? image.uri : "?"); return; } writeEmbeddedImage(json, views, img_data.c_str(), img_data.size(), mime_type.c_str(), info.kind); } void writeEncodedImage(std::string& json, std::vector& views, const cgltf_image& image, const std::string& encoded, const ImageInfo& info, const char* output_path, const Settings& settings) { bool dataUri = image.uri && strncmp(image.uri, "data:", 5) == 0; if (!settings.texture_embed && image.uri && !dataUri && output_path) { std::string ktx_uri = getFileName(image.uri) + ".ktx2"; std::string ktx_full_path = getFullPath(decodeUri(ktx_uri.c_str()).c_str(), output_path); if (writeFile(ktx_full_path.c_str(), encoded)) { append(json, "\"uri\":\""); append(json, ktx_uri); append(json, "\""); } else { fprintf(stderr, "Warning: unable to save encoded image %s, skipping\n", image.uri); } } else { writeEmbeddedImage(json, views, encoded.c_str(), encoded.size(), "image/ktx2", info.kind); } } void writeTexture(std::string& json, const cgltf_texture& texture, const ImageInfo* info, cgltf_data* data, const Settings& settings) { if (texture.image) { if (texture.sampler) { append(json, "\"sampler\":"); append(json, size_t(texture.sampler - data->samplers)); append(json, ","); } if (info && settings.texture_mode[info->kind] != TextureMode_Raw) { append(json, "\"extensions\":{\"KHR_texture_basisu\":{\"source\":"); append(json, size_t(texture.image - data->images)); append(json, "}}"); } else { append(json, "\"source\":"); append(json, size_t(texture.image - data->images)); } } } void writeMeshAttributes(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationPosition& qp, const QuantizationTexture& qt, const Settings& settings) { std::string scratch; for (size_t j = 0; j < mesh.streams.size(); ++j) { const Stream& stream = mesh.streams[j]; if (stream.target != target) continue; scratch.clear(); StreamFormat format = writeVertexStream(scratch, stream, qp, qt, settings); BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; size_t view = getBufferView(views, BufferView::Kind_Vertex, format.filter, compression, format.stride, stream.type); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); if (stream.type == cgltf_attribute_type_position) { float min[3] = {}; float max[3] = {}; getPositionBounds(min, max, stream, qp, settings); writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, stream.data.size(), min, max, 3); } else { writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, stream.data.size()); } size_t vertex_accr = accr_offset++; comma(json); append(json, "\""); append(json, attributeType(stream.type)); if (stream.type != cgltf_attribute_type_position && stream.type != cgltf_attribute_type_normal && stream.type != cgltf_attribute_type_tangent) { append(json, "_"); append(json, size_t(stream.index)); } append(json, "\":"); append(json, vertex_accr); } } size_t writeMeshIndices(std::vector& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, const Settings& settings) { std::string scratch; StreamFormat format = writeIndexStream(scratch, mesh.indices); BufferView::Compression compression = settings.compress ? (mesh.type == cgltf_primitive_type_triangles ? BufferView::Compression_Index : BufferView::Compression_IndexSequence) : BufferView::Compression_None; size_t view = getBufferView(views, BufferView::Kind_Index, StreamFormat::Filter_None, compression, format.stride); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, mesh.indices.size()); size_t index_accr = accr_offset++; return index_accr; } static size_t writeAnimationTime(std::vector& views, std::string& json_accessors, size_t& accr_offset, float mint, int frames, float period, const Settings& settings) { std::vector time(frames); for (int j = 0; j < frames; ++j) time[j] = mint + float(j) * period; std::string scratch; StreamFormat format = writeTimeStream(scratch, time); BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; size_t view = getBufferView(views, BufferView::Kind_Time, StreamFormat::Filter_None, compression, format.stride); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); writeAccessor(json_accessors, view, offset, cgltf_type_scalar, format.component_type, format.normalized, frames, &time.front(), &time.back(), 1); size_t time_accr = accr_offset++; return time_accr; } size_t writeJointBindMatrices(std::vector& views, std::string& json_accessors, size_t& accr_offset, const cgltf_skin& skin, const QuantizationPosition& qp, const Settings& settings) { std::string scratch; for (size_t j = 0; j < skin.joints_count; ++j) { float transform[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; if (skin.inverse_bind_matrices) { cgltf_accessor_read_float(skin.inverse_bind_matrices, j, transform, 16); } if (settings.quantize && !settings.pos_float) { float node_scale = qp.scale / float((1 << qp.bits) - 1) * (qp.normalized ? 65535.f : 1.f); // pos_offset has to be applied first, thus it results in an offset rotated by the bind matrix transform[12] += qp.offset[0] * transform[0] + qp.offset[1] * transform[4] + qp.offset[2] * transform[8]; transform[13] += qp.offset[0] * transform[1] + qp.offset[1] * transform[5] + qp.offset[2] * transform[9]; transform[14] += qp.offset[0] * transform[2] + qp.offset[1] * transform[6] + qp.offset[2] * transform[10]; // node_scale will be applied before the rotation/scale from transform for (int k = 0; k < 12; ++k) transform[k] *= node_scale; } scratch.append(reinterpret_cast(transform), sizeof(transform)); } BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; size_t view = getBufferView(views, BufferView::Kind_Skin, StreamFormat::Filter_None, compression, 64); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); writeAccessor(json_accessors, view, offset, cgltf_type_mat4, cgltf_component_type_r_32f, false, skin.joints_count); size_t matrix_accr = accr_offset++; return matrix_accr; } static void writeInstanceData(std::vector& views, std::string& json_accessors, cgltf_animation_path_type type, const std::vector& data, const Settings& settings) { BufferView::Compression compression = settings.compress ? BufferView::Compression_Attribute : BufferView::Compression_None; std::string scratch; StreamFormat format = writeKeyframeStream(scratch, type, data, settings); size_t view = getBufferView(views, BufferView::Kind_Instance, format.filter, compression, format.stride, type); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, data.size()); } size_t writeInstances(std::vector& views, std::string& json_accessors, size_t& accr_offset, const std::vector& transforms, const QuantizationPosition& qp, const Settings& settings) { std::vector position, rotation, scale; position.resize(transforms.size()); rotation.resize(transforms.size()); scale.resize(transforms.size()); for (size_t i = 0; i < transforms.size(); ++i) { decomposeTransform(position[i].f, rotation[i].f, scale[i].f, transforms[i].data); if (settings.quantize && !settings.pos_float) { const float* transform = transforms[i].data; float node_scale = qp.scale / float((1 << qp.bits) - 1) * (qp.normalized ? 65535.f : 1.f); // pos_offset has to be applied first, thus it results in an offset rotated by the instance matrix position[i].f[0] += qp.offset[0] * transform[0] + qp.offset[1] * transform[4] + qp.offset[2] * transform[8]; position[i].f[1] += qp.offset[0] * transform[1] + qp.offset[1] * transform[5] + qp.offset[2] * transform[9]; position[i].f[2] += qp.offset[0] * transform[2] + qp.offset[1] * transform[6] + qp.offset[2] * transform[10]; // node_scale will be applied before the rotation/scale from transform scale[i].f[0] *= node_scale; scale[i].f[1] *= node_scale; scale[i].f[2] *= node_scale; } } writeInstanceData(views, json_accessors, cgltf_animation_path_type_translation, position, settings); writeInstanceData(views, json_accessors, cgltf_animation_path_type_rotation, rotation, settings); writeInstanceData(views, json_accessors, cgltf_animation_path_type_scale, scale, settings); size_t result = accr_offset; accr_offset += 3; return result; } void writeMeshNode(std::string& json, size_t mesh_offset, cgltf_node* node, cgltf_skin* skin, cgltf_data* data, const QuantizationPosition* qp) { comma(json); append(json, "{\"mesh\":"); append(json, mesh_offset); if (skin) { comma(json); append(json, "\"skin\":"); append(json, size_t(skin - data->skins)); } if (qp) { float node_scale = qp->scale / float((1 << qp->bits) - 1) * (qp->normalized ? 65535.f : 1.f); append(json, ",\"translation\":["); append(json, qp->offset[0]); append(json, ","); append(json, qp->offset[1]); append(json, ","); append(json, qp->offset[2]); append(json, "],\"scale\":["); append(json, node_scale); append(json, ","); append(json, node_scale); append(json, ","); append(json, node_scale); append(json, "]"); } if (node && node->weights_count) { append(json, ",\"weights\":["); for (size_t j = 0; j < node->weights_count; ++j) { comma(json); append(json, node->weights[j]); } append(json, "]"); } append(json, "}"); } void writeMeshNodeInstanced(std::string& json, size_t mesh_offset, size_t accr_offset) { comma(json); append(json, "{\"mesh\":"); append(json, mesh_offset); append(json, ",\"extensions\":{\"EXT_mesh_gpu_instancing\":{\"attributes\":{"); comma(json); append(json, "\"TRANSLATION\":"); append(json, accr_offset + 0); comma(json); append(json, "\"ROTATION\":"); append(json, accr_offset + 1); comma(json); append(json, "\"SCALE\":"); append(json, accr_offset + 2); append(json, "}}}"); append(json, "}"); } void writeSkin(std::string& json, const cgltf_skin& skin, size_t matrix_accr, const std::vector& nodes, cgltf_data* data) { comma(json); append(json, "{"); append(json, "\"joints\":["); for (size_t j = 0; j < skin.joints_count; ++j) { comma(json); append(json, size_t(nodes[skin.joints[j] - data->nodes].remap)); } append(json, "]"); append(json, ",\"inverseBindMatrices\":"); append(json, matrix_accr); if (skin.skeleton) { comma(json); append(json, "\"skeleton\":"); append(json, size_t(nodes[skin.skeleton - data->nodes].remap)); } append(json, "}"); } void writeNode(std::string& json, const cgltf_node& node, const std::vector& nodes, cgltf_data* data) { const NodeInfo& ni = nodes[&node - data->nodes]; if (node.name && *node.name) { comma(json); append(json, "\"name\":\""); append(json, node.name); append(json, "\""); } if (node.has_translation) { comma(json); append(json, "\"translation\":["); append(json, node.translation[0]); append(json, ","); append(json, node.translation[1]); append(json, ","); append(json, node.translation[2]); append(json, "]"); } if (node.has_rotation) { comma(json); append(json, "\"rotation\":["); append(json, node.rotation[0]); append(json, ","); append(json, node.rotation[1]); append(json, ","); append(json, node.rotation[2]); append(json, ","); append(json, node.rotation[3]); append(json, "]"); } if (node.has_scale) { comma(json); append(json, "\"scale\":["); append(json, node.scale[0]); append(json, ","); append(json, node.scale[1]); append(json, ","); append(json, node.scale[2]); append(json, "]"); } if (node.has_matrix) { comma(json); append(json, "\"matrix\":["); for (int k = 0; k < 16; ++k) { comma(json); append(json, node.matrix[k]); } append(json, "]"); } bool has_children = !ni.mesh_nodes.empty(); for (size_t j = 0; j < node.children_count; ++j) has_children |= nodes[node.children[j] - data->nodes].keep; if (has_children) { comma(json); append(json, "\"children\":["); for (size_t j = 0; j < node.children_count; ++j) { const NodeInfo& ci = nodes[node.children[j] - data->nodes]; if (ci.keep) { comma(json); append(json, size_t(ci.remap)); } } for (size_t j = 0; j < ni.mesh_nodes.size(); ++j) { comma(json); append(json, ni.mesh_nodes[j]); } append(json, "]"); } if (ni.has_mesh) { comma(json); append(json, "\"mesh\":"); append(json, ni.mesh_index); if (ni.mesh_skin) { append(json, ",\"skin\":"); append(json, size_t(ni.mesh_skin - data->skins)); } if (node.weights_count) { append(json, ",\"weights\":["); for (size_t j = 0; j < node.weights_count; ++j) { comma(json); append(json, node.weights[j]); } append(json, "]"); } } if (node.camera) { comma(json); append(json, "\"camera\":"); append(json, size_t(node.camera - data->cameras)); } if (node.light) { comma(json); append(json, "\"extensions\":{\"KHR_lights_punctual\":{\"light\":"); append(json, size_t(node.light - data->lights)); append(json, "}}"); } } void writeAnimation(std::string& json, std::vector& views, std::string& json_accessors, size_t& accr_offset, const Animation& animation, size_t i, cgltf_data* data, const std::vector& nodes, const Settings& settings) { std::vector tracks; for (size_t j = 0; j < animation.tracks.size(); ++j) { const Track& track = animation.tracks[j]; const NodeInfo& ni = nodes[track.node - data->nodes]; if (!ni.keep) continue; if (!settings.anim_const && (ni.animated_paths & (1 << track.path)) == 0) continue; tracks.push_back(&track); } if (tracks.empty()) { char index[16]; snprintf(index, sizeof(index), "%d", int(i)); fprintf(stderr, "Warning: ignoring animation %s because it has no tracks with motion; use -ac to override\n", animation.name && *animation.name ? animation.name : index); return; } bool needs_time = false; bool needs_pose = false; for (size_t j = 0; j < tracks.size(); ++j) { const Track& track = *tracks[j]; assert(track.time.empty()); assert(track.data.size() == track.components * (track.constant ? 1 : animation.frames)); needs_time = needs_time || !track.constant; needs_pose = needs_pose || track.constant; } bool needs_range = needs_pose && !needs_time && animation.frames > 1; needs_pose = needs_pose && !(needs_range && tracks.size() == 1); assert(int(needs_time) + int(needs_pose) + int(needs_range) <= 2); float animation_period = 1.f / float(settings.anim_freq); float animation_length = float(animation.frames - 1) * animation_period; size_t time_accr = needs_time ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, animation.frames, animation_period, settings) : 0; size_t pose_accr = needs_pose ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, 1, 0.f, settings) : 0; size_t range_accr = needs_range ? writeAnimationTime(views, json_accessors, accr_offset, animation.start, 2, animation_length, settings) : 0; std::string json_samplers; std::string json_channels; size_t track_offset = 0; for (size_t j = 0; j < tracks.size(); ++j) { const Track& track = *tracks[j]; bool range = needs_range && j == 0; int range_size = range ? 2 : 1; std::string scratch; StreamFormat format = writeKeyframeStream(scratch, track.path, track.data, settings); if (range) { assert(range_size == 2); scratch += scratch; } BufferView::Compression compression = settings.compress && track.path != cgltf_animation_path_type_weights ? BufferView::Compression_Attribute : BufferView::Compression_None; size_t view = getBufferView(views, BufferView::Kind_Keyframe, format.filter, compression, format.stride, track.path); size_t offset = views[view].data.size(); views[view].data += scratch; comma(json_accessors); writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, track.data.size() * range_size); size_t data_accr = accr_offset++; comma(json_samplers); append(json_samplers, "{\"input\":"); append(json_samplers, range ? range_accr : track.constant ? pose_accr : time_accr); append(json_samplers, ",\"output\":"); append(json_samplers, data_accr); if (track.interpolation == cgltf_interpolation_type_step) append(json_samplers, ",\"interpolation\":\"STEP\""); append(json_samplers, "}"); const NodeInfo& tni = nodes[track.node - data->nodes]; size_t target_node = size_t(tni.remap); // when animating morph weights, quantization may move mesh assignments to a mesh node in which case we need to move the animation output if (track.path == cgltf_animation_path_type_weights && tni.mesh_nodes.size() == 1) target_node = tni.mesh_nodes[0]; comma(json_channels); append(json_channels, "{\"sampler\":"); append(json_channels, track_offset); append(json_channels, ",\"target\":{\"node\":"); append(json_channels, target_node); append(json_channels, ",\"path\":\""); append(json_channels, animationPath(track.path)); append(json_channels, "\"}}"); track_offset++; } comma(json); append(json, "{"); if (animation.name && *animation.name) { append(json, "\"name\":\""); append(json, animation.name); append(json, "\","); } append(json, "\"samplers\":["); append(json, json_samplers); append(json, "],\"channels\":["); append(json, json_channels); append(json, "]}"); } void writeCamera(std::string& json, const cgltf_camera& camera) { comma(json); append(json, "{"); switch (camera.type) { case cgltf_camera_type_perspective: append(json, "\"type\":\"perspective\",\"perspective\":{"); append(json, "\"yfov\":"); append(json, camera.data.perspective.yfov); append(json, ",\"znear\":"); append(json, camera.data.perspective.znear); if (camera.data.perspective.aspect_ratio != 0.f) { append(json, ",\"aspectRatio\":"); append(json, camera.data.perspective.aspect_ratio); } if (camera.data.perspective.zfar != 0.f) { append(json, ",\"zfar\":"); append(json, camera.data.perspective.zfar); } append(json, "}"); break; case cgltf_camera_type_orthographic: append(json, "\"type\":\"orthographic\",\"orthographic\":{"); append(json, "\"xmag\":"); append(json, camera.data.orthographic.xmag); append(json, ",\"ymag\":"); append(json, camera.data.orthographic.ymag); append(json, ",\"znear\":"); append(json, camera.data.orthographic.znear); append(json, ",\"zfar\":"); append(json, camera.data.orthographic.zfar); append(json, "}"); break; default: fprintf(stderr, "Warning: skipping camera of unknown type\n"); } append(json, "}"); } void writeLight(std::string& json, const cgltf_light& light) { comma(json); append(json, "{\"type\":\""); append(json, lightType(light.type)); append(json, "\""); if (memcmp(light.color, white, 12) != 0) { comma(json); append(json, "\"color\":["); append(json, light.color[0]); append(json, ","); append(json, light.color[1]); append(json, ","); append(json, light.color[2]); append(json, "]"); } if (light.intensity != 1.f) { comma(json); append(json, "\"intensity\":"); append(json, light.intensity); } if (light.range != 0.f) { comma(json); append(json, "\"range\":"); append(json, light.range); } if (light.type == cgltf_light_type_spot) { comma(json); append(json, "\"spot\":{"); append(json, "\"innerConeAngle\":"); append(json, light.spot_inner_cone_angle); append(json, ",\"outerConeAngle\":"); append(json, light.spot_outer_cone_angle == 0.f ? 0.78539816339f : light.spot_outer_cone_angle); append(json, "}"); } append(json, "}"); } void writeArray(std::string& json, const char* name, const std::string& contents) { if (contents.empty()) return; comma(json); append(json, "\""); append(json, name); append(json, "\":["); append(json, contents); append(json, "]"); } void writeExtensions(std::string& json, const ExtensionInfo* extensions, size_t count) { bool used_extensions = false; bool required_extensions = false; for (size_t i = 0; i < count; ++i) { used_extensions |= extensions[i].used; required_extensions |= extensions[i].used && extensions[i].required; } if (used_extensions) { comma(json); append(json, "\"extensionsUsed\":["); for (size_t i = 0; i < count; ++i) if (extensions[i].used) { comma(json); append(json, "\""); append(json, extensions[i].name); append(json, "\""); } append(json, "]"); } if (required_extensions) { comma(json); append(json, "\"extensionsRequired\":["); for (size_t i = 0; i < count; ++i) if (extensions[i].used && extensions[i].required) { comma(json); append(json, "\""); append(json, extensions[i].name); append(json, "\""); } append(json, "]"); } } void writeExtras(std::string& json, const cgltf_extras& extras) { if (!extras.data) return; comma(json); append(json, "\"extras\":"); appendJson(json, extras.data); } void writeScene(std::string& json, const cgltf_scene& scene, const std::string& roots) { comma(json); append(json, "{"); if (scene.name && *scene.name) { append(json, "\"name\":\""); append(json, scene.name); append(json, "\""); } if (!roots.empty()) { comma(json); append(json, "\"nodes\":["); append(json, roots); append(json, "]"); } append(json, "}"); }