Jackalope / jrf (public) (License: GPLv3 or later version) (since 2019-11-21) (hash sha1)
Libriary for reading and writing resource files: vertices, indices, meshes, images, models and scene data.
Supports reading from filesystem as well as from zip files.
It uses unique binary container format for those resources.
It is used in tool for converting popular formats like wavefront .obj file, TARGA image to this container and also used in resource loader as part of graphics framework.

It requires jlib libriary (see my user repositories) and libzip.
It is possible to remove libzip dependency.
List of commits:
Subject Hash Author Date (UTC)
jrf-tool - executable tool for writing resources to jrf format. 8cc5188998e46b8ba868b2755fdd8efd2f92aaee Jackalope 2020-06-03 21:35:29
VertexAttribute to string 36ff737fe2216507ce19140570d95f86084e0da6 Jackalope 2020-06-04 18:13:19
ResourceHandle instead of ResourceUnion. ac45fbd8749b2d98703c77f3d17a4e0b94349f9d Jackalope 2020-06-03 21:33:34
Convert array of images to image array. 6f3666370a02ddd127ba3f6515c68cfea5f50a6c Jackalope 2020-06-03 01:47:05
Wavefront obj file converter. c24ea785073b8362a9659e3f275f0329e4799a25 Jackalope 2020-06-03 01:45:56
Forget to add config to installation! 9eb91f41049b2f0dcda1442c1750da4266e97376 Jackalope 2020-06-01 16:26:51
Better config, solving some issues with linking to jrf. 11ab2d21090cf684c08954ffabea0fe317afcc0a Jackalope 2020-06-01 16:24:51
Link jrf-shared to jlib-shared and jrf-static to jrf-static. b965856e0f894aa48555b9ccfef812bd15d2e3a6 Jackalope 2020-06-01 16:23:26
Version 0.2.2 d6aaf170a2db27ddcd429a89fd873341ea8a0082 Jackalope 2020-05-31 23:47:23
Move definitions to configuration file. a7b28738de65520eddd08cbd5d8c958fa16eedbe Jackalope 2020-05-31 21:45:28
CMake: build both shared and static with JRF_SHARED and JRF_STATIC options. When is subdirectory, select only one instead. 0dc49150959af90cd9210367a61113eca85650eb Jackalope 2020-05-31 21:38:50
CMake: version check changed. 500bcbe11c7f1e418e5d8116e89654d160c8dbd6 Jackalope 2020-05-31 21:37:47
Reduced warnings. 865f2c52ed60c37c050a1033905bb2df278032a0 Jackalope 2020-05-27 15:03:07
Make "find_package(jlib 0.2.0 REQUIRED)" optional 58bfc04890fab24adf5fc84963cb1a3bca7a8706 Jackalope 2020-05-24 16:39:14
changed notice about native compilation 0ed7e9e5e72a2d13e8add619f7999a51634f8e9c Jackalope 2020-05-24 16:37:54
Version 0.2.1 02d30aa69e9026a3f96d8549a297fccfbefbda47 Jackalope 2020-05-24 14:31:31
Why there is Doxyfile? 16cf9fe9e64353a2a717668cc4083465e2f8dff5 Jackalope 2020-05-24 14:25:20
Add license notice to CMakeLists.txt db2e7f018714cd2643c7928a8cce966d3da25059 Jackalope 2020-05-24 14:23:39
Make zip functionality optional 8e4409ba503637e5981f98c4df39ff4d649351de Jackalope 2020-05-24 14:23:18
remove LTO option 9b39746de0abc22a2ae8d2ce8c2db253dd9ad71a Jackalope 2020-05-24 13:41:20
Commit 8cc5188998e46b8ba868b2755fdd8efd2f92aaee - jrf-tool - executable tool for writing resources to jrf format.
Author: Jackalope
Author date (UTC): 2020-06-03 21:35
Committer name: Jackalope
Committer date (UTC): 2020-06-04 20:57
Parent(s): 36ff737fe2216507ce19140570d95f86084e0da6
Signing key:
Tree: 4f056e018b0a3fd0542e78b939b3dd369ef7d1c2
File Lines added Lines deleted
CMakeLists.txt 86 6
src/tool.cpp 953 0
File CMakeLists.txt changed (mode: 100644) (index 68b5d3d..e30db2e)
... ... project(JRF
23 23 ) )
24 24 option(JRF_SKIP_PACKAGE_JLIB-0.4 "Skip find_package(jlib 0.4 REQUIRED)" OFF) option(JRF_SKIP_PACKAGE_JLIB-0.4 "Skip find_package(jlib 0.4 REQUIRED)" OFF)
25 25 if (NOT JRF_SKIP_PACKAGE_JLIB-0.4) if (NOT JRF_SKIP_PACKAGE_JLIB-0.4)
26 find_package(jlib 0.4 REQUIRED)
26 find_package(jlib 0.4.3 REQUIRED)
27 27 endif() endif()
28 28
29 29 option(JRF_ZIP "Enable reading from zip AND libzip wrapper" ON) option(JRF_ZIP "Enable reading from zip AND libzip wrapper" ON)
 
... ... if (JRF_SHARED)
89 89 add_library(jrf-shared SHARED ${SOURCE_FILES}) add_library(jrf-shared SHARED ${SOURCE_FILES})
90 90 list(APPEND TARGETS shared) list(APPEND TARGETS shared)
91 91 list(APPEND JRF_TARGETS jrf-shared) list(APPEND JRF_TARGETS jrf-shared)
92 if (NOT TARGET jlib-shared)
93 message(FATAL_ERROR "For jrf-shared target jlib-shared is needed!")
94 endif()
92 95 endif() endif()
93 96 if(JRF_STATIC) if(JRF_STATIC)
94 97 add_library(jrf-static STATIC ${SOURCE_FILES}) add_library(jrf-static STATIC ${SOURCE_FILES})
95 98 list(APPEND TARGETS static) list(APPEND TARGETS static)
96 99 list(APPEND JRF_TARGETS jrf-static) list(APPEND JRF_TARGETS jrf-static)
100 if (NOT TARGET jlib-static)
101 message(FATAL_ERROR "For jrf-static target jlib-static is needed!")
102 endif()
97 103 endif() endif()
98 104
99 105
 
... ... if (JRF_NATIVE)
102 108 message(NOTICE "JRF_NATIVE is enabled, compiling for native arch.") message(NOTICE "JRF_NATIVE is enabled, compiling for native arch.")
103 109 endif() endif()
104 110
111 option(JRF_FORCE_OPTIMIZATIONS
112 "Enen \"-O3\" even when CMAKE_BUILD_TYPE=\"Debug\"" ON
113 )
114 if (NOT JRF_FORCE_OPTIMIZATIONS)
115 message(
116 "JRF: WARNING!!"
117 "Wavefront .obj file importing without optimizations will be very slow."
118 )
119 endif()
120
105 121 set(EXPORT_NAME jrf-${PROJECT_VERSION}) set(EXPORT_NAME jrf-${PROJECT_VERSION})
106 122
107 123 foreach(TARGET ${TARGETS}) foreach(TARGET ${TARGETS})
 
... ... foreach(TARGET ${TARGETS})
112 128 target_link_libraries(jrf-${TARGET} PUBLIC jlib-${TARGET} ${ZIP_LIB}) target_link_libraries(jrf-${TARGET} PUBLIC jlib-${TARGET} ${ZIP_LIB})
113 129 set_target_properties(jrf-${TARGET} PROPERTIES OUTPUT_NAME jrf) set_target_properties(jrf-${TARGET} PROPERTIES OUTPUT_NAME jrf)
114 130
115 target_compile_options(jrf-${TARGET} PRIVATE
116 $<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>
117 $<$<CONFIG:RELEASE>:-O3>
118 $<$<CONFIG:RELWITHDEBINFO>:-O3;-g3;-ggdb>
119 )
131 if (JRF_FORCE_OPTIMIZATIONS)
132 target_compile_options(jrf-${TARGET} PRIVATE -O3
133 $<$<CONFIG:DEBUG>:-g3;-ggdb>
134 $<$<CONFIG:RELWITHDEBINFO>:-g3;-ggdb>
135 )
136 else()
137 target_compile_options(jrf-${TARGET} PRIVATE
138 $<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>
139 $<$<CONFIG:RELEASE>:-O3>
140 $<$<CONFIG:RELWITHDEBINFO>:-O3;-g3;-ggdb>
141 )
142 endif()
120 143 target_compile_features(jrf-${TARGET} PUBLIC cxx_std_17) target_compile_features(jrf-${TARGET} PUBLIC cxx_std_17)
121 144 if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
122 145 target_compile_options(jrf-${TARGET} PRIVATE target_compile_options(jrf-${TARGET} PRIVATE
 
... ... else()
202 225 RENAME jrf-config.cmake RENAME jrf-config.cmake
203 226 ) )
204 227 endif() endif()
228
229 option(JRF_TOOL "Enable jrftool" ON)
230 if (JRF_TOOL)
231 add_executable(jrftool src/tool.cpp)
232
233 option(JRF_TOOL_FORCE_OPTIMIZATIONS
234 "Enen \"-O3\" even when CMAKE_BUILD_TYPE=\"Debug\"" OFF
235 )
236 if (JRF_TOOL_FORCE_OPTIMIZATIONS)
237 target_compile_options(jrftool PRIVATE -O3
238 $<$<CONFIG:DEBUG>:-g3;-ggdb>
239 $<$<CONFIG:RELWITHDEBINFO>:-g3;-ggdb>
240 )
241 else()
242 target_compile_options(jrftool PRIVATE
243 $<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>
244 $<$<CONFIG:RELEASE>:-O3>
245 $<$<CONFIG:RELWITHDEBINFO>:-O3;-g3;-ggdb>
246 )
247 endif()
248
249 if (JRF_SHARED)
250 target_link_libraries(jrftool PRIVATE jrf-shared jlib-shared)
251 elseif(JRF_STATIC)
252 target_link_libraries(jrftool PRIVATE jrf-static jlib-static)
253 endif()
254 target_compile_options(jrftool PRIVATE
255 -Weverything
256 -Wno-c++98-compat-pedantic
257 -Wno-c++11-compat-pedantic
258 -Wno-c++14-compat-pedantic
259 -Wno-padded
260 -Wno-switch-enum
261 )
262 if (JRT_NATIVE)
263 target_compile_options(jrftool PRIVATE -march=native)
264 endif()
265
266 if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
267 OR
268 CMAKE_CXX_COMPILER_ID STREQUAL "GCC")
269 target_compile_options(jrftool PRIVATE -fno-exceptions -fno-rtti -ffast-math)
270 elseif()
271 message(FATAL_ERROR "Unrecognized compiler")
272 endif()
273
274 option(JRF_TOOL_LTO "Enable LTO" ON)
275 if (JRF_TOOL_LTO AND CMAKE_BUILD_TYPE STREQUAL Release)
276 set_property(TARGET jrftool APPEND_STRING PROPERTY LINK_FLAGS -flto)
277 target_compile_options(jrftool PRIVATE -flto)
278 message(STATUS "LTO is enabled")
279 endif()
280
281 set_target_properties(jrftool PROPERTIES SUFFIX "-${PROJECT_VERSION}")
282 set(EXPORT_NAME jrftool-${PROJECT_VERSION})
283 install(TARGETS jrftool EXPORT jrf-config)
284 endif()
File src/tool.cpp added (mode: 100644) (index 0000000..eefe60b)
1 #include <jlib/darray.h>
2 #include <jlib/io_agent_fs.h>
3 #include <jrf/convert.h>
4 #include <jrf/wavefront_obj.h>
5 #include <jrf/write.h>
6 #include <jrf/number.h>
7 #include <charconv>
8
9 struct FileInfo {
10 const char *path;
11 jl::string_ro data;
12 };
13 struct FileIndex {
14 uint64_t line_offset;
15 uint64_t line_size;
16 uint64_t line;
17 uint64_t column;
18 };
19
20 static void
21 print_file_index(FileInfo fi, FileIndex pos) {
22 fprintf(stderr, "%.*s\n", int(pos.line_size), &fi.data[pos.line_offset]);
23 fprintf(stderr, "%*s^\n", int(pos.column), "");
24 }
25 static void
26 print_file_error(FileInfo fi1, FileIndex fi2, const char *m1, const char *m2) {
27 fprintf(stderr, "%s %lu:%lu - %s: %s\n",
28 fi1.path, fi2.line + 1, fi2.column + 1, m1, m2);
29 print_file_index(fi1, fi2);
30 }
31 static void
32 compute_file_index(const jl::string_ro &data, uint64_t offset, FileIndex *p_fi){
33 p_fi->line_offset = 0;
34 uint64_t &line_count = p_fi->line;
35 line_count = 0;
36
37 uint64_t i = 0;
38 offset = jl::min(offset, data.length());
39 for (; i < offset; ++i) {
40 if (data[i] == '\n') {
41 p_fi->line_offset = i + 1;
42 ++line_count;
43 }
44 }
45 for (; i < data.count() && data[i] != '\n'; ++i);
46
47 p_fi->line_size = i - p_fi->line_offset;
48 p_fi->column = offset - p_fi->line_offset;
49 }
50
51 static void
52 print_file_error(const char *path, jl::string_ro file_content,
53 uint64_t error_position,
54 const char *p_err_msg,
55 const char *p_err_msg_type = "Error") {
56 FileIndex fin;
57 compute_file_index(file_content, error_position, &fin);
58 print_file_error({path, file_content}, fin, p_err_msg_type, p_err_msg);
59 }
60
61
62 template<jrf::ResourceType>
63 struct ParsedInfo {};
64
65 using ParsedInfoImage = ParsedInfo<jrf::ResourceType::IMAGE>;
66 using ParsedInfoMesh = ParsedInfo<jrf::ResourceType::MESH>;
67 using ParsedInfoModel = ParsedInfo<jrf::ResourceType::MODEL>;
68 using ParsedInfoScene = ParsedInfo<jrf::ResourceType::SCENE>;
69
70 template<>
71 struct ParsedInfo<jrf::ResourceType::IMAGE> {
72 void destroy() {
73 files.destroy();
74 }
75 jl::rarray<const char*> files;
76 };
77
78 [[nodiscard]] static bool
79 parse_info(uint32_t arg_count, const char *const* args,
80 ParsedInfoImage *p_dst, uint32_t *p_out_readed_args) {
81 uint32_t count;
82 std::from_chars(args[0], args[0] + 99999, count);
83 if (count == 0) {
84 fprintf(stderr, "Nothing to do? Provided 0 sources for an image.\n");
85 return false;
86 }
87 if (arg_count-1 != count) {
88 fprintf(stderr,
89 "Specified %u count of images but %u file paths provided.\n",
90 count, arg_count-1);
91 return false;
92 }
93 if (not p_dst->files.init(count))
94 return false;
95 for (uint32_t i = 0; i < count; ++i)
96 p_dst->files[i] = args[i+1];
97 *p_out_readed_args = count + 1;
98 return true;
99 }
100
101 [[nodiscard]] static bool
102 convert(const ParsedInfoImage &info, jrf::Image *p_dst) {
103 size_t err_i;
104 jrf::Result res = jrf::convert::from_tga(info.files, p_dst, &err_i);
105 if (res != jrf::Result::SUCCESS) {
106 fprintf(stderr, "Error in file %s: %s\n",
107 info.files[err_i], jrf::to_str(res));
108 return false;
109 }
110 return true;
111 }
112
113
114 template<>
115 struct ParsedInfo<jrf::ResourceType::MESH> {
116 void destroy() {}
117 jrf::wavefront_obj::PerAttr<bool> is_defaults_given;
118 jrf::wavefront_obj::DefaultsPerAttr defaults;
119 jl::array<uint8_t, 3> dimensions;
120 jrf::IndexFormat index_format;
121 const char* p_src_file_path;
122 };
123
124 [[nodiscard]] static bool
125 read_defaults(const char **pp_begin, const char *p_end,
126 jrf::wavefront_obj::Defaults *p_dst) {
127 uint32_t attr_i = 0;
128 auto &p_curr = *pp_begin;
129 while (p_end != p_curr) {
130 uint64_t readed;
131 uint64_t length = uint64_t(p_end - p_curr);
132 (*p_dst)[attr_i] = jrf::Float::from_str(p_curr, length, &readed).to_float();
133 if (readed == 0)
134 return false;
135 p_curr += readed;
136 if (p_curr == p_end)
137 return true;
138 if (*p_curr != ',')
139 return false;
140 ++p_curr;
141 ++attr_i;
142 if (attr_i >= p_dst->count())
143 return true;
144 }
145 return true;
146 }
147
148 [[nodiscard]] static bool
149 parse_info(uint32_t arg_count, const char *const *args,
150 ParsedInfoMesh *p_dst, uint32_t *p_out_readed_args) {
151 *p_dst = {};
152 p_dst->index_format = {1};
153
154 uint8_t value = 0;
155 uint8_t mode = 0;
156 uint32_t attr_i = UINT32_MAX;
157 for (uint32_t i = 0; i < arg_count; ++i) {
158 if (args[i][0] != '-') {
159 p_dst->p_src_file_path = args[i];
160 *p_out_readed_args = i + 1;
161 break;
162 }
163 const char *p_begin = args[i] + 1;
164 const char *p_next = p_begin;
165 READ_AGAIN:
166 switch(*p_next) {
167 case 'p': mode = 0; attr_i = jrf::VertexAttribute::POSITION; break;
168 case 't': mode = 0; attr_i = jrf::VertexAttribute::TEX_COORD; break;
169 case 'n': mode = 0; attr_i = jrf::VertexAttribute::NORMAL; break;
170 case 'I': mode = 1; break;
171 case '=': {
172 if (mode != 0) {
173 fprintf(stderr, "Default values must be after an attribute: %s",
174 p_begin);
175 return false;
176 }
177 mode = 2;
178 } break;
179 default: continue;
180 }
181 ++p_next;
182 uint64_t length = strlen(p_next);
183 switch (mode) {
184 case 0: {
185 p_next = std::from_chars(p_next, p_next + length, value).ptr;
186 p_dst->dimensions[attr_i] = value;
187 break;
188 }
189 case 1: {
190 p_next = std::from_chars(p_next, p_next + length, value).ptr;
191 p_dst->index_format = jrf::IndexFormat(value);
192 break;
193 }
194 case 2: {
195 bool s;
196 s = read_defaults(&p_next, p_next + length, &p_dst->defaults[attr_i]);
197 if (not s) {
198 fprintf(stderr,
199 "Wrong default values for an attribute in argument: %s",
200 p_begin);
201 }
202 p_dst->is_defaults_given[attr_i] = true;
203 break;
204 }
205 }
206 if (*p_next != 0)
207 goto READ_AGAIN;
208 }
209 if (p_dst->index_format != 0 and p_dst->index_format != 1
210 and
211 p_dst->index_format != jrf::IndexFormat::U16
212 and
213 p_dst->index_format != jrf::IndexFormat::U32) {
214 fprintf(stderr, "Unknown index format %u.\n", p_dst->index_format);
215 return false;
216 }
217 if (p_dst->p_src_file_path == nullptr) {
218 fprintf(stderr, "I need source file path!\n");
219 return false;
220 }
221 for (auto d : p_dst->dimensions) {
222 if (d > 0)
223 return true;
224 }
225 fprintf(stderr, "Nothing to do! "
226 "Please select at least single dimension with any attribute.\n");
227 return false;
228 }
229
230 [[nodiscard]] static bool
231 convert(const ParsedInfoMesh &info, jrf::Mesh *p_dst) {
232 *p_dst = {};
233 namespace obj = jrf::wavefront_obj;
234 char *p_text;
235 uint64_t text_size;
236 if (not jfs::read_file(info.p_src_file_path, &p_text, &text_size)) {
237 fprintf(stderr, "Error while reading file %s: %s\n",
238 info.p_src_file_path, jfs::error_str());
239 return false;
240 }
241 obj::ParseInfo obj_info = {};
242 obj_info.text = {p_text, text_size};
243 obj_info.dimensions = info.dimensions;
244 obj_info.read_indices = true;
245 obj_info.read_coordinates = true;
246 obj::CoordinatesPerAttr cpa;
247 obj::TrianglesPerAttr tpa;
248 uint64_t err_pos;
249 obj::Result ores;
250 ores = obj::parse(obj_info, &cpa, &tpa, &err_pos);
251 if (ores != obj::Result::SUCCESS) {
252 print_file_error(info.p_src_file_path, obj_info.text, err_pos,
253 obj::to_str(ores), "Error while reading wavefront obj");
254 }
255 jl::deallocate(&p_text);
256 if (ores != obj::Result::SUCCESS)
257 return false;
258 bool is_success = true;
259 uint32_t decoded_vcount = 0;
260 obj::CoordinatesPerAttr decoded_cpa = {};
261
262 for (uint32_t attr_i = 0; attr_i < obj::ATTR_COUNT; ++attr_i) {
263 if (info.dimensions[attr_i] > 0 and cpa[attr_i].size() == 0
264 and not info.is_defaults_given[attr_i]) {
265 fprintf(stderr, "Error while creating mesh: "
266 "Requested attribute with %s is missing in source file %s "
267 "and default values are not specified.\n",
268 jrf::to_str(jrf::VertexAttribute(attr_i)), info.p_src_file_path);
269 is_success = false;
270 goto OBJ_FILE_NOT_SUITABLE;
271 }
272 }
273
274 for (uint32_t attr_i = 0; attr_i < obj::ATTR_COUNT; ++attr_i) {
275 if (cpa[attr_i].count() > 0) {
276 ores = obj::decode(cpa[attr_i], info.dimensions[attr_i],
277 tpa[attr_i], &decoded_cpa[attr_i]);
278 if (ores != obj::Result::SUCCESS) {
279 fprintf(stderr, "Error while decoding obj data %s: %s\n",
280 info.p_src_file_path, obj::to_str(ores));
281 is_success = false;
282 break;
283 }
284 if (decoded_vcount == 0)
285 decoded_vcount = uint32_t(decoded_cpa[attr_i].count()
286 / info.dimensions[attr_i]);
287 else if (decoded_vcount != decoded_cpa[attr_i].count() / info.dimensions[attr_i]) {
288 fprintf(stderr,
289 "Attributes vertices count mismatch. %u vs %lu in attr â„–%u\n",
290 decoded_vcount, decoded_cpa[attr_i].count(), attr_i);
291 is_success = false;
292 break;
293 }
294 }
295 }
296 OBJ_FILE_NOT_SUITABLE:
297 cpa.destroy();
298 tpa.destroy();
299 if (not is_success) {
300 decoded_cpa.destroy();
301 return false;
302 }
303 if (info.index_format == 0) {
304 p_dst->vert.mode = jrf::ResourceMode::DATA;
305 auto &v = p_dst->vert.u.data;
306 v.vecs_count = decoded_vcount;
307 v.attributes = {};
308 uint32_t size = 0;
309 for (uint32_t attr_i = 0; attr_i < obj::ATTR_COUNT; ++attr_i) {
310 auto dcount = info.dimensions[attr_i];
311 v.attributes[attr_i].dimension_count = dcount;
312 v.attributes[attr_i].format = jrf::VertexFormat::F32;
313 v.attributes[attr_i].offset = size;
314 size += dcount * sizeof(float) * decoded_vcount;
315 }
316 v.data_size = size;
317 uint64_t offset = 0;
318 if (not jl::allocate_bytes(&v.p_data, size)) {
319 p_dst->destroy();
320 return false;
321 }
322 uint8_t *p_vdst = reinterpret_cast<uint8_t*>(v.p_data);
323 for (uint32_t attr_i = 0; attr_i < obj::ATTR_COUNT; ++attr_i) {
324 uint32_t vsize = info.dimensions[attr_i] * sizeof(float);
325 if (vsize > 0) {
326 if (decoded_cpa[attr_i].count() > 0) {
327 size = vsize * decoded_vcount;
328 memcpy(p_vdst, decoded_cpa[attr_i].begin(), size);
329 p_vdst += size;
330 }
331 else {
332 for (uint32_t i = 0; i < decoded_vcount; ++i) {
333 memcpy(p_vdst, info.defaults[attr_i].begin(), vsize);
334 p_vdst += vsize;
335 }
336 }
337 }
338 }
339 jassert(uint64_t(p_vdst-reinterpret_cast<uint8_t*>(v.p_data)) == v.data_size,
340 "memcpy failure");
341 jassert(offset == v.data_size, "memcpy failure");
342 decoded_cpa.destroy();
343 return true;
344 }
345 obj::CompressInfo obj_info2;
346 obj_info2.coordinates = {
347 decoded_cpa[0].begin(),
348 decoded_cpa[1].begin(),
349 decoded_cpa[2].begin()
350 };
351 obj_info2.count = decoded_vcount;
352 obj_info2.dimensions = obj_info.dimensions;
353 obj::CoordinatesPtrsPerAttr res_coordinates;
354 uint64_t res_vcount;
355 obj::Indices indices;
356 ores = obj::compress(obj_info2, &res_coordinates, &res_vcount, &indices);
357 decoded_cpa.destroy();
358 if (ores != obj::Result::SUCCESS) { OVERFLOW:
359 fprintf(stderr, "Error while compressing obj data %s: %s\n",
360 info.p_src_file_path, obj::to_str(ores));
361 return false;
362 }
363 p_dst->vert.mode = jrf::ResourceMode::DATA;
364 auto &v = p_dst->vert.u.data;
365 v.attributes = {};
366 v.vecs_count = res_vcount;
367 uint32_t size = 0;
368 for (uint32_t attr_i = 0; attr_i < obj::ATTR_COUNT; ++attr_i) {
369 auto &dcount = info.dimensions[attr_i];
370 v.attributes[attr_i].dimension_count = dcount;
371 v.attributes[attr_i].format = jrf::VertexFormat::F32;
372 v.attributes[attr_i].offset = size;
373 size += dcount * sizeof(float) * v.vecs_count;
374 }
375 uint32_t iformat = info.index_format;
376 uint8_t *p_vdst;
377 if (not jl::allocate_bytes(&v.p_data, size))
378 goto CANCEL;
379 v.data_size = size;
380 p_vdst = reinterpret_cast<uint8_t*>(v.p_data);
381 for (uint32_t attr_i = 0; attr_i < obj::ATTR_COUNT; ++attr_i) {
382 uint32_t vsize = info.dimensions[attr_i] * sizeof(float);
383 if (vsize == 0)
384 continue;
385 if (res_coordinates[attr_i] != nullptr) {
386 size = vsize * uint32_t(v.vecs_count);
387 memcpy(p_vdst, res_coordinates[attr_i], size);
388 p_vdst += size;
389 }
390 else {
391 for (uint32_t i = 0; i < v.vecs_count; ++i) {
392 memcpy(p_vdst, info.defaults[attr_i].begin(), vsize);
393 p_vdst += vsize;
394 }
395 }
396 }
397 jassert(uint64_t(p_vdst-reinterpret_cast<uint8_t*>(v.p_data)) == v.data_size,
398 "memcpy failure");
399 for (auto &p : res_coordinates)
400 if (p != nullptr)
401 jl::deallocate(&p);
402 if (iformat == 1) {
403 if (res_vcount-1 > std::numeric_limits<uint32_t>::max()) {
404 ores = obj::Result::INDEX_VALUE_OVERFLOW;
405 goto OVERFLOW;
406 }
407 iformat = res_vcount-1 > std::numeric_limits<uint16_t>::max() ? 4 : 2;
408 }
409 if (iformat == 4) {
410 p_dst->ind.mode = jrf::ResourceMode::DATA;
411 auto &ind = p_dst->ind.u.data;
412 ind.format = jrf::IndexFormat::U32;
413 ind.size = indices.size();
414 ind.p_data = indices.begin();
415 indices = {};
416 return true;
417 }
418 if (iformat == 2) {
419 if (res_vcount-1 > std::numeric_limits<uint16_t>::max()) {
420 ores = obj::Result::INDEX_VALUE_OVERFLOW;
421 goto OVERFLOW;
422 }
423 jl::rarray<uint16_t> indices_16;
424 if (not indices_16.init(indices.count()))
425 goto CANCEL;
426 for (uint64_t i = 0; i < indices.count(); ++i)
427 indices_16[i] = uint16_t(indices[i]);
428 indices.destroy();
429 p_dst->ind.mode = jrf::ResourceMode::DATA;
430 auto &ind = p_dst->ind.u.data;
431 ind.format = jrf::IndexFormat::U16;
432 ind.size = indices_16.size();
433 ind.p_data = indices_16.begin();
434 return true;
435 }
436 CANCEL:
437 for (auto &p : res_coordinates)
438 if (p != nullptr)
439 jl::deallocate(&p);
440 indices.destroy();
441 p_dst->destroy();
442 return false;
443 }
444
445 template<jrf::ResourceType RT>
446 [[nodiscard]] bool
447 parse_convert(uint32_t arg_count, const char *const*args,
448 uint32_t *p_out_readed_args, typename jrf::Resource<RT>::T *p_dst)
449 {
450 ParsedInfo<RT> info;
451 if (not parse_info(arg_count, args, &info, p_out_readed_args))
452 return false;
453 bool s = convert(info, p_dst);
454 info.destroy();
455 return s;
456 }
457
458
459 [[nodiscard]] static bool
460 write_resource(jrf::ResourceType type, const jrf::ResourceHandle rh,
461 const char* p_write_path) {
462 jl::io_agent_fs file;
463 if (not file.stream.open_file(p_write_path,
464 jfs::stream::CLEAN | jfs::stream::WRITE)) {
465 fprintf(stderr, "Error in file %s: %s\n",
466 p_write_path, jfs::error_str());
467 return false;
468 }
469 jrf::Result res;
470 res = jrf::write(&file, type, rh);
471 file.stream.close();
472 if (res != jrf::Result::SUCCESS) {
473 fprintf(stderr, "Error while writing image %s: %s\n",
474 p_write_path, jrf::to_str(res));
475 return false;
476 }
477 return true;
478 }
479
480 template<jrf::ResourceType RT>
481 [[nodiscard]] bool
482 parse_convert_write(uint32_t arg_count, const char *const*args,
483 const char *p_out_file_path) {
484 ParsedInfo<RT> info;
485 uint32_t junk;
486 if (not parse_info(arg_count, args, &info, &junk))
487 return false;
488 typename jrf::Resource<RT>::T resource;
489 bool s = convert(info, &resource);
490 info.destroy();
491 if (s) {
492 s = write_resource(RT, {reinterpret_cast<jrf::Image*>(&resource)},
493 p_out_file_path);
494 resource.destroy();
495 }
496 return s;
497 }
498
499 template<>
500 [[nodiscard]] bool
501 parse_convert_write<jrf::ResourceType::MODEL>
502 (uint32_t arg_count, const char *const*args, const char *p_out_file_path) {
503 bool is_image_done = false;
504 bool is_mesh_done = false;
505 jrf::Model model = {};
506 jrf::ResourceHandle rh = {reinterpret_cast<jrf::Image*>(&model)};
507 bool is_data;
508 bool success = false;
509 NOW_SAY_AGAIN_WHAT:
510 if (arg_count < 3) {
511 fprintf(stderr, "Wrong model arguments!\n");
512 goto LEAVE_IN_HURRY;
513 }
514 is_data = args[1][0] != 'p';
515 uint32_t readed_args;
516 args += 2;
517 arg_count -= 2;
518 switch(args[-2][0]) {
519 case 'm': {
520 if (is_mesh_done) {
521 fprintf(stderr, "Model must have only a single mesh!\n");
522 goto LEAVE_IN_HURRY;
523 }
524 bool s;
525 auto &mu = model.mesh;
526 if (is_data) {
527 mu.mode = jrf::ResourceMode::DATA;
528 s = parse_convert<jrf::ResourceType::MESH>
529 (arg_count, args, &readed_args, &mu.u.data);
530 }
531 else {
532 mu.mode = jrf::ResourceMode::PATH;
533 s = mu.u.path.init(args[0]);
534 readed_args = 1;
535 }
536 if (not s)
537 goto LEAVE_IN_HURRY;
538 is_mesh_done = true;
539 arg_count -= readed_args;
540 args += readed_args;
541 break;
542 }
543 case 'i': {
544 if (is_image_done) {
545 fprintf(stderr, "Model must have only a single image!\n");
546 goto LEAVE_IN_HURRY;
547 }
548 bool s;
549 auto &iu = model.image;
550 if (is_data) {
551 iu.mode = jrf::ResourceMode::DATA;
552 s = parse_convert<jrf::ResourceType::IMAGE>
553 (arg_count, args, &readed_args, &iu.u.data);
554 }
555 else {
556 iu.mode = jrf::ResourceMode::PATH;
557 s = iu.u.path.init(args[0]);
558 readed_args = 1;
559 }
560 if (not s)
561 goto LEAVE_IN_HURRY;
562 is_image_done = true;
563 arg_count -= readed_args;
564 args += readed_args;
565 break;
566 }
567 }
568 if (not is_mesh_done or not is_image_done)
569 goto NOW_SAY_AGAIN_WHAT;
570 if (arg_count > 0) {
571 fprintf(stderr, "There is %u excess arguments, beginning with \"%s\", "
572 "but I don't care.\n",
573 arg_count, args[0]);
574 }
575 success = write_resource(jrf::ResourceType::MODEL, rh, p_out_file_path);
576 LEAVE_IN_HURRY:
577 model.destroy();
578 return success;
579 }
580
581 [[nodiscard]] constexpr uint32_t
582 string_length_constexpr(const char *p_str) {
583 return *p_str == 0 ? 0 : 1 + string_length_constexpr(p_str + 1);
584 }
585
586 [[nodiscard]] static const char *
587 skip_junk(const char *p_cstr) {
588 for (; *p_cstr != 0;) {
589 if (*p_cstr == '\t' or *p_cstr == '\n' or *p_cstr == ' ') {
590 ++p_cstr;
591 continue;
592 }
593 if (*p_cstr == '#') {
594 for(;;) {
595 ++p_cstr;
596 if (*p_cstr == 0)
597 return p_cstr;
598 if (*p_cstr == '\n') {
599 ++p_cstr;
600 break;
601 }
602 }
603 continue;
604 }
605 break;
606 }
607 return p_cstr;
608 }
609
610 [[nodiscard]] static const char *
611 search_junk(const char *p_cstr) {
612 for (; *p_cstr != 0; ++p_cstr) {
613 auto c = *p_cstr;
614 if (c == '\t' or c == '\n' or c == ' ' or c == '#' or c == '\"')
615 return p_cstr;
616 }
617 return p_cstr;
618 }
619
620 [[nodiscard]] static const char *
621 convert_number(const char *p_begin, const char *p_end, int32_t *p_dst) {
622 return std::from_chars(p_begin, p_end, *p_dst).ptr;
623 }
624 [[nodiscard]] static const char*
625 convert_number(const char *p_begin, const char *p_end, float *p_dst) {
626 uint64_t readed;
627 *p_dst = jrf::Float::from_str(p_begin, uint64_t(p_end - p_begin), &readed)
628 .to_float();
629 return p_begin + readed;
630 }
631
632 template<typename Array>
633 [[nodiscard]] bool
634 convert_number_array(const char **pp_str, const char *p_end, Array *p_array) {
635 auto &p_curr = *pp_str;
636 for (auto &num : *p_array) {
637 if (p_curr == p_end)
638 return false;
639 auto p_next = convert_number(p_curr, p_end, &num);
640 if (p_next == p_curr)
641 return false;
642 p_curr = skip_junk(p_next);
643 }
644 return true;
645 }
646
647 template<>
648 [[nodiscard]] bool
649 parse_convert_write<jrf::ResourceType::SCENE>
650 (uint32_t arg_count, const char *const*args, const char *p_out_file_path) {
651 if (arg_count > 1) {
652 fprintf(stderr,
653 "There is %u excess arguments, must be only one for scene.\n",
654 arg_count - 1);
655 return false;
656 }
657 char *p_text;
658 uint64_t text_size;
659 if (not jfs::read_file(args[0], &p_text, &text_size)) {
660 fprintf(stderr, "Failed to read textual file %s.\n", args[0]);
661 return false;
662 }
663
664 enum Word {
665 OFFSET_SHIFT, OFFSET, POS, TRANSFORM
666 };
667 constexpr static const uint32_t WORD_COUNT = 4;
668 constexpr static const char *WORDS[WORD_COUNT] = {
669 "offset_shift", "offset", "pos", "transform"
670 };
671 constexpr static const uint32_t WORD_LENGTHS[WORD_COUNT] = {
672 string_length_constexpr(WORDS[0]),
673 string_length_constexpr(WORDS[1]),
674 string_length_constexpr(WORDS[2]),
675 string_length_constexpr(WORDS[3]),
676 };
677
678
679 jrf::SceneOptions options;
680 options.set_default();
681 jl::darray<jrf::SceneEntry> entries = {};
682 Word word;
683 const char *p_end = p_text + text_size;
684 const char *p_err = {};
685 bool is_model_mode = false;
686
687 jrf::SceneEntry entry;
688 entry.model_path = {};
689 entry.options.set_default();
690
691 const char *p_curr = p_text;
692 for (; p_curr < p_end;)
693 {
694 p_curr = skip_junk(p_curr);
695 if (p_curr >= p_end)
696 break;
697 if (*p_curr == '\"') {
698 ++p_curr;
699 auto p_end_word = search_junk(p_curr);
700 if (p_end_word == p_curr) {
701 p_err = "There must be model name or must not be <\"> symbol";
702 goto ERROR;
703 }
704 if (*p_end_word != '\"') {
705 p_err = "Can't find <\"> symbol to detect end of the name";
706 goto ERROR;
707 }
708 if (is_model_mode) {
709 if (not entries.insert(entry)) {
710 p_err = "Allocation failure";
711 goto ERROR;
712 }
713 entry.model_path = {};
714 entry.options.set_default();
715 }
716 if (not entry.model_path.init(p_curr, size_t(p_end_word - p_curr))) {
717 p_err = "Allocation failure";
718 goto ERROR;
719 }
720 p_curr = p_end_word + 1;
721 is_model_mode = true;
722 continue;
723 }
724
725 for (uint32_t word_i = 0; word_i < WORD_COUNT; ++word_i) {
726 if (strncmp(WORDS[word_i], p_curr, WORD_LENGTHS[word_i]) == 0) {
727 word = *reinterpret_cast<Word*>(&word_i);
728 p_curr += WORD_LENGTHS[word_i];
729 goto FOUND;
730 }
731 }
732 p_err = "Unrecognized option";
733 goto ERROR;
734 FOUND:
735 p_curr = skip_junk(p_curr);
736 switch (word) {
737 case OFFSET: {
738 bool s;
739 if (not is_model_mode)
740 s = convert_number_array(&p_curr, p_end, &options.offset);
741 else
742 s = convert_number_array(&p_curr, p_end, &entry.options.offset);
743 if (not s) {
744 p_err = "There must be three integer values";
745 goto ERROR;
746 }
747 break;
748 }
749 case OFFSET_SHIFT: {
750 if (is_model_mode) {
751 p_err = "Model does not have option \"offset_shift\"";
752 goto ERROR;
753 }
754 auto p_next = convert_number(p_curr, p_end, &options.offset_shift);
755 if (p_curr == p_next) {
756 p_err = "Failed to read integer value";
757 }
758 p_curr = p_next;
759 break;
760 }
761 case POS: {
762 if (not is_model_mode) {
763 p_err = "Scene does not have option \"pos\"";
764 goto ERROR;
765 }
766 if (not convert_number_array(&p_curr, p_end, &entry.options.position)) {
767 p_err = "Failed to read three floating point values";
768 goto ERROR;
769 }
770 break;
771 }
772 case TRANSFORM: {
773 if (not is_model_mode) {
774 p_err = "Scene does not have option \"transform\"";
775 goto ERROR;
776 }
777 if (not convert_number_array(&p_curr, p_end, &entry.options.transform)) {
778 p_err = "Failed to read 16 (4x4) floating point values";
779 goto ERROR;
780 }
781 break;
782 }
783 }
784 }
785 {
786 if (is_model_mode and not entries.insert(entry)) {
787 p_err = "Allocation failure";
788 goto ERROR;
789 }
790 if (entries.count() == 0) {
791 entry.model_path = {};
792 p_err = "Scene is empty, nothing to save.";
793 goto ERROR;
794 }
795 jrf::Scene scene;
796 scene.options = options;
797 scene.entries = {entries.begin(), entries.end()};
798 jl::deallocate(&p_text);
799 jrf::ResourceHandle rh;
800 rh.p_scene = &scene;
801 bool s = write_resource(jrf::ResourceType::SCENE, rh, p_out_file_path);
802 scene.destroy();
803 return s;
804 }
805 ERROR:
806 print_file_error(args[0],{p_text,text_size},uint64_t(p_curr - p_text),p_err,
807 "Error while parsing scene description");
808 jl::deallocate(&p_text);
809 entries.destroy(&jrf::SceneEntry::destroy);
810 entry.destroy();
811 return false;
812 }
813
814 constexpr static const char * HELP_MSG =
815 "Usage:\n"
816 " jrftool <output file name> <resource type> <resource specific args...>\n"
817 "\n"
818 "Resource types: i (image), m (mesh), M (Model), s (scene).\n"
819 "\n"
820 "To get resource info:\n"
821 " jrftool - <resource type>\n";
822
823 constexpr static const char * IMAGE_HELP_MSG =
824 "Make jrf image from \"Truevision TGA\" format.\n"
825 "Usage:\n"
826 " jrftool <output file name> i <number of layers> <file1> <file2> <...>\n"
827 "\n"
828 "All array levels must have same extent.\n"
829 "Every array layer is separate source file.\n";
830
831 constexpr static const char * MESH_HELP_MSG =
832 "Make jrf mesh from \"Wavefront .obj\" format.\n"
833 " jrftool <output file name> m -<options> [-<options>] <source file name>\n"
834 "\n"
835 "BE CAREFUL!\n"
836 "Producing mesh from obj files with >=50k vertices will take forever!\n"
837 "\n"
838 "Options:\n"
839 " p t n - number of dimensions for vertex attribute:\n"
840 " p (position), t (texture coordinates), n (normals).\n"
841 " Default number of dimentions is 0.\n"
842 " \"=\" is used to specify default attribute values\n"
843 " If default attribute values are not specified,\n"
844 " than source file must contain requested attribute data.\n"
845 " I - index format, one of those:\n"
846 " 0 - does not create indexed mesh\n"
847 " 1 - convert to optimal\n"
848 " 2 - 16-bit integer indices\n"
849 " 4 - 32-bit integer indices\n"
850 " Default index format is 1\n"
851 "\n"
852 "Example: generate mesh file \"mesh.out\" from \"obj.txt\" with:\n"
853 " - 3D positions;\n"
854 " - 2D texture coordinates with default values (0,0);\n"
855 " - 3D normals with default values (0,-1,0);\n"
856 " - indices with omptimal format.\n"
857 " jrftool mesh.out m d -p3 -t2=0.0,0 -n3=0,-1,0 obj.txt\n";
858
859 constexpr static const char * MODEL_HELP_MSG =
860 "Create jrf model from jrf image and jrf mesh.\n"
861 "Usage:\n"
862 " jrftool <output file name> M m <mode> <mesh args> i <mode> <image args>\n"
863 "\n"
864 "Modes:\n"
865 " p - path to resource. Model file will contain path itself.\n"
866 " d - arguments to make resource and store in model directly.\n"
867 "\n"
868 "Example: construct model from obj file and tga image:\n"
869 " jrftool model.jrfM M m d -p3t2n3 obj.txt i d 1 ball.tga\n"
870 "Example: construct model from paths to jrf files:\n"
871 " jrftool model.jrfM M m p mesh.jrfm i p image.jrfi\n";
872
873 constexpr static const char * SCENE_HELP_MSG =
874 "Create binary scene description from textual form.\n"
875 "Usage:\n"
876 " jrftool <output file name> s <path to textual file>\n"
877 "\n"
878 "Scene is number of models and options.\n"
879 "\n"
880 "Textual scene description structure is:\n"
881 " [scene options]\n"
882 " [[\"model1 name\"]\n"
883 " [model1 options]]\n"
884 " [[\"model2 name\"]\n"
885 " [model2 options]]\n"
886 " [...]\n"
887 "\n"
888 "Scene options:\n"
889 " offset - 3D integer position of the scene;\n"
890 " offset_shift - shift of offset, or multiplications by 2.\n"
891 " world_pos.x = offset.x << offset_shift\n"
892 "\n"
893 "Model options:\n"
894 " pos - 3D floating point position;\n"
895 " offset - 3D integer offset of position;\n"
896 " world_pos.x = pos.x + (offset.x << scene.offset_shift)\n"
897 " transform - 4x4 transformation matrix.\n"
898 " model_transform = transform * translation(world_pos)\n"
899 "\n"
900 "Example of textual scene description:\n"
901 " offset 5 -43 123\n"
902 " offset_shift 0\n\n"
903 " \"path_to_model1\"\n"
904 " pos 0.1 -0.5 20\n"
905 " offset 0 0 0\n"
906 " transform 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1\n\n"
907 " \"path_to_model2\"\n"
908 " #comment\n"
909 " #etc ...\n";
910
911 int main(int arg_count, char *args[]) {
912 const char *p_msg;
913 if (arg_count < 3) {
914 p_msg = HELP_MSG;
915 SHOW_HELP:
916 printf("%s",p_msg);
917 return 0;
918 }
919 const char *p_file = args[1];
920 if (p_file[0] == '-' or arg_count < 4) {
921 switch(args[2][0]) {
922 case 'i': p_msg = IMAGE_HELP_MSG; break;
923 case 'm': p_msg = MESH_HELP_MSG; break;
924 case 'M': p_msg = MODEL_HELP_MSG; break;
925 case 's': p_msg = SCENE_HELP_MSG; break;
926 default:
927 fprintf(stderr, "Unknown resource type: %s\n", args[2]);
928 return -1;
929 }
930 goto SHOW_HELP;
931 }
932 bool s = false;
933 uint32_t ac = uint32_t(arg_count) - 3;
934 const char * const *p_arg = args + 3;
935 switch(args[2][0]) {
936 case 'i':
937 s = parse_convert_write<jrf::ResourceType::IMAGE>(ac, p_arg, p_file);
938 break;
939 case 'm':
940 s = parse_convert_write<jrf::ResourceType::MESH>(ac, p_arg, p_file);
941 break;
942 case 'M':
943 s = parse_convert_write<jrf::ResourceType::MODEL>(ac, p_arg, p_file);
944 break;
945 case 's':
946 s = parse_convert_write<jrf::ResourceType::SCENE>(ac, p_arg, p_file);
947 break;
948 default:
949 fprintf(stderr, "Unknown resource type: %s\n", args[2]);
950 return -1;
951 }
952 return s ? 0 : -1;
953 }
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/Jackalope/jrf

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/Jackalope/jrf

Clone this repository using git:
git clone git://git.rocketgit.com/user/Jackalope/jrf

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main