|
|
@@ -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");
|
|
|
|