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)
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
more lost changes c52ad1ddc9fe753cb2ec6f388e4a88f9dfb0fadf Jackalope 2020-05-24 13:30:51
added lost changes 1e60b3d0d8ecda7995f3a102257d8f3c0ca4b15e Jackalope 2020-05-24 13:27:42
CMake installation target 57ba69a5bc17124a96b401c20e68f1602345c4f7 Jackalope 2020-05-24 13:22:15
Moved read,write functions to read.h,write.h, merged vertices,indices and mesh files to mesh.h 365bcb01b01e95448e223de82e9f9e96b51fa789 Jackalope 2020-05-24 13:21:23
Commit c24ea785073b8362a9659e3f275f0329e4799a25 - Wavefront obj file converter.
Author: Jackalope
Author date (UTC): 2020-06-03 01:45
Committer name: Jackalope
Committer date (UTC): 2020-06-04 20:57
Parent(s): 9eb91f41049b2f0dcda1442c1750da4266e97376
Signing key:
Tree: 847bde6c265f5428bae57e0240c13f4b7e29fe86
File Lines added Lines deleted
CMakeLists.txt 1 0
include/jrf/number.h 65 0
include/jrf/wavefront_obj.h 94 0
src/wavefront_obj.cpp 340 0
File CMakeLists.txt changed (mode: 100644) (index 5f8009c..68b5d3d)
... ... list(TRANSFORM INCLUDE_FILES PREPEND jrf/)
56 56 set(SOURCE_FILES set(SOURCE_FILES
57 57 src/result.cpp src/result.cpp
58 58 src/convert.cpp src/convert.cpp
59 src/wavefront_obj.cpp
59 60 ) )
60 61
61 62 configure_file( configure_file(
File include/jrf/number.h added (mode: 100644) (index 0000000..e4fc599)
1 #pragma once
2 #include <memory>
3 #include <cinttypes>
4 #include <charconv>
5
6 namespace jrf {
7 struct Float;
8 }
9
10 struct jrf::Float {
11 [[nodiscard]] static Float
12 from_str(const char* p_str, uint64_t limit, uint64_t *p_readed_dst) {
13 bool is_negative = false;
14 uint64_t integer = 0;
15 uint64_t fraction = 0;
16 uint64_t fraction_scale = 1;
17
18 uint64_t &i = *p_readed_dst;
19 i = 0;
20 if (p_str[i] == '-') {
21 ++i;
22 is_negative = true;
23 }
24 else if (p_str[i] == '+')
25 ++i;
26
27 if (i >= limit) {
28 i = 0;
29 return Float{};
30 }
31
32 while (p_str[i] >= '0' && p_str[i] <= '9') {
33 integer *= 10;
34 integer += uint64_t(p_str[i] - '0');
35 ++i;
36 if (i >= limit)
37 goto BUILT;
38 }
39 if (p_str[i] != '.')
40 goto BUILT;
41 ++i;
42
43 while (p_str[i] >= '0' && p_str[i <= '9']) {
44 fraction *= 10;
45 fraction += uint64_t(p_str[i] - '0');
46 fraction_scale *= 10;
47 ++i;
48 if (i >= limit)
49 break;
50 }
51 BUILT:
52 Float result;
53 result._ = double(integer) + double(fraction) / double(fraction_scale);
54 result._ *= 1 - 2 * is_negative;
55 return result;
56 }
57 [[nodiscard]] double to_double() const { return double(_); }
58 [[nodiscard]] float to_float() const { return float(_); }
59
60 [[nodiscard]] bool operator != (const Float&__) const {
61 return memcmp(&_,&__, sizeof(_)) != 0;
62 }
63 private:
64 double _;
65 };
File include/jrf/wavefront_obj.h added (mode: 100644) (index 0000000..45c691a)
1 #pragma once
2 #include <jlib/darray.h>
3 #include <jlib/array.h>
4 #include <jlib/string.h>
5
6 namespace jrf::wavefront_obj {
7 enum Result {
8 SUCCESS,
9 ALLOCATION_FAIL,
10 EXPECTED_INDEX,
11 EXPECTED_VERTEX,
12 UNEXPECTED_SYMBOL,
13 INDEX_VALUE_OVERFLOW,
14 INDEX_CANT_BE_ZERO
15 };
16 const char*
17 to_str(Result result);
18
19 using Triangle = jl::array<uint32_t, 3>;
20 using Coordinates = jl::rarray<float>;
21 using Triangles = jl::rarray<Triangle>;
22 using Indices = jl::rarray<uint32_t>;
23
24 namespace AttributeN { enum T : uint32_t {
25 POSITION, TEXTURE_COORDINATES, NORMAL
26 }; }
27 using Attribute = AttributeN::T;
28 constexpr static const uint8_t ATTR_COUNT = Attribute::NORMAL+1;
29
30 using Defaults = jl::array<float, 4>;
31
32 template<typename T>
33 using PerAttr = jl::array<T, ATTR_COUNT>;
34
35 using CoordinatesPerAttr = PerAttr<Coordinates>;
36 using CoordinatesPtrsPerAttr = PerAttr<float*>;
37 using TrianglesPerAttr = PerAttr<Triangles>;
38 using DimensionsPerAttr = PerAttr<uint8_t>;
39 using DefaultsPerAttr = PerAttr<Defaults>;
40
41 struct ParseInfo {
42 jl::string_ro text;
43 DimensionsPerAttr dimensions;
44 DefaultsPerAttr defaults;
45 bool read_coordinates;
46 bool read_indices;
47 };
48
49 /// @brief Convert textual representation of wavefront obj to binary data.
50 /// Binary data is just binary representation of text data with triangles and
51 /// vertices, if there is several attributes with different indices -
52 /// data cannot be used directly for rendering, because there is
53 /// "(vertices+indices)-per-attr" instead of "(vertices-per-attr)+indices"
54 /// Data can be decoded to vertices array per attribute and then
55 /// compressed to vertices per attribute + indices.
56 /// @see decode
57 /// @see compress
58 /// Parser is don't care about many features of obj file, so it will return
59 /// an error only if vertex or face text is not followed by correct syntax.
60 /// Parser does not care about amount of vertices per attribute:
61 /// is it equal or not, it will succeed.
62 /// It is possible that there will be no data in textual representation
63 /// filtered with provided ParseInfo options,
64 /// SUCCESS will be returned anyway.
65 /// @return Almost any error. If not SUCCESS, nothing will be created and
66 /// output arguments will be cleared.
67 [[nodiscard]] Result
68 parse(const ParseInfo &in_info,
69 CoordinatesPerAttr *out_p_coordinates_per_attribute,
70 TrianglesPerAttr *out_p_triangle_indices_per_attribute,
71 uint64_t *out_p_char_error_ind);
72
73 /// @brief Convert vertices+indices representation to vertices only.
74 /// @return SUCCESS, ALLOCATION_FAIL, INDEX_VALUE_OVERFLOW
75 [[nodiscard]] Result
76 decode(const Coordinates &in_encoded_coordinates,
77 uint8_t in_dimension_count,
78 const Triangles &in_decoders_triangle_indices,
79 Coordinates *out_p_decoded_coordinates);
80
81 struct CompressInfo {
82 PerAttr<const float*> coordinates;
83 uint64_t count;
84 DimensionsPerAttr dimensions;
85 };
86 /// @brief Compress vertices-per-attr to (vertices-per-attr)+indices.
87 /// Very slow. Indices will represent triangles.
88 /// @return SUCCESS, ALLOCATION_FAIL, INDEX_VALUE_OVERFLOW
89 [[nodiscard]] Result
90 compress(const CompressInfo &info,
91 CoordinatesPtrsPerAttr *p_out_coordinates_per_attr,
92 uint64_t *p_out_vertex_count,
93 Indices *p_out_indices);
94 }
File src/wavefront_obj.cpp added (mode: 100644) (index 0000000..dd31608)
1 #include <jrf/wavefront_obj.h>
2 #include <jrf/number.h>
3 using namespace jrf::wavefront_obj;
4
5 enum DataType : uint8_t {
6 POSITION, TEXTURE_COORDINATE, NORMAL, FACE, INVALID
7 };
8
9 const char *
10 jrf::wavefront_obj::to_str(Result result) {
11 #define STR(x) case Result::x: return #x;
12 switch (result) {
13 STR(SUCCESS)
14 STR(ALLOCATION_FAIL)
15 STR(EXPECTED_INDEX)
16 STR(EXPECTED_VERTEX)
17 STR(UNEXPECTED_SYMBOL)
18 STR(INDEX_VALUE_OVERFLOW)
19 STR(INDEX_CANT_BE_ZERO)
20 }
21 return "unknown error";
22 #undef STR
23 }
24
25 /// @param p_i Character index: in and out
26 [[nodiscard]] static bool
27 skip_junk(jl::string_ro code, uint64_t *p_i, bool skip_new_line = true) {
28 ONE_MORE_TIME:
29 if (*p_i >= code.count())
30 return false;
31 switch(code[*p_i]) {
32 case '\n': if (not skip_new_line)
33 return true;
34 [[fallthrough]];
35 case ' ': {
36 ++(*p_i);
37 goto ONE_MORE_TIME;
38 }
39 case '#': {
40 for(++(*p_i); *p_i < code.count(); ++(*p_i)) {
41 if (code[*p_i] == '\n') {
42 ++(*p_i);
43 goto ONE_MORE_TIME;
44 }
45 }
46 return false;
47 }
48 default: return true;
49 }
50 };
51
52 /// @param p_i Character index: in and out
53 /// Any junk is ignored, not an error
54 [[nodiscard]] static DataType
55 read_type(jl::string_ro code, uint64_t *p_i) {
56 char sym = code[*p_i];
57 ++(*p_i);
58 switch(sym) {
59 default:
60 case 'g':
61 case '\0':
62 case '\n':
63 return DataType::INVALID;
64 case 'v': {
65 if (code.count() > 1) {
66 sym = code[*p_i];
67 if (sym == 'n') {
68 ++(*p_i);
69 return DataType::NORMAL;
70 }
71 if (sym == 't') {
72 ++(*p_i);
73 return TEXTURE_COORDINATE;
74 }
75 }
76 return DataType::POSITION;
77 }
78 case 'f':
79 return DataType::FACE;
80 }
81 };
82
83 using CoordinateStream = jl::darray<float>;
84
85 /// @param p_i Character index: in and out
86 [[nodiscard]] static jrf::wavefront_obj::Result
87 read_vert(jl::string_ro code, uint8_t dims, const Defaults &defaults,
88 CoordinateStream *p_cs, uint64_t *p_i)
89 {
90 uint64_t &i = *p_i;
91 uint8_t j = 0;
92 for (; j < dims; ++j) {
93 float value;
94 uint64_t readed;
95 if (not skip_junk(code, &i, false)
96 or code[i] == '\n') {
97 UNREADED:
98 break;
99 }
100 value = jrf::Float::from_str(code.begin() + i, code.count() - i, &readed)
101 .to_float();
102 if (readed == 0)
103 goto UNREADED;
104 if (not p_cs->insert(value))
105 return Result::ALLOCATION_FAIL;
106 i += readed;
107 }
108 for (; j < dims; ++j) {
109 if (not p_cs->insert(defaults[j]))
110 return Result::ALLOCATION_FAIL;
111 }
112 for (; i < code.count(); ++i) {
113 if (code[i] == '\n') {
114 ++i;
115 break;
116 }
117 }
118 return Result::SUCCESS;
119 };
120
121 [[nodiscard]] static uint32_t
122 index_from_str(const char* p_str, uint64_t str_len, uint64_t *p_readed_dst) {
123 uint32_t integer = 0;
124 uint64_t &i = *p_readed_dst;
125 for (i = 0; i < str_len and p_str[i] >= '0' and p_str[i] <= '9'; ++i)
126 integer = integer * 10 + uint32_t(p_str[i] - '0');
127 return integer;
128 }
129
130 using TriangleStream = jl::darray<Triangle>;
131 using TriangleSrreamPerAttr = PerAttr<TriangleStream>;
132
133 [[nodiscard]] static jrf::wavefront_obj::Result
134 read_face(jl::string_ro code, const DimensionsPerAttr &attr_flags,
135 uint64_t *p_i, TriangleSrreamPerAttr *p_is) {
136 uint64_t &i = *p_i;
137 uint32_t ind_count = 0;
138 uint32_t ind_i = 0;
139
140 jl::array<Triangle, ATTR_COUNT> triangle_attrs;
141 bool is_complete_triangle = false;
142 NEXT_I:
143 if (not skip_junk(code, &i, false))
144 return Result::EXPECTED_INDEX;
145
146 if (code[i] == '\n')
147 return is_complete_triangle ? Result::SUCCESS : EXPECTED_INDEX;
148
149 if (is_complete_triangle) {
150 for (uint32_t attr_i = 0; attr_i < ATTR_COUNT; ++attr_i)
151 triangle_attrs[attr_i][1] = triangle_attrs[attr_i][0];
152 ind_i = 0;
153 }
154
155 for (uint32_t attr_i = 0; attr_i < ATTR_COUNT; ++attr_i) {
156 uint64_t readed;
157 uint32_t value;
158 value = index_from_str(code.begin() + i, code.count() - i, &readed);
159 i += readed;
160 if (readed == 0) {
161 if (attr_i == 0)
162 return Result::EXPECTED_INDEX;
163 triangle_attrs[attr_i][ind_i] = triangle_attrs[0][ind_i];
164 }
165 else {
166 if (value == 0)
167 return Result::INDEX_CANT_BE_ZERO;
168 triangle_attrs[attr_i][ind_i] = value - 1;
169 }
170 if (code[i] == '/') {
171 ++i;
172 continue;
173 }
174 }
175
176 ++ind_i;
177 ++ind_count;
178
179 is_complete_triangle = ind_count >= 3;
180 if (is_complete_triangle) {
181 for (uint32_t attr_i = 0; attr_i < ATTR_COUNT; ++attr_i) {
182 if (attr_flags[attr_i]
183 and
184 not (*p_is)[attr_i].insert(triangle_attrs[attr_i]))
185 return Result::ALLOCATION_FAIL;
186 }
187 }
188 goto NEXT_I;
189 };
190
191 [[nodiscard]] Result jrf::wavefront_obj::
192 parse(const ParseInfo &info,
193 CoordinatesPerAttr *p_coordinates_out,
194 TrianglesPerAttr *p_triangle_out,
195 uint64_t *p_char_error_ind)
196 {
197 *p_coordinates_out = {};
198 *p_triangle_out = {};
199 if (not (info.read_indices or info.read_indices))
200 return Result::SUCCESS;
201
202 PerAttr<CoordinateStream> coords = {};
203 TriangleSrreamPerAttr triangles = {};
204
205 Result res;
206 uint64_t &i = *p_char_error_ind;
207 auto &text = info.text;
208
209 for (i = 0; i < text.count();) {
210 if (not skip_junk(text, &i))
211 break;
212
213 DataType dtype = read_type(text, &i);
214 if (dtype < ATTR_COUNT and info.read_coordinates)
215 {
216 if (not info.dimensions[dtype])
217 goto SKIP;
218 res = read_vert(text, info.dimensions[dtype], info.defaults[dtype],
219 &coords[dtype], &i);
220 if (res != Result::SUCCESS)
221 goto CANCEL;
222 }
223 else if (dtype == FACE and info.read_indices)
224 {
225 res = read_face(text, info.dimensions, &i, &triangles);
226 if (res != Result::SUCCESS)
227 goto CANCEL;
228 }
229 else {
230 SKIP:
231 for(; i < text.count(); ++i) {
232 if (text[i] == '\n') {
233 ++i;
234 break;
235 }
236 }
237 }
238 }
239 for (uint32_t attr_i = 0; attr_i < ATTR_COUNT; ++attr_i) {
240 auto &cs = coords[attr_i];
241 (*p_coordinates_out)[attr_i] = {cs.begin(), cs.end()};
242 auto &ts = triangles[attr_i];
243 (*p_triangle_out)[attr_i] = {ts.begin(), ts.end()};
244 }
245 return Result::SUCCESS;
246
247 CANCEL:
248 coords.destroy();
249 triangles.destroy();
250 return res;
251 }
252
253 [[nodiscard]] Result jrf::wavefront_obj::
254 decode(const Coordinates &in_coordinates,
255 uint8_t dimension_count,
256 const Triangles &in_triangle_indices,
257 Coordinates *p_out_coordinates)
258 {
259 uint64_t decoded_count = in_triangle_indices.count() * 3 * dimension_count;
260 if (not p_out_coordinates->init(decoded_count))
261 return Result::ALLOCATION_FAIL;
262 float *p_dst = p_out_coordinates->begin();
263 for (auto &triangle : in_triangle_indices) {
264 for (auto &index : triangle) {
265 const float *p_src = in_coordinates.begin() + index * dimension_count;
266 if (p_src >= in_coordinates.end())
267 return Result::INDEX_VALUE_OVERFLOW;
268 memcpy(p_dst, p_src, sizeof(float) * dimension_count);
269 p_dst += dimension_count;
270 }
271 }
272 return Result::SUCCESS;
273 }
274
275 [[nodiscard]] Result jrf::wavefront_obj::
276 compress(const CompressInfo &info,
277 CoordinatesPtrsPerAttr *p_out_coordinates_per_attr,
278 uint64_t *p_out_vertex_count,
279 Indices *p_out_indices)
280 {
281 *p_out_coordinates_per_attr = {};
282 *p_out_indices = {};
283
284 if (info.count > std::numeric_limits<uint32_t>::max())
285 return Result::INDEX_VALUE_OVERFLOW;
286
287 auto &out_csas = *p_out_coordinates_per_attr;
288 auto &out_is = *p_out_indices;
289 uint64_t vcount = 0;
290
291 for (uint32_t attr_i = 0; attr_i < ATTR_COUNT; ++attr_i) {
292 auto &out_cs = out_csas[attr_i];
293 if (info.coordinates[attr_i] == nullptr)
294 out_cs = nullptr;
295 else if (not jl::allocate(&out_cs, info.count * info.dimensions[attr_i]))
296 goto CANCEL;
297 }
298 if (not out_is.init(info.count))
299 goto CANCEL;
300
301 for (uint64_t i = 0; i < info.count; ++i) {
302 // Search if vertex is duplicate
303 uint64_t index;
304 for (index = 0; index < vcount; ++index) {
305 for (uint32_t attr_i = 0; attr_i < ATTR_COUNT; ++attr_i) {
306 if (info.coordinates[attr_i] == nullptr)
307 continue;
308 auto dc = info.dimensions[attr_i];
309 const float *p1 = info.coordinates[attr_i] + dc * i;
310 float *p2 = out_csas[attr_i] + dc * index;
311 jassert(p1 + dc <= info.coordinates[attr_i] + dc * info.count, "know you");
312 jassert(p2 + dc <= out_csas[attr_i] + info.count * dc, "you know");
313 if (memcmp(p1, p2, dc * sizeof(float)) != 0)
314 goto NEXT_SEARCH_INDEX;
315 }
316 goto FOUND;
317 NEXT_SEARCH_INDEX:;
318 }
319
320 for (uint32_t attr_i = 0; attr_i < ATTR_COUNT; ++attr_i) {
321 if (info.coordinates[attr_i] == nullptr)
322 continue;
323 auto dc = info.dimensions[attr_i];
324 const float *p_src = info.coordinates[attr_i] + dc * i;
325 float *p_dst = out_csas[attr_i] + dc * index;
326 memcpy(p_dst, p_src, dc * sizeof(float));
327 }
328 ++vcount;
329 FOUND:
330 out_is[i] = uint32_t(index);
331 }
332 *p_out_vertex_count = vcount;
333 return Result::SUCCESS;
334 CANCEL:
335 for (auto &p : out_csas)
336 if (p != nullptr)
337 jl::deallocate(&p);
338 p_out_indices->destroy();
339 return Result::ALLOCATION_FAIL;
340 }
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