summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp')
-rw-r--r--3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp389
1 files changed, 282 insertions, 107 deletions
diff --git a/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp
index aa6c49bb0d9..e939f3f1362 100644
--- a/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp
+++ b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp
@@ -84,6 +84,8 @@ struct Settings
bool simplify_aggressive;
bool compress;
+ bool fallback;
+
int verbose;
};
@@ -180,6 +182,30 @@ cgltf_accessor* getAccessor(const cgltf_attribute* attributes, size_t attribute_
return 0;
}
+void readAccessor(std::vector<float>& data, const cgltf_accessor* accessor)
+{
+ assert(accessor->type == cgltf_type_scalar);
+
+ data.resize(accessor->count);
+ cgltf_accessor_unpack_floats(accessor, &data[0], data.size());
+}
+
+void readAccessor(std::vector<Attr>& data, const cgltf_accessor* accessor)
+{
+ size_t components = cgltf_num_components(accessor->type);
+
+ std::vector<float> temp(accessor->count * components);
+ cgltf_accessor_unpack_floats(accessor, &temp[0], temp.size());
+
+ data.resize(accessor->count);
+
+ for (size_t i = 0; i < accessor->count; ++i)
+ {
+ for (size_t k = 0; k < components && k < 4; ++k)
+ data[i].f[k] = temp[i * components + k];
+ }
+}
+
void transformPosition(float* ptr, const float* transform)
{
float x = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8] + transform[12];
@@ -291,10 +317,7 @@ void parseMeshesGltf(cgltf_data* data, std::vector<Mesh>& meshes)
}
Stream s = {attr.type, attr.index};
- s.data.resize(attr.data->count);
-
- for (size_t i = 0; i < attr.data->count; ++i)
- cgltf_accessor_read_float(attr.data, i, s.data[i].f, 4);
+ readAccessor(s.data, attr.data);
result.streams.push_back(s);
}
@@ -314,10 +337,7 @@ void parseMeshesGltf(cgltf_data* data, std::vector<Mesh>& meshes)
}
Stream s = {attr.type, attr.index, int(ti + 1)};
- s.data.resize(attr.data->count);
-
- for (size_t i = 0; i < attr.data->count; ++i)
- cgltf_accessor_read_float(attr.data, i, s.data[i].f, 4);
+ readAccessor(s.data, attr.data);
result.streams.push_back(s);
}
@@ -821,10 +841,10 @@ void reindexMesh(Mesh& mesh)
}
}
-const Stream* getPositionStream(const Mesh& mesh)
+Stream* getStream(Mesh& mesh, cgltf_attribute_type type)
{
for (size_t i = 0; i < mesh.streams.size(); ++i)
- if (mesh.streams[i].type == cgltf_attribute_type_position)
+ if (mesh.streams[i].type == type)
return &mesh.streams[i];
return 0;
@@ -835,7 +855,7 @@ void simplifyMesh(Mesh& mesh, float threshold, bool aggressive)
if (threshold >= 1)
return;
- const Stream* positions = getPositionStream(mesh);
+ const Stream* positions = getStream(mesh, cgltf_attribute_type_position);
if (!positions)
return;
@@ -883,12 +903,59 @@ void optimizeMesh(Mesh& mesh)
}
}
+struct BoneInfluence
+{
+ float i;
+ float w;
+
+ bool operator<(const BoneInfluence& other) const
+ {
+ return i < other.i;
+ }
+};
+
+void sortBoneInfluences(Mesh& mesh)
+{
+ Stream* joints = getStream(mesh, cgltf_attribute_type_joints);
+ Stream* weights = getStream(mesh, cgltf_attribute_type_weights);
+
+ if (!joints || !weights)
+ return;
+
+ size_t vertex_count = mesh.streams[0].data.size();
+
+ for (size_t i = 0; i < vertex_count; ++i)
+ {
+ Attr& ja = joints->data[i];
+ Attr& wa = weights->data[i];
+
+ BoneInfluence inf[4] = {};
+ int count = 0;
+
+ for (int k = 0; k < 4; ++k)
+ if (wa.f[k] > 0.f)
+ {
+ inf[count].i = ja.f[k];
+ inf[count].w = wa.f[k];
+ count++;
+ }
+
+ std::sort(inf, inf + count);
+
+ for (int k = 0; k < 4; ++k)
+ {
+ ja.f[k] = inf[k].i;
+ wa.f[k] = inf[k].w;
+ }
+ }
+}
+
void simplifyPointMesh(Mesh& mesh, float threshold)
{
if (threshold >= 1)
return;
- const Stream* positions = getPositionStream(mesh);
+ const Stream* positions = getStream(mesh, cgltf_attribute_type_position);
if (!positions)
return;
@@ -919,7 +986,7 @@ void simplifyPointMesh(Mesh& mesh, float threshold)
void sortPointMesh(Mesh& mesh)
{
- const Stream* positions = getPositionStream(mesh);
+ const Stream* positions = getStream(mesh, cgltf_attribute_type_position);
if (!positions)
return;
@@ -1870,13 +1937,20 @@ size_t getBufferView(std::vector<BufferView>& views, BufferView::Kind kind, int
return views.size() - 1;
}
-void writeBufferView(std::string& json, BufferView::Kind kind, size_t count, size_t stride, size_t bin_offset, size_t bin_size, int compression)
+void writeBufferView(std::string& json, BufferView::Kind kind, size_t count, size_t stride, size_t bin_offset, size_t bin_size, int compression, size_t compressed_offset, size_t compressed_size)
{
- append(json, "{\"buffer\":0");
- append(json, ",\"byteLength\":");
- append(json, bin_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 >= 0 ? 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\":");
@@ -1891,12 +1965,17 @@ void writeBufferView(std::string& json, BufferView::Kind kind, size_t count, siz
{
append(json, ",\"extensions\":{");
append(json, "\"MESHOPT_compression\":{");
- append(json, "\"mode\":");
+ 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, size_t(compression));
append(json, ",\"count\":");
append(json, count);
- append(json, ",\"byteStride\":");
- append(json, stride);
append(json, "}}");
}
append(json, "}");
@@ -1961,7 +2040,7 @@ float getDelta(const Attr& l, const Attr& r, cgltf_animation_path_type type)
}
}
-bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_path_type type, cgltf_node* target_node)
+bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_path_type type, cgltf_node* target_node, Attr* out_first = 0)
{
const float tolerance = 1e-3f;
@@ -1972,15 +2051,16 @@ bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_pat
assert(sampler.input->count * value_stride * components == sampler.output->count);
+ std::vector<Attr> output;
+ readAccessor(output, sampler.output);
+
for (size_t j = 0; j < components; ++j)
{
- Attr first = {};
- cgltf_accessor_read_float(sampler.output, j * value_stride + value_offset, first.f, 4);
+ Attr first = output[j * value_stride + value_offset];
for (size_t i = 1; i < sampler.input->count; ++i)
{
- Attr attr = {};
- cgltf_accessor_read_float(sampler.output, (i * components + j) * value_stride + value_offset, attr.f, 4);
+ const Attr& attr = output[(i * components + j) * value_stride + value_offset];
if (getDelta(first, attr, type) > tolerance)
return false;
@@ -1992,8 +2072,7 @@ bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_pat
{
for (int k = 0; k < 2; ++k)
{
- Attr t = {};
- cgltf_accessor_read_float(sampler.output, (i * components + j) * 3 + k * 2, t.f, 4);
+ const Attr& t = output[(i * components + j) * 3 + k * 2];
float error = fabsf(t.f[0]) + fabsf(t.f[1]) + fabsf(t.f[2]) + fabsf(t.f[3]);
@@ -2004,6 +2083,9 @@ bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_pat
}
}
+ if (out_first)
+ *out_first = output[value_offset];
+
return true;
}
@@ -2093,6 +2175,11 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s
{
size_t components = (type == cgltf_animation_path_type_weights) ? target_node->mesh->primitives[0].targets_count : 1;
+ std::vector<float> input;
+ readAccessor(input, sampler.input);
+ std::vector<Attr> output;
+ readAccessor(output, sampler.output);
+
size_t cursor = 0;
for (int i = 0; i < frames; ++i)
@@ -2101,8 +2188,7 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s
while (cursor + 1 < sampler.input->count)
{
- float next_time = 0;
- cgltf_accessor_read_float(sampler.input, cursor + 1, &next_time, 1);
+ float next_time = input[cursor + 1];
if (next_time > time)
break;
@@ -2112,10 +2198,8 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s
if (cursor + 1 < sampler.input->count)
{
- float cursor_time = 0;
- float next_time = 0;
- cgltf_accessor_read_float(sampler.input, cursor + 0, &cursor_time, 1);
- cgltf_accessor_read_float(sampler.input, cursor + 1, &next_time, 1);
+ float cursor_time = input[cursor + 0];
+ float next_time = input[cursor + 1];
float range = next_time - cursor_time;
float inv_range = (range == 0.f) ? 0.f : 1.f / (next_time - cursor_time);
@@ -2127,32 +2211,25 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s
{
case cgltf_interpolation_type_linear:
{
- Attr v0 = {};
- Attr v1 = {};
- cgltf_accessor_read_float(sampler.output, (cursor + 0) * components + j, v0.f, 4);
- cgltf_accessor_read_float(sampler.output, (cursor + 1) * components + j, v1.f, 4);
+ const Attr& v0 = output[(cursor + 0) * components + j];
+ const Attr& v1 = output[(cursor + 1) * components + j];
data.push_back(interpolateLinear(v0, v1, t, type));
}
break;
case cgltf_interpolation_type_step:
{
- Attr v = {};
- cgltf_accessor_read_float(sampler.output, cursor * components + j, v.f, 4);
+ const Attr& v = output[cursor * components + j];
data.push_back(v);
}
break;
case cgltf_interpolation_type_cubic_spline:
{
- Attr v0 = {};
- Attr b0 = {};
- Attr a1 = {};
- Attr v1 = {};
- cgltf_accessor_read_float(sampler.output, (cursor * 3 + 1) * components + j, v0.f, 4);
- cgltf_accessor_read_float(sampler.output, (cursor * 3 + 2) * components + j, b0.f, 4);
- cgltf_accessor_read_float(sampler.output, (cursor * 3 + 3) * components + j, a1.f, 4);
- cgltf_accessor_read_float(sampler.output, (cursor * 3 + 4) * components + j, v1.f, 4);
+ const Attr& v0 = output[(cursor * 3 + 1) * components + j];
+ const Attr& b0 = output[(cursor * 3 + 2) * components + j];
+ const Attr& a1 = output[(cursor * 3 + 3) * components + j];
+ const Attr& v1 = output[(cursor * 3 + 4) * components + j];
data.push_back(interpolateHermite(v0, b0, v1, a1, t, range, type));
}
break;
@@ -2168,8 +2245,7 @@ void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& s
for (size_t j = 0; j < components; ++j)
{
- Attr v = {};
- cgltf_accessor_read_float(sampler.output, offset * components + j, v.f, 4);
+ const Attr& v = output[offset * components + j];
data.push_back(v);
}
}
@@ -2193,8 +2269,15 @@ void markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes)
NodeInfo& ni = nodes[channel.target_node - data->nodes];
// mark nodes that have animation tracks that change their base transform as animated
- if (!isTrackConstant(sampler, channel.target_path, channel.target_node))
+ Attr first = {};
+ if (!isTrackConstant(sampler, channel.target_path, channel.target_node, &first))
+ {
+ ni.animated_paths |= (1 << channel.target_path);
+ }
+ else if (channel.target_path == cgltf_animation_path_type_weights)
{
+ // we currently preserve constant weight tracks because the usecase is very rare and
+ // isTrackConstant doesn't return the full set of weights to compare against
ni.animated_paths |= (1 << channel.target_path);
}
else
@@ -2212,12 +2295,10 @@ void markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes)
case cgltf_animation_path_type_scale:
memcpy(base.f, channel.target_node->scale, 3 * sizeof(float));
break;
- default:;
+ default:
+ assert(!"Unknown target path");
}
- Attr first = {};
- cgltf_accessor_read_float(sampler.output, 0, first.f, 4);
-
const float tolerance = 1e-3f;
if (getDelta(base, first, channel.target_path) > tolerance)
@@ -2896,6 +2977,53 @@ void writeLight(std::string& json, const cgltf_light& light)
append(json, "}");
}
+void finalizeBufferViews(std::string& json, std::vector<BufferView>& views, std::string& bin, std::string& fallback)
+{
+ for (size_t i = 0; i < views.size(); ++i)
+ {
+ BufferView& view = views[i];
+
+ size_t bin_offset = bin.size();
+ size_t fallback_offset = fallback.size();
+
+ size_t count = view.data.size() / view.stride;
+
+ int compression = -1;
+
+ if (view.compressed)
+ {
+ if (view.kind == BufferView::Kind_Index)
+ {
+ compressIndexStream(bin, view.data, count, view.stride);
+ compression = 1;
+ }
+ else
+ {
+ compressVertexStream(bin, view.data, count, view.stride);
+ compression = 0;
+ }
+
+ fallback += view.data;
+ }
+ else
+ {
+ bin += view.data;
+ }
+
+ size_t raw_offset = (compression >= 0) ? fallback_offset : bin_offset;
+
+ comma(json);
+ writeBufferView(json, view.kind, count, view.stride, raw_offset, view.data.size(), compression, bin_offset, bin.size() - bin_offset);
+
+ // record written bytes for statistics
+ view.bytes = bin.size() - bin_offset;
+
+ // align each bufferView by 4 bytes
+ bin.resize((bin.size() + 3) & ~3);
+ fallback.resize((fallback.size() + 3) & ~3);
+ }
+}
+
void printMeshStats(const std::vector<Mesh>& meshes, const char* name)
{
size_t triangles = 0;
@@ -2952,7 +3080,7 @@ void printAttributeStats(const std::vector<BufferView>& views, BufferView::Kind
}
}
-void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settings, std::string& json, std::string& bin)
+void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settings, std::string& json, std::string& bin, std::string& fallback)
{
if (settings.verbose)
{
@@ -3017,6 +3145,7 @@ void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settin
reindexMesh(mesh);
simplifyMesh(mesh, settings.simplify_threshold, settings.simplify_aggressive);
optimizeMesh(mesh);
+ sortBoneInfluences(mesh);
break;
default:
@@ -3342,7 +3471,7 @@ void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settin
append(json, "\"KHR_lights_punctual\"");
}
append(json, "]");
- if (settings.compress)
+ if (settings.compress && !settings.fallback)
{
append(json, ",\"extensionsRequired\":[");
// Note: ideally we should include MESHOPT_quantized_geometry in the required extension list (regardless of compression)
@@ -3353,47 +3482,13 @@ void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settin
append(json, "]");
}
- size_t bytes[BufferView::Kind_Count] = {};
-
if (!views.empty())
{
- append(json, ",\"bufferViews\":[");
- for (size_t i = 0; i < views.size(); ++i)
- {
- BufferView& view = views[i];
-
- size_t offset = bin.size();
- size_t count = view.data.size() / view.stride;
-
- int compression = -1;
-
- if (view.compressed)
- {
- if (view.kind == BufferView::Kind_Index)
- {
- compressIndexStream(bin, view.data, count, view.stride);
- compression = 1;
- }
- else
- {
- compressVertexStream(bin, view.data, count, view.stride);
- compression = 0;
- }
- }
- else
- {
- bin += view.data;
- }
+ std::string json_views;
+ finalizeBufferViews(json_views, views, bin, fallback);
- comma(json);
- writeBufferView(json, view.kind, count, view.stride, offset, bin.size() - offset, compression);
-
- view.bytes = bin.size() - offset;
- bytes[view.kind] += view.bytes;
-
- // align each bufferView by 4 bytes
- bin.resize((bin.size() + 3) & ~3);
- }
+ append(json, ",\"bufferViews\":[");
+ append(json, json_views);
append(json, "]");
}
if (!json_accessors.empty())
@@ -3466,6 +3561,14 @@ void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settin
if (settings.verbose)
{
+ size_t bytes[BufferView::Kind_Count] = {};
+
+ for (size_t i = 0; i < views.size(); ++i)
+ {
+ BufferView& view = views[i];
+ bytes[view.kind] += view.bytes;
+ }
+
printf("output: %d nodes, %d meshes (%d primitives), %d materials\n", int(node_offset), int(mesh_offset), int(meshes.size()), int(material_offset));
printf("output: JSON %d bytes, buffers %d bytes\n", int(json.size()), int(bin.size()));
printf("output: buffers: vertex %d bytes, index %d bytes, skin %d bytes, time %d bytes, keyframe %d bytes, image %d bytes\n",
@@ -3495,6 +3598,56 @@ bool requiresExtension(cgltf_data* data, const char* name)
return false;
}
+const char* getBaseName(const char* path)
+{
+ const char* slash = strrchr(path, '/');
+ const char* backslash = strrchr(path, '\\');
+
+ const char* rs = slash ? slash + 1 : path;
+ const char* bs = backslash ? backslash + 1 : path;
+
+ return std::max(rs, bs);
+}
+
+std::string getBufferSpec(const char* bin_path, size_t bin_size, const char* fallback_path, size_t fallback_size, bool fallback_ref)
+{
+ std::string json;
+ append(json, "\"buffers\":[");
+ append(json, "{");
+ if (bin_path)
+ {
+ append(json, "\"uri\":\"");
+ append(json, bin_path);
+ append(json, "\"");
+ }
+ comma(json);
+ append(json, "\"byteLength\":");
+ append(json, bin_size);
+ append(json, "}");
+ if (fallback_ref)
+ {
+ comma(json);
+ append(json, "{");
+ if (fallback_path)
+ {
+ append(json, "\"uri\":\"");
+ append(json, fallback_path);
+ append(json, "\"");
+ }
+ comma(json);
+ append(json, "\"byteLength\":");
+ append(json, fallback_size);
+ append(json, ",\"extensions\":{");
+ append(json, "\"MESHOPT_compression\":{");
+ append(json, "\"fallback\":true");
+ append(json, "}}");
+ append(json, "}");
+ }
+ append(json, "]");
+
+ return json;
+}
+
int gltfpack(const char* input, const char* output, const Settings& settings)
{
cgltf_data* data = 0;
@@ -3549,8 +3702,8 @@ int gltfpack(const char* input, const char* output, const Settings& settings)
return 2;
}
- std::string json, bin;
- process(data, meshes, settings, json, bin);
+ std::string json, bin, fallback;
+ process(data, meshes, settings, json, bin, fallback);
cgltf_free(data);
@@ -3566,41 +3719,52 @@ int gltfpack(const char* input, const char* output, const Settings& settings)
std::string binpath = output;
binpath.replace(binpath.size() - 5, 5, ".bin");
- std::string binname = binpath;
- std::string::size_type slash = binname.find_last_of("/\\");
- if (slash != std::string::npos)
- binname.erase(0, slash + 1);
+ std::string fbpath = output;
+ fbpath.replace(fbpath.size() - 5, 5, ".fallback.bin");
FILE* outjson = fopen(output, "wb");
FILE* outbin = fopen(binpath.c_str(), "wb");
- if (!outjson || !outbin)
+ FILE* outfb = settings.fallback ? fopen(fbpath.c_str(), "wb") : NULL;
+ if (!outjson || !outbin || (!outfb && settings.fallback))
{
fprintf(stderr, "Error saving %s\n", output);
return 4;
}
- fprintf(outjson, "{\"buffers\":[{\"uri\":\"%s\",\"byteLength\":%zu}],", binname.c_str(), bin.size());
+ std::string bufferspec = getBufferSpec(getBaseName(binpath.c_str()), bin.size(), settings.fallback ? getBaseName(fbpath.c_str()) : NULL, fallback.size(), settings.compress);
+
+ fprintf(outjson, "{");
+ fwrite(bufferspec.c_str(), bufferspec.size(), 1, outjson);
+ fprintf(outjson, ",");
fwrite(json.c_str(), json.size(), 1, outjson);
fprintf(outjson, "}");
fwrite(bin.c_str(), bin.size(), 1, outbin);
+ if (settings.fallback)
+ fwrite(fallback.c_str(), fallback.size(), 1, outfb);
+
fclose(outjson);
fclose(outbin);
+ if (outfb)
+ fclose(outfb);
}
else if (oext && (strcmp(oext, ".glb") == 0 || strcmp(oext, ".GLB") == 0))
{
+ std::string fbpath = output;
+ fbpath.replace(fbpath.size() - 4, 4, ".fallback.bin");
+
FILE* out = fopen(output, "wb");
- if (!out)
+ FILE* outfb = settings.fallback ? fopen(fbpath.c_str(), "wb") : NULL;
+ if (!out || (!outfb && settings.fallback))
{
fprintf(stderr, "Error saving %s\n", output);
return 4;
}
- char bufferspec[64];
- sprintf(bufferspec, "{\"buffers\":[{\"byteLength\":%zu}],", bin.size());
+ std::string bufferspec = getBufferSpec(NULL, bin.size(), settings.fallback ? getBaseName(fbpath.c_str()) : NULL, fallback.size(), settings.compress);
- json.insert(0, bufferspec);
+ json.insert(0, "{" + bufferspec + ",");
json.push_back('}');
while (json.size() % 4)
@@ -3621,7 +3785,12 @@ int gltfpack(const char* input, const char* output, const Settings& settings)
writeU32(out, 0x004E4942);
fwrite(bin.c_str(), bin.size(), 1, out);
+ if (settings.fallback)
+ fwrite(fallback.c_str(), fallback.size(), 1, outfb);
+
fclose(out);
+ if (outfb)
+ fclose(outfb);
}
else
{
@@ -3698,6 +3867,11 @@ int main(int argc, char** argv)
{
settings.compress = true;
}
+ else if (strcmp(arg, "-cf") == 0)
+ {
+ settings.compress = true;
+ settings.fallback = true;
+ }
else if (strcmp(arg, "-v") == 0)
{
settings.verbose = 1;
@@ -3749,7 +3923,8 @@ int main(int argc, char** argv)
fprintf(stderr, "-kn: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally\n");
fprintf(stderr, "-si R: simplify meshes to achieve the ratio R (default: 1; R should be between 0 and 1)\n");
fprintf(stderr, "-sa: aggressively simplify to the target ratio disregarding quality\n");
- fprintf(stderr, "-c: produce compressed glb files\n");
+ fprintf(stderr, "-c: produce compressed gltf/glb files\n");
+ fprintf(stderr, "-cf: produce compressed gltf/glb files with fallback for loaders that don't support compression\n");
fprintf(stderr, "-v: verbose output\n");
fprintf(stderr, "-h: display this help and exit\n");