commit dc981e8c59accd42fc74bd804a2e4706365827e1
parent 8aacfd06304ea28481a8b864b139ee35b2a9bd7d
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date: Mon, 25 Apr 2022 15:32:17 +0200
Add STL write feature, add binary format
Diffstat:
| M | src/sstl.c | | | 548 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- |
| M | src/sstl.h | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/test_sstl_load.c | | | 264 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
3 files changed, 813 insertions(+), 62 deletions(-)
diff --git a/src/sstl.c b/src/sstl.c
@@ -30,6 +30,7 @@
#include "sstl.h"
+#include <rsys/rsys.h>
#include <rsys/cstr.h>
#include <rsys/float3.h>
#include <rsys/hash_table.h>
@@ -39,6 +40,7 @@
#include <rsys/stretchy_array.h>
#include <string.h>
+#include <stdio.h>
#ifdef COMPILER_CL
#pragma warning(push)
@@ -47,6 +49,8 @@
#define strtok_r strtok_s
#endif
+#define OK(Expr) if((res = (Expr)) != RES_OK) goto error; else (void)0
+
struct solid {
char* name;
unsigned* indices;
@@ -87,6 +91,18 @@ struct sstl {
ref_T ref;
};
+/* A type for binary files I/O */
+struct tmp {
+ float normal[3];
+ union u {
+ float vertices[3][3];
+ struct vertex vtx[3];
+ } u;
+ unsigned short attrib, foo;
+};
+
+STATIC_ASSERT(sizeof(struct tmp) == 52, Invalid_struct_size);
+
/*******************************************************************************
* Helper functions
******************************************************************************/
@@ -109,25 +125,61 @@ streamer_release(struct streamer* streamer)
sa_release(streamer->buf);
}
+/* Large enough to read "solid\0" */
+#define CHECK_SOLID_LEN 6
+
static char*
-streamer_read_line(struct streamer* streamer)
+streamer_read_line
+ (struct streamer* streamer,
+ int checking_solid)
{
const size_t buf_chunk = 256;
+ size_t read_sz;
char* line;
ASSERT(streamer);
- for(;(line=fgets(streamer->buf, (int)sa_size(streamer->buf), streamer->stream));
+ if(checking_solid) {
+ size_t current = sa_size(streamer->buf);
+ if(current < CHECK_SOLID_LEN) {
+ char* remain = sa_add(streamer->buf, CHECK_SOLID_LEN - current);
+ if(!remain) {
+ FATAL("Not enough memory: couldn't resize the stream buffer.\n");
+ }
+ }
+ read_sz = CHECK_SOLID_LEN;
+ } else {
+ read_sz = sa_size(streamer->buf);
+ }
+
+ for(;(line=fgets(streamer->buf, (int)read_sz, streamer->stream));
++streamer->iline) {
- /* Ensure that the whole line is read */
+ /* If checking for a line starting with "solid", stop reading early if the
+ * line does not. If no error, ensure that the whole line is read */
while(!strrchr(line, '\n')) {
- char* remain = sa_add(streamer->buf, buf_chunk);
- if(!remain) {
- FATAL("Not enough memory: couldn't resize the stream buffer.\n");
+ char* remain;
+ size_t remain_sz;
+ if(checking_solid) {
+ if(0 != strncmp("solid", line, 5)) {
+ /* Dont read further */
+ return line;
+ }
+ remain = line + 5;
+ ASSERT(*remain == '\0');
+ remain_sz = sa_size(streamer->buf) - 5;
+ } else {
+ remain = sa_add(streamer->buf, buf_chunk);
+ if(!remain) {
+ FATAL("Not enough memory: couldn't resize the stream buffer.\n");
+ }
+ remain_sz = buf_chunk;
}
line = streamer->buf;
- if(!fgets(remain, (int)buf_chunk, streamer->stream)) /* EOF */
+ if(!fgets(remain, (int)remain_sz, streamer->stream)) /* EOF */
break;
+
+ read_sz = sa_size(streamer->buf);
+ checking_solid = 0;
}
if(strspn(streamer->buf, " \t\r\n") != strlen(streamer->buf)) { /* Not empty */
@@ -275,9 +327,8 @@ parse_solid_name
goto error;
}
- res = parse_name_string
- (sstl, strtok_r(NULL, "\0", tok_ctx), &solid->name, filename, iline, tok_ctx);
- if(res != RES_OK) goto error;
+ OK(parse_name_string(sstl, strtok_r(NULL, "\0", tok_ctx), &solid->name,
+ filename, iline, tok_ctx));
exit:
return res;
@@ -392,32 +443,31 @@ parse_directive
}
static res_T
-load_stream
+load_ascii_stream
(struct sstl* sstl,
FILE* stream,
- const char* stream_name,
- char** tok_ctx)
+ const char* stream_name)
{
res_T res = RES_OK;
struct streamer streamer;
struct solid solid = SOLID_NULL;
char* line = NULL;
+ char* tok_ctx;
char* tk;
- ASSERT(sstl && stream);
+ ASSERT(sstl && stream && stream_name);
streamer_init(&streamer, stream, stream_name);
clear(sstl);
- line = streamer_read_line(&streamer);
- res = parse_solid_name(sstl, &solid, line, streamer.name, streamer.iline, tok_ctx);
- if(res != RES_OK) goto error;
+ line = streamer_read_line(&streamer, 1);
+ OK(parse_solid_name(sstl, &solid, line, streamer.name, streamer.iline, &tok_ctx));
for(;;) { /* Parse the solid facets */
float normal[3];
unsigned facet[3];
int ivertex;
- line = streamer_read_line(&streamer);
+ line = streamer_read_line(&streamer, 0);
if(!line) {
print_log(sstl, LOG_ERROR, "%s:%lu: missing directive.\n",
streamer.name, (unsigned long)streamer.iline);
@@ -425,7 +475,7 @@ load_stream
goto error;
}
- tk = strtok_r(line, " \t", tok_ctx);
+ tk = strtok_r(line, " \t", &tok_ctx);
/* Stop on "endsolid" directive */
if(!strcmp(tk, "endsolid"))
@@ -433,7 +483,7 @@ load_stream
/* Parse the facet normal directive */
if(strcmp(tk, "facet")
- || !(tk = strtok_r(NULL, " \t", tok_ctx))
+ || !(tk = strtok_r(NULL, " \t", &tok_ctx))
|| strcmp(tk, "normal")) {
print_log(sstl, LOG_ERROR,
"%s:%lu: missing or malformed \"facet normal X Y Z\" directive.\n",
@@ -441,21 +491,18 @@ load_stream
res = RES_BAD_ARG;
goto error;
}
- res = parse_float3
- (sstl, strtok_r(NULL, "\0", tok_ctx), normal, streamer.name, streamer.iline, tok_ctx);
- if(res != RES_OK) goto error;
+ OK(parse_float3(sstl, strtok_r(NULL, "\0", &tok_ctx), normal, streamer.name,
+ streamer.iline, &tok_ctx));
/* Parse the Outer loop directive */
- line = streamer_read_line(&streamer);
- res = parse_outer_loop(sstl, line, streamer.name, streamer.iline, tok_ctx);
- if(res != RES_OK) goto error;
+ line = streamer_read_line(&streamer, 0);
+ OK(parse_outer_loop(sstl, line, streamer.name, streamer.iline, &tok_ctx));
/* Parse the facet vertices. Assume that only 3 vertices are submitted */
FOR_EACH(ivertex, 0, 3) {
- line = streamer_read_line(&streamer);
- res = parse_solid_vertex
- (sstl, &solid, facet+ivertex, line, streamer.name, streamer.iline, tok_ctx);
- if(res != RES_OK) goto error;
+ line = streamer_read_line(&streamer, 0);
+ OK(parse_solid_vertex(sstl, &solid, facet+ivertex, line, streamer.name,
+ streamer.iline, &tok_ctx));
}
if(!f3_is_normalized(normal)) { /* Use geometry normal */
@@ -468,21 +515,18 @@ load_stream
}
f3_set(sa_add(solid.normals, 3), normal);
- line = streamer_read_line(&streamer);
- res = parse_directive(sstl, "endloop", line, streamer.name, streamer.iline, tok_ctx);
- if(res != RES_OK) goto error;
+ line = streamer_read_line(&streamer, 0);
+ OK(parse_directive(sstl, "endloop", line, streamer.name, streamer.iline, &tok_ctx));
- line = streamer_read_line(&streamer);
- res = parse_directive(sstl, "endfacet", line, streamer.name, streamer.iline, tok_ctx);
- if(res != RES_OK) goto error;
+ line = streamer_read_line(&streamer, 0);
+ OK(parse_directive(sstl, "endfacet", line, streamer.name, streamer.iline, &tok_ctx));
}
/* Check the solid/endsolid name consistency */
if(sstl->verbose && solid.name) {
char* name = NULL;
- res = parse_name_string
- (sstl, strtok_r(NULL, "\0", tok_ctx), &name, streamer.name, streamer.iline, tok_ctx);
- if(res != RES_OK) goto error;
+ OK(parse_name_string(sstl, strtok_r(NULL, "\0", &tok_ctx), &name,
+ streamer.name, streamer.iline, &tok_ctx));
/* Compare the "endsolid" name with the one of the "solid" directive */
if(name && strcmp(name, solid.name)) {
@@ -504,6 +548,354 @@ error:
goto exit;
}
+static res_T
+load_binary_stream
+ (struct sstl* sstl,
+ FILE* stream,
+ const char* stream_name,
+ const size_t already_read_count)
+{
+ res_T res = RES_OK;
+ char header[80];
+ unsigned triangles_count;
+ struct solid solid = SOLID_NULL;
+ unsigned i;
+
+ ASSERT(sstl && stream && stream_name && already_read_count <= 80);
+ clear(sstl);
+
+ /* Read header; the first 80 bytes are meaningless and can be safely
+ * (partially) dropped */
+ if(1 != fread(header+already_read_count, sizeof(header)-already_read_count, 1,
+ stream))
+ {
+ print_log(sstl, LOG_ERROR, "%s: missing header.\n", stream_name);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ if(1 != fread(&triangles_count, 4, 1, stream)) {
+ print_log(sstl, LOG_ERROR, "%s: missing triangle count.\n", stream_name);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ for(i = 0; i < triangles_count; i++) {
+ struct tmp tmp;
+ int n;
+ if(1 != fread(&tmp, 50, 1, stream)) {
+ print_log(sstl, LOG_ERROR, "%s: missing triangle data.\n", stream_name);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ f3_set(sa_add(solid.normals, 3), tmp.normal);
+ for(n = 0; n < 3; n++) {
+ /* Look for an already registered vertex position */
+ unsigned* found_id = htable_vertex_find(&sstl->vertex2id, &tmp.u.vtx[n]);
+ unsigned index;
+
+ if(found_id) {
+ index = *found_id;
+ } else {
+ /* Add a new vertex */
+ index = (unsigned)sa_size(solid.vertices) / 3;
+ res = htable_vertex_set(&sstl->vertex2id, &tmp.u.vtx[n], &index);
+ if(res != RES_OK) {
+ print_log(sstl, LOG_ERROR,
+ "%s:%u: couldn't register a vertex position.\n",
+ stream_name, i);
+ }
+ f3_set(sa_add(solid.vertices, 3), tmp.u.vtx[n].xyz);
+ }
+ sa_push(solid.indices, index);
+ }
+ }
+ i = 0;
+ while (1 == fread(header, 1, 1, stream)) i++;
+ if(i) {
+ print_log(sstl, LOG_WARNING,
+ "%s: %u unexpected trailing bytes.\n",
+ stream_name, i);
+ }
+
+ /* Register the solid */
+ sstl->solid = solid;
+
+exit:
+ return res;
+error:
+ solid_clear(&solid);
+ goto exit;
+}
+
+#if defined(COMPILER_GCC)
+ #define FTELL(P, S) \
+ if(-1L == ((P) = ftell(S))) { res = RES_IO_ERR; goto error; }
+#elif deined(COMPILER_CL)
+ #define FTELL(S) \
+ if(-1L == ((P) = _ftelli64(S))) { res = RES_IO_ERR; goto error; }
+#else
+ #error Undefined FTELL macro
+#endif
+
+enum allowed_load_steps {
+ TRY_READ_ASCII = 1,
+ TRY_READ_BINARY = 2,
+ TRY_READ_ALL = 3
+};
+
+static res_T
+load_stream
+ (struct sstl* sstl,
+ FILE* stream,
+ const char* stream_name,
+ int allowed)
+{
+ res_T res = RES_OK;
+ long long int idx1, idx2;
+ int log = (allowed == TRY_READ_ALL);
+ size_t count;
+
+ ASSERT(sstl && stream && stream_name && allowed);
+
+ /* Try reading as an ASCII file; if the file doesn't start with "solid" the
+ * file is not read past the first 5 characters, so that we can continue
+ * reading as a binary file, exploiting the fact that binary files' first 80
+ * characters are meaningless */
+ FTELL(idx1, stream);
+ if(allowed & TRY_READ_ASCII) {
+ if(log) {
+ print_log(sstl, LOG_WARNING,
+ "%s: attempt to read as ASCII file.\n", stream_name);
+ }
+ res = load_ascii_stream(sstl, stream, stream_name);
+ if(res == RES_OK) {
+ if(log) print_log(sstl, LOG_WARNING, "Attempt successful.\n");
+ goto exit;
+ }
+ }
+ if(!(allowed & TRY_READ_BINARY)) goto exit;
+ FTELL(idx2, stream);
+ ASSERT(idx2 >= idx1);
+ count = (size_t)(idx2 - idx1);
+ if(count > CHECK_SOLID_LEN)
+ /* "solid" was found: was an ill-formed ASCII file */
+ return res;
+ if(log) {
+ print_log(sstl, LOG_WARNING,
+ "%s: attempt to read as binary file.\n", stream_name);
+ }
+ res = load_binary_stream(sstl, stream, stream_name, count);
+ if(res != RES_OK) goto error;
+ if(log) print_log(sstl, LOG_WARNING, "Attempt successful.\n");
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+#undef FTELL
+
+static res_T
+load_base(struct sstl* sstl, const char* filename, int allowed)
+{
+ FILE* file;
+ res_T res = RES_OK;
+
+ file = fopen(filename, "r");
+ if(!file) {
+ print_log(sstl, LOG_ERROR, "Error opening `%s'.\n", filename);
+ return RES_IO_ERR;
+ }
+ res = load_stream(sstl, file, filename, allowed);
+ fclose(file);
+ return res;
+}
+
+static res_T
+get_sstl_triangle_normal
+ (const unsigned idx,
+ const void* data,
+ float normal[3])
+{
+ res_T res = RES_OK;
+ const struct sstl* sstl = (struct sstl*)data;
+ struct sstl_desc desc;
+
+ if(!sstl || !normal) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ OK(sstl_get_desc(sstl, &desc));
+ if(idx >= desc.triangles_count) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ ASSERT(3*idx+2 < sa_size(desc.normals));
+ f3_set(normal, desc.normals + 3*idx);
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+get_sstl_triangle_vertices
+ (const unsigned idx,
+ const void* data,
+ float vtx[3][3])
+{
+ res_T res = RES_OK;
+ const struct sstl* sstl = (struct sstl*)data;
+ struct sstl_desc desc;
+ unsigned n;
+
+ if(!sstl || !vtx) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ OK(sstl_get_desc(sstl, &desc));
+ if(idx >= desc.triangles_count) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ for(n = 0; n < 3; n++) {
+ size_t vtx_idx = desc.indices[3*idx + n];
+ ASSERT(3*vtx_idx+2 < sa_size(desc.vertices));
+ f3_set(vtx[n], desc.vertices + 3*vtx_idx);
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static void
+print_sstl_error_log
+ (const void* data,
+ const char* msg,
+ ...)
+{
+ const struct sstl_write_data* sstld = (struct sstl_write_data*)data;
+ va_list vargs_list;
+ va_start(vargs_list, msg);
+ print_log(sstld->data, LOG_ERROR, msg, vargs_list);
+ va_end(vargs_list);
+}
+
+res_T
+sstl_pack_write_data
+ (const struct sstl* sstl,
+ struct sstl_write_data* out)
+{
+ size_t sz;
+ if(!sstl || !out) return RES_BAD_ARG;
+ if(!sstl->solid.indices || !sstl->solid.normals || !sstl->solid.vertices)
+ return RES_BAD_ARG;
+ sz = sa_size(sstl->solid.indices);
+ if(sz % 3 != 0) return RES_BAD_ARG;
+ sz /= 3;
+ if(sz > UINT_MAX) return RES_BAD_ARG;
+ out->name = sstl->solid.name;
+ out->data = sstl;
+ out->get_triangle_normal = get_sstl_triangle_normal;
+ out->get_triangle_vertices = get_sstl_triangle_vertices;
+ out->print_error_log = print_sstl_error_log;
+ out->triangles_count = (unsigned)sz;
+ return RES_OK;
+}
+
+#define OKP(Expr) if((Expr) < 0) { res=RES_IO_ERR; goto error; }
+
+static res_T
+write_stream
+ (const struct sstl_write_data* data,
+ FILE* stream,
+ const char* stream_name,
+ const int binary)
+{
+ res_T res = RES_OK;
+ unsigned i;
+
+ ASSERT(data && stream && stream_name);
+
+ if(data->get_triangle_normal == NULL) {
+ if(data->print_error_log) {
+ data->print_error_log(data,
+ "%s: no getter defined for triangles' normals.\n", stream_name);
+ }
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ if(data->get_triangle_vertices == NULL) {
+ if(data->print_error_log) {
+ data->print_error_log(data,
+ "%s: no getter defined for triangles' vertices.\n", stream_name);
+ }
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ if(binary) {
+ char header[80] = "Binary STL written by the star-stl library";
+ if(1 != fwrite(header, sizeof(header), 1, stream)) {
+ res = RES_IO_ERR;
+ goto error;
+ }
+ ASSERT(sizeof(data->triangles_count == 4));
+ if(1 != fwrite(&data->triangles_count, 4, 1, stream)) {
+ res = RES_IO_ERR;
+ goto error;
+ }
+ for(i = 0; i < data->triangles_count; i++) {
+ struct tmp tmp;
+ res = data->get_triangle_normal(i, data->data, tmp.normal);
+ res = data->get_triangle_vertices(i, data->data, tmp.u.vertices);
+ tmp.attrib = 0;
+ if(1 != fwrite(&tmp, 50, 1, stream)) {
+ res = RES_IO_ERR;
+ goto error;
+ }
+ }
+ } else {
+ if(data->name) {
+ OKP(fprintf(stream, "solid %s\n", data->name));
+ } else {
+ OKP(fprintf(stream, "solid \n"));
+ }
+ for(i = 0; i < data->triangles_count; i++) {
+ float normal[3], vtx[3][3];
+ int n;
+
+ res = data->get_triangle_normal(i, data->data, normal);
+ OKP(fprintf(stream, " facet normal %g %g %g\n", SPLIT3(normal)));
+
+ res = data->get_triangle_vertices(i, data->data, vtx);
+ OKP(fprintf(stream, " outer loop\n"));
+ for(n = 0; n < 3; n++) {
+ OKP(fprintf(stream, " vertex %g %g %g\n", SPLIT3(vtx[n])));
+ }
+ OKP(fprintf(stream, " endloop\n"));
+ OKP(fprintf(stream, " endfacet\n"));
+ }
+ OKP(fprintf(stream, "endsolid \n"));
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+#undef OKP
+
static void
sstl_release(ref_T* ref)
{
@@ -583,31 +975,45 @@ sstl_ref_put(struct sstl* sstl)
}
res_T
+sstl_load_ascii(struct sstl* sstl, const char* filename)
+{
+ if(!sstl || !filename) return RES_BAD_ARG;
+ return load_base(sstl, filename, TRY_READ_ASCII);
+}
+
+res_T
+sstl_load_binary(struct sstl* sstl, const char* filename)
+{
+ if(!sstl || !filename) return RES_BAD_ARG;
+ return load_base(sstl, filename, TRY_READ_BINARY);
+}
+
+res_T
sstl_load(struct sstl* sstl, const char* filename)
{
- FILE* file;
- char* tok_ctx;
- res_T res = RES_OK;
+ if(!sstl || !filename) return RES_BAD_ARG;
+ return load_base(sstl, filename, TRY_READ_ALL);
+}
- if(!sstl || !filename)
- return RES_BAD_ARG;
+res_T
+sstl_load_stream_ascii(struct sstl* sstl, FILE* stream)
+{
+ if(!sstl || !stream) return RES_BAD_ARG;
+ return load_stream(sstl, stream, "STREAM", TRY_READ_ASCII);
+}
- file = fopen(filename, "r");
- if(!file) {
- print_log(sstl, LOG_ERROR, "Error opening `%s'.\n", filename);
- return RES_IO_ERR;
- }
- res = load_stream(sstl, file, filename, &tok_ctx);
- fclose(file);
- return res;
+res_T
+sstl_load_stream_binary(struct sstl* sstl, FILE* stream)
+{
+ if(!sstl || !stream) return RES_BAD_ARG;
+ return load_stream(sstl, stream, "STREAM", TRY_READ_BINARY);
}
res_T
sstl_load_stream(struct sstl* sstl, FILE* stream)
{
- char* tok_ctx;
if(!sstl || !stream) return RES_BAD_ARG;
- return load_stream(sstl, stream, "STREAM", &tok_ctx);
+ return load_stream(sstl, stream, "STREAM", TRY_READ_ALL);
}
res_T
@@ -627,6 +1033,40 @@ sstl_get_desc(const struct sstl* sstl, struct sstl_desc* desc)
return RES_OK;
}
+res_T
+sstl_write
+ (const struct sstl_write_data* data,
+ const int binary,
+ const char* filename)
+{
+ FILE* file;
+ res_T res = RES_OK;
+
+ if(!data || !filename)
+ return RES_BAD_ARG;
+
+ file = fopen(filename, (binary ? "wb" : "w"));
+ if(!file) {
+ if(data->print_error_log) {
+ data->print_error_log(data, "Error opening `%s'.\n", filename);
+ }
+ return RES_IO_ERR;
+ }
+ res = write_stream(data, file, filename, binary);
+ fclose(file);
+ return res;
+}
+
+res_T
+sstl_write_stream
+ (const struct sstl_write_data* data,
+ const int binary,
+ FILE* stream)
+{
+ if(!data || !stream) return RES_BAD_ARG;
+ return write_stream(data, stream, "STREAM", binary);
+}
+
#ifdef COMPILER_CL
#pragma warning(pop)
#endif
diff --git a/src/sstl.h b/src/sstl.h
@@ -62,6 +62,22 @@ struct sstl_desc {
size_t vertices_count;
};
+/* Data descriptor used to write a bunch of triangles to a file in the STL
+ * format.
+ * - name can be NULL,
+ * - print_error_log can be NULL: no log,
+ * - data can be NULL if get_triangle_xxx functions do not use it. */
+struct sstl_write_data {
+ const char* name;
+ const void* data;
+ res_T (*get_triangle_vertices)
+ (const unsigned idx, const void* data, float vtx[3][3]);
+ res_T (*get_triangle_normal)
+ (const unsigned idx, const void* data, float normal[3]);
+ void (*print_error_log)(const void* data, const char* msg, ...);
+ unsigned triangles_count;
+};
+
/* Forward declaration of external types */
struct logger;
struct mem_allocator;
@@ -90,15 +106,62 @@ sstl_ref_put
(struct sstl* sstl);
SSTL_API res_T
+sstl_load_ascii
+ (struct sstl* sstl,
+ const char* filename);
+
+SSTL_API res_T
+sstl_load_stream_ascii
+ (struct sstl* sstl,
+ FILE* stream);
+
+SSTL_API res_T
+sstl_load_binary
+ (struct sstl* sstl,
+ const char* filename);
+
+SSTL_API res_T
+sstl_load_stream_binary
+ (struct sstl* sstl,
+ FILE* stream);
+
+/* Try first loading as an ASCII file, then as a binary file if ASCII failed.
+ * Note that a binary file starting with "solid" will be wrongly identified as
+ * ASCII, thus leading to a failure without trying a binary load.
+ * Also warning and error messages will possibly report on both attempts. */
+SSTL_API res_T
sstl_load
(struct sstl* sstl,
const char* filename);
+/* Try first loading as an ASCII stream, then as a binary stream if ASCII failed.
+ * Note that a binary stream starting with "solid" will be wrongly identified as
+ * ASCII, thus leading to a failure without trying a binary load.
+ * Also warning and error messages will possibly report on both attempts. */
SSTL_API res_T
sstl_load_stream
(struct sstl* sstl,
FILE* stream);
+/* Create a sstl_write_data from a sstl.
+ * The returned descriptor is valid until a new load process */
+SSTL_API res_T
+sstl_pack_write_data
+ (const struct sstl* sstl,
+ struct sstl_write_data* out);
+
+SSTL_API res_T
+sstl_write
+ (const struct sstl_write_data* data,
+ const int binary,
+ const char* filename);
+
+SSTL_API res_T
+sstl_write_stream
+ (const struct sstl_write_data* data,
+ const int binary,
+ FILE* stream);
+
/* The returned descriptor is valid until a new load process */
SSTL_API res_T
sstl_get_desc
diff --git a/src/test_sstl_load.c b/src/test_sstl_load.c
@@ -32,6 +32,7 @@
#include <rsys/clock_time.h>
#include <rsys/float3.h>
#include <rsys/logger.h>
+#include <rsys/rsys.h>
static void
test_basic(struct sstl* sstl)
@@ -141,6 +142,7 @@ test_basic(struct sstl* sstl)
struct sstl_desc desc;
FILE* file;
size_t i;
+ struct sstl_write_data wd;
CHK(sstl != NULL);
@@ -149,6 +151,18 @@ test_basic(struct sstl* sstl)
fwrite(test0, sizeof(char), strlen(test0), file);
fclose(file);
+ CHK(sstl_load_ascii(NULL, NULL) == RES_BAD_ARG);
+ CHK(sstl_load_ascii(sstl, NULL) == RES_BAD_ARG);
+ CHK(sstl_load_ascii(NULL, "test_basic.stl") == RES_BAD_ARG);
+ CHK(sstl_load_ascii(sstl, "none.stl") == RES_IO_ERR);
+ CHK(sstl_load_ascii(sstl, "test_basic.stl") == RES_OK);
+
+ CHK(sstl_load_binary(NULL, NULL) == RES_BAD_ARG);
+ CHK(sstl_load_binary(sstl, NULL) == RES_BAD_ARG);
+ CHK(sstl_load_binary(NULL, "test_basic.stl") == RES_BAD_ARG);
+ CHK(sstl_load_binary(sstl, "none.stl") == RES_IO_ERR);
+ CHK(sstl_load_binary(sstl, "test_basic.stl") == RES_BAD_ARG);
+
CHK(sstl_load(NULL, NULL) == RES_BAD_ARG);
CHK(sstl_load(sstl, NULL) == RES_BAD_ARG);
CHK(sstl_load(NULL, "test_basic.stl") == RES_BAD_ARG);
@@ -174,15 +188,59 @@ test_basic(struct sstl* sstl)
file = tmpfile();
CHK(file != NULL);
fwrite(test1, sizeof(char), strlen(test1), file);
+
+ rewind(file);
+ CHK(sstl_load_stream_ascii(NULL, NULL) == RES_BAD_ARG);
+ CHK(sstl_load_stream_ascii(sstl, NULL) == RES_BAD_ARG);
+ CHK(sstl_load_stream_ascii(NULL, file) == RES_BAD_ARG);
+ CHK(sstl_load_stream_ascii(sstl, file) == RES_OK);
+
+ rewind(file);
+ CHK(sstl_load_stream_binary(NULL, NULL) == RES_BAD_ARG);
+ CHK(sstl_load_stream_binary(sstl, NULL) == RES_BAD_ARG);
+ CHK(sstl_load_stream_binary(NULL, file) == RES_BAD_ARG);
+ CHK(sstl_load_stream_binary(sstl, file) == RES_BAD_ARG);
+
rewind(file);
CHK(sstl_load_stream(NULL, NULL) == RES_BAD_ARG);
CHK(sstl_load_stream(sstl, NULL) == RES_BAD_ARG);
CHK(sstl_load_stream(NULL, file) == RES_BAD_ARG);
- CHK(sstl_load_stream(sstl, file) == RES_OK);
+ CHK(sstl_load_stream_ascii(sstl, file) == RES_OK);
fclose(file);
+ CHK(sstl_pack_write_data(NULL, &wd) == RES_BAD_ARG);
+ CHK(sstl_pack_write_data(sstl, NULL) == RES_BAD_ARG);
+ CHK(sstl_pack_write_data(NULL, NULL) == RES_BAD_ARG);
+ CHK(sstl_pack_write_data(sstl, &wd) == RES_OK);
+ CHK(sstl_write(&wd, 1, "test_basic.stl") == RES_OK);
+ CHK(sstl_load_binary(sstl, "test_basic.stl") == RES_OK);
+
+ CHK(sstl_pack_write_data(sstl, &wd) == RES_OK);
+ wd.data = NULL;
+ CHK(sstl_write(&wd, 1, "test_basic.stl") == RES_BAD_ARG);
+
+ CHK(sstl_pack_write_data(sstl, &wd) == RES_OK);
+ wd.get_triangle_normal = NULL;
+ CHK(sstl_write(&wd, 1, "test_basic.stl") == RES_BAD_ARG);
+
+ CHK(sstl_pack_write_data(sstl, &wd) == RES_OK);
+ wd.get_triangle_vertices = NULL;
+ CHK(sstl_write(&wd, 1, "test_basic.stl") == RES_BAD_ARG);
+
+ CHK(sstl_pack_write_data(sstl, &wd) == RES_OK);
+ wd.triangles_count += 1;
+ CHK(sstl_write(&wd, 1, "test_basic.stl") == RES_BAD_ARG);
+
+ file = tmpfile();
+ CHK(file != NULL);
+ CHK(sstl_pack_write_data(sstl, &wd) == RES_OK);
+ CHK(sstl_write_stream(&wd, 1, file) == RES_OK);
+ rewind(file);
+ CHK(sstl_load_stream_binary(sstl,file) == RES_OK);
+ fclose(file);
+
CHK(sstl_get_desc(sstl, &desc) == RES_OK);
- CHK(strcmp(desc.solid_name, "my_solid") == 0);
+ CHK(desc.solid_name == NULL);
CHK(desc.vertices_count == 3);
CHK(desc.triangles_count == 1);
CHK(desc.indices[0] == 0);
@@ -207,14 +265,9 @@ test_basic(struct sstl* sstl)
file = tmpfile();
fwrite(test3, sizeof(char), strlen(test3), file);
rewind(file);
- CHK(sstl_load_stream(sstl, file) == RES_OK);
+ CHK(sstl_load_stream_binary(sstl, file) == RES_BAD_ARG);
fclose(file);
- CHK(sstl_get_desc(sstl, &desc) == RES_OK);
- CHK(desc.vertices_count == 3);
- CHK(desc.triangles_count == 1);
- CHK(f3_eq(desc.normals, f3(tmp, 0.f, -1.f, 0.f)) == 1);
-
FOR_EACH(i, 0, nbads) {
file = tmpfile();
fwrite(bad[i], sizeof(char), strlen(bad[i]), file);
@@ -222,6 +275,60 @@ test_basic(struct sstl* sstl)
CHK(sstl_load_stream(sstl, file) == RES_BAD_ARG);
fclose(file);
}
+
+ CHK(sstl_pack_write_data(NULL, NULL) == RES_BAD_ARG);
+ CHK(sstl_pack_write_data(sstl, NULL) == RES_BAD_ARG);
+ CHK(sstl_pack_write_data(NULL, &wd) == RES_BAD_ARG);
+
+ CHK(sstl_write(NULL, 0, NULL) == RES_BAD_ARG);
+ CHK(sstl_write(&wd, 0, NULL) == RES_BAD_ARG);
+ CHK(sstl_write(NULL, 0, "test_basic.stl") == RES_BAD_ARG);
+ CHK(sstl_write(&wd, 0, "") == RES_IO_ERR);
+ CHK(sstl_write(&wd, 1, "") == RES_IO_ERR);
+
+ file = fopen("test_basic.stl", "w");
+ CHK(file != NULL);
+ fwrite(test0, sizeof(char), strlen(test0), file);
+ fclose(file);
+
+ CHK(sstl_load_ascii(sstl, "test_basic.stl") == RES_OK);
+ CHK(sstl_pack_write_data(sstl, &wd) == RES_OK);
+ CHK(sstl_write(&wd, 0, "test_basic.stl") == RES_OK);
+ CHK(sstl_load(sstl, "test_basic.stl") == RES_OK);
+ CHK(sstl_get_desc(sstl, &desc) == RES_OK);
+
+ CHK(desc.solid_name == NULL);
+ CHK(desc.vertices_count == 3);
+ CHK(desc.triangles_count == 1);
+ CHK(desc.indices[0] == 0);
+ CHK(desc.indices[1] == 1);
+ CHK(desc.indices[2] == 2);
+ CHK(f3_eq(desc.vertices + 0*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + 1*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + 2*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.normals, f3(tmp, 0.f, -1.f, 0.f)) == 1);
+
+ file = fopen("test_basic.stl", "w");
+ CHK(file != NULL);
+ fwrite(test0, sizeof(char), strlen(test0), file);
+ fclose(file);
+
+ CHK(sstl_load(sstl, "test_basic.stl") == RES_OK);
+ CHK(sstl_pack_write_data(sstl, &wd) == RES_OK);
+ CHK(sstl_write(&wd, 1, "test_basic2.stl") == RES_OK);
+ CHK(sstl_load(sstl, "test_basic2.stl") == RES_OK);
+ CHK(sstl_get_desc(sstl, &desc) == RES_OK);
+
+ CHK(desc.solid_name == NULL);
+ CHK(desc.vertices_count == 3);
+ CHK(desc.triangles_count == 1);
+ CHK(desc.indices[0] == 0);
+ CHK(desc.indices[1] == 1);
+ CHK(desc.indices[2] == 2);
+ CHK(f3_eq(desc.vertices + 0*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + 1*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + 2*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.normals, f3(tmp, 0.f, -1.f, 0.f)) == 1);
}
static void
@@ -264,6 +371,7 @@ test_tetrahedron(struct sstl* sstl)
struct sstl_desc desc;
float tmp[3];
size_t i;
+ struct sstl_write_data wd;
CHK(sstl != NULL);
@@ -298,6 +406,146 @@ test_tetrahedron(struct sstl* sstl)
CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1);
f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f));
CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1);
+
+ rewind(file);
+ CHK(sstl_load_stream_binary(sstl, file) == RES_BAD_ARG);
+ rewind(file);
+ CHK(sstl_load_stream_ascii(sstl, file) == RES_OK);
+
+ CHK(sstl_get_desc(sstl, &desc) == RES_OK);
+ CHK(strcmp(desc.solid_name, "cube_corner") == 0);
+ CHK(desc.vertices_count == 4);
+ CHK(desc.triangles_count == 4);
+
+ CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+
+ CHK(f3_eq(desc.normals + 0*3, f3(tmp, 0.f,-1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.normals + 1*3, f3(tmp, 0.f, 0.f,-1.f)) == 1);
+ CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1);
+ f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f));
+ CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1);
+
+ CHK(sstl_pack_write_data(sstl, &wd) == RES_OK);
+ CHK(sstl_write(&wd, 0, "corner.stl") == RES_OK);
+ CHK(sstl_write(&wd, 1, "corner_bin.stl") == RES_OK);
+
+ CHK(sstl_load_ascii(sstl, "corner_bin.stl") == RES_BAD_ARG);
+ CHK(sstl_load_binary(sstl, "corner.stl") == RES_BAD_ARG);
+
+ CHK(sstl_load(sstl, "corner.stl") == RES_OK);
+
+ CHK(sstl_get_desc(sstl, &desc) == RES_OK);
+ CHK(strcmp(desc.solid_name, "cube_corner") == 0);
+ CHK(desc.vertices_count == 4);
+ CHK(desc.triangles_count == 4);
+
+ CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+
+ CHK(f3_eq(desc.normals + 0*3, f3(tmp, 0.f,-1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.normals + 1*3, f3(tmp, 0.f, 0.f,-1.f)) == 1);
+ CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1);
+ f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f));
+ CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1);
+
+ CHK(sstl_load_ascii(sstl, "corner.stl") == RES_OK);
+
+ CHK(sstl_get_desc(sstl, &desc) == RES_OK);
+ CHK(strcmp(desc.solid_name, "cube_corner") == 0);
+ CHK(desc.vertices_count == 4);
+ CHK(desc.triangles_count == 4);
+
+ CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+
+ CHK(f3_eq(desc.normals + 0*3, f3(tmp, 0.f,-1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.normals + 1*3, f3(tmp, 0.f, 0.f,-1.f)) == 1);
+ CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1);
+ f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f));
+ CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1);
+
+ CHK(sstl_load(sstl, "corner_bin.stl") == RES_OK);
+
+ CHK(sstl_get_desc(sstl, &desc) == RES_OK);
+ CHK(desc.solid_name == NULL); /* binary files don't store a name */
+ CHK(desc.vertices_count == 4);
+ CHK(desc.triangles_count == 4);
+
+ CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+
+ CHK(f3_eq(desc.normals + 0*3, f3(tmp, 0.f,-1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.normals + 1*3, f3(tmp, 0.f, 0.f,-1.f)) == 1);
+ CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1);
+ f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f));
+ CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1);
+
+ CHK(sstl_load_binary(sstl, "corner_bin.stl") == RES_OK);
+
+ CHK(sstl_get_desc(sstl, &desc) == RES_OK);
+ CHK(desc.solid_name == NULL); /* binary files don't store a name */
+ CHK(desc.vertices_count == 4);
+ CHK(desc.triangles_count == 4);
+
+ CHK(f3_eq(desc.vertices + desc.indices[0]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[1]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[2]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[3]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[4]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[5]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[6]*3, f3(tmp, 0.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[7]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[8]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[9]*3, f3(tmp, 1.f, 0.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[10]*3, f3(tmp, 0.f, 1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.vertices + desc.indices[11]*3, f3(tmp, 0.f, 0.f, 1.f)) == 1);
+
+ CHK(f3_eq(desc.normals + 0*3, f3(tmp, 0.f,-1.f, 0.f)) == 1);
+ CHK(f3_eq(desc.normals + 1*3, f3(tmp, 0.f, 0.f,-1.f)) == 1);
+ CHK(f3_eq(desc.normals + 2*3, f3(tmp,-1.f, 0.f, 0.f)) == 1);
+ f3_normalize(tmp, f3(tmp, 1.f, 1.f, 1.f));
+ CHK(f3_eq_eps(desc.normals + 3*3, tmp, 1.e-6f) == 1);
}
int