Skip to content

Commit

Permalink
Merge pull request jkuhlmann#181 from sanghoon/feature/fix_write
Browse files Browse the repository at this point in the history
Fix a bug related to json extras & Impl. a data URI writer.
  • Loading branch information
jkuhlmann authored Aug 31, 2022
2 parents c887e78 + 7793e6e commit 93ba78e
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 7 deletions.
3 changes: 3 additions & 0 deletions cgltf.h
Original file line number Diff line number Diff line change
Expand Up @@ -924,12 +924,15 @@ static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t
*/


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

#ifndef CGLTF_MALLOC
#define CGLTF_MALLOC(size) malloc(size)
Expand Down
73 changes: 66 additions & 7 deletions cgltf_write.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
*
* Reference:
* `cgltf_result cgltf_write_file(const cgltf_options* options, const char*
* path, const cgltf_data* data)` writes JSON to the given file path. Buffer
* files and external images are not written out. `data` is not deallocated.
* path, const cgltf_data* data)` writes a glTF data to the given file path.
* If `options->type` is `cgltf_file_type_glb`, both JSON content and binary
* buffer of the given glTF data will be written in a GLB format.
* Otherwise, only the JSON part will be written.
* External buffers and images are not written out. `data` is not deallocated.
*
* `cgltf_size cgltf_write(const cgltf_options* options, char* buffer,
* cgltf_size size, const cgltf_data* data)` writes JSON into the given memory
Expand All @@ -25,7 +28,7 @@
* been written. `data` is not deallocated.
*
* To write custom JSON into the `extras` field, aggregate all the custom JSON
* into a single buffer, then set `file_data` to this buffer. By supplying
* into a single buffer, then set `json` to this buffer. By supplying
* start_offset and end_offset values for various objects, you can select a
* range of characters within the aggregated buffer.
*/
Expand Down Expand Up @@ -158,6 +161,16 @@ typedef struct {
cgltf_write_extras(context, &info.extras); \
cgltf_write_line(context, "}"); }

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

static void cgltf_write_indent(cgltf_write_context* context)
{
if (context->needs_comma)
Expand Down Expand Up @@ -209,9 +222,9 @@ static void cgltf_write_strprop(cgltf_write_context* context, const char* label,
static void cgltf_write_extras(cgltf_write_context* context, const cgltf_extras* extras)
{
cgltf_size length = extras->end_offset - extras->start_offset;
if (length > 0 && context->data->file_data)
if (length > 0 && context->data->json)
{
char* json_string = ((char*) context->data->file_data) + extras->start_offset;
char* json_string = ((char*) context->data->json) + extras->start_offset;
cgltf_write_indent(context);
CGLTF_SPRINTF("%s", "\"extras\": ");
CGLTF_SNPRINTF(length, "%.*s", (int)(extras->end_offset - extras->start_offset), json_string);
Expand Down Expand Up @@ -1097,6 +1110,47 @@ static void cgltf_write_variant(cgltf_write_context* context, const cgltf_materi
cgltf_write_line(context, "}");
}

static void cgltf_write_glb(FILE* file, const void* json_buf, const cgltf_size json_size, const void* bin_buf, const cgltf_size bin_size)
{
char header[GlbHeaderSize];
char chunk_header[GlbChunkHeaderSize];
char json_pad[3] = { 0x20, 0x20, 0x20 };
char bin_pad[3] = { 0, 0, 0 };

cgltf_size json_padsize = (json_size % 4 != 0) ? 4 - json_size % 4 : 0;
cgltf_size bin_padsize = (bin_size % 4 != 0) ? 4 - bin_size % 4 : 0;
cgltf_size total_size = GlbHeaderSize + GlbChunkHeaderSize + json_size + json_padsize;
if (bin_buf != NULL && bin_size > 0) {
total_size += GlbChunkHeaderSize + bin_size + bin_padsize;
}

// Write a GLB header
memcpy(header, &GlbMagic, 4);
memcpy(header + 4, &GlbVersion, 4);
memcpy(header + 8, &total_size, 4);
fwrite(header, 1, GlbHeaderSize, file);

// Write a JSON chunk (header & data)
uint32_t json_chunk_size = (uint32_t)(json_size + json_padsize);
memcpy(chunk_header, &json_chunk_size, 4);
memcpy(chunk_header + 4, &GlbMagicJsonChunk, 4);
fwrite(chunk_header, 1, GlbChunkHeaderSize, file);

fwrite(json_buf, 1, json_size, file);
fwrite(json_pad, 1, json_padsize, file);

if (bin_buf != NULL && bin_size > 0) {
// Write a binary chunk (header & data)
uint32_t bin_chunk_size = (uint32_t)(bin_size + bin_padsize);
memcpy(chunk_header, &bin_chunk_size, 4);
memcpy(chunk_header + 4, &GlbMagicBinChunk, 4);
fwrite(chunk_header, 1, GlbChunkHeaderSize, file);

fwrite(bin_buf, 1, bin_size, file);
fwrite(bin_pad, 1, bin_padsize, file);
}
}

cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, const cgltf_data* data)
{
cgltf_size expected = cgltf_write(options, NULL, 0, data);
Expand All @@ -1105,13 +1159,18 @@ cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, co
if (expected != actual) {
fprintf(stderr, "Error: expected %zu bytes but wrote %zu bytes.\n", expected, actual);
}
FILE* file = fopen(path, "wt");
FILE* file = fopen(path, "wb");
if (!file)
{
return cgltf_result_file_not_found;
}
// Note that cgltf_write() includes a null terminator, which we omit from the file content.
fwrite(buffer, actual - 1, 1, file);
if (options->type == cgltf_file_type_glb) {
cgltf_write_glb(file, buffer, actual - 1, data->bin, data->bin_size);
} else {
// Write a plain JSON file.
fwrite(buffer, actual - 1, 1, file);
}
fclose(file);
free(buffer);
return cgltf_result_success;
Expand Down
13 changes: 13 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ else()
endif()
install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin )

set( EXE_NAME test_write_glb )
add_executable( ${EXE_NAME} test_write_glb.cpp )
set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 )
if(MSVC)
target_compile_options(${EXE_NAME} PRIVATE /W4 /WX)
add_definitions( -D_CRT_SECURE_NO_WARNINGS)
else()
target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror)
target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address)
target_link_options(${EXE_NAME} PUBLIC -fsanitize=address)
endif()
install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin )

set( EXE_NAME test_math )
add_executable( ${EXE_NAME} test_math.cpp )
set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 )
Expand Down
1 change: 1 addition & 0 deletions test/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def collect_files(path, type, name):
collect_files("glTF-Sample-Models/2.0/", ".glb", "test_conversion")
collect_files("glTF-Sample-Models/2.0/", ".gltf", "test_conversion")
collect_files("glTF-Sample-Models/2.0/", ".gltf", "test_write")
collect_files("glTF-Sample-Models/2.0/", ".glb", "test_write_glb")

result = os.system(get_executable_path("test_math"))
if result != 0:
Expand Down
58 changes: 58 additions & 0 deletions test/test_write_glb.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#define CGLTF_IMPLEMENTATION
#define CGLTF_WRITE_IMPLEMENTATION
#include "../cgltf_write.h"

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <limits>
#include <string.h>

int main(int argc, char** argv)
{
if (argc < 2)
{
printf("err\n");
return -1;
}

cgltf_options options = {};
cgltf_data* data0 = NULL;
cgltf_result result = cgltf_parse_file(&options, argv[1], &data0);

// Silently skip over files that are unreadable since this is a writing test.
if (result != cgltf_result_success)
{
return cgltf_result_success;
}

options.type = cgltf_file_type_glb; // Write back in a GLB format
result = cgltf_write_file(&options, "out.glb", data0);
if (result != cgltf_result_success)
{
return result;
}

cgltf_data* data1 = NULL;
result = cgltf_parse_file(&options, "out.glb", &data1);
if (result != cgltf_result_success)
{
return result;
}

if (data0->meshes_count != data1->meshes_count) {
return -1;
}

// Compare binary buffers
if (data0->bin_size != data1->bin_size) {
return -1;
}
if (memcmp(data0->bin, data1->bin, data0->bin_size) != 0) {
return -1;
}

cgltf_free(data1);
cgltf_free(data0);
return cgltf_result_success;
}

0 comments on commit 93ba78e

Please sign in to comment.