libstl-c

C STL file-format library
git clone https://git.sinitax.com/sinitax/libstl-c
Log | Files | Refs | LICENSE | sfeed.txt

commit 2522effc2444a6cb4eb9f3b6168d344983d72170
Author: Louis Burda <quent.burda@gmail.com>
Date:   Thu, 16 Mar 2023 01:09:07 +0100

Initial version

Diffstat:
A.gitignore | 3+++
AMakefile | 38++++++++++++++++++++++++++++++++++++++
Ainclude/stl.h | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alibstl.api | 2++
Alibstl.lds | 6++++++
Asrc/bench.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/stl.c | 366+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 564 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +build +compile_commands.json +.cache diff --git a/Makefile b/Makefile @@ -0,0 +1,38 @@ +PREFIX ?= /usr/local +LIBDIR ?= /lib + +CFLAGS = -I include + +ifeq "$(DEBUG)" "1" +CFLAGS += -g +endif + +all: build/libstl.a build/libstl.so build/bench + +clean: + rm -rf build + +build: + mkdir build + +build/libstl.a: src/stl.c include/stl.h libstl.api | build + $(CC) -c -o build/tmp.o $< $(CFLAGS) $(LDLIBS) + objcopy --keep-global-symbols=libstl.api build/tmp.o build/fixed.o + ar rcs $@ build/fixed.o + +build/libstl.so: src/stl.c include/stl.h libstl.lds | build + $(CC) -o $@ $< -fPIC $(CFLAGS) \ + -shared -Wl,-version-script libstl.lds + +build/bench: src/bench.c build/libstl.a | build + $(CC) -o $@ $^ $(CFLAGS) $(LDLIBS) + +install: + install -m755 build/libstl.a -t "$(DESTDIR)$(PREFIX)$(LIBDIR)" + install -m755 build/libstl.so -t "$(DESTDIR)$(PREFIX)$(LIBDIR)" + +uninstall: + rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/libstl.a" + rm -f "$(DESTDIR)$(PREFIX)$(LIBDIR)/libstl.so" + +.PHONY: all clean install uninstall diff --git a/include/stl.h b/include/stl.h @@ -0,0 +1,92 @@ +#pragma once + +#include <stdbool.h> +#include <stdlib.h> + +#define STL_BUFMAX 256 + +#define STL_STRERROR_INIT \ + [STL_OK] = "Success", \ + [STL_DONE] = "Done processing", \ + [STL_INVALID] = "Invalid stl file", \ + [STL_INVALID_ARG] = "Invalid argument", \ + [STL_INCOMPLETE] = "Unexpected end of file" \ + +enum { + STL_OK, + STL_DONE, + STL_INVALID, + STL_INVALID_ARG, + STL_INCOMPLETE, +}; + +enum { + STL_TYPE_DETECT, + STL_TYPE_ASCII, + STL_TYPE_BINARY, +}; + +enum { + STL_RES_SOLID_NAME, + STL_RES_HEADER, + STL_RES_TRIANGLE, + STL_RES_FILETYPE +}; + +struct stl_vertex { + union { + struct { + float x, y, z; + }; + struct { + float dim[3]; + }; + }; +}; + +struct stl_triangle { + struct stl_vertex vtx[3]; + struct stl_vertex normal; +}; + +struct stl_span { + const char *str; + size_t len; +}; + +struct stl_result { + int type; + union { + struct stl_span solid_name; + struct stl_span header; + struct stl_triangle tri; + int filetype; + }; +}; + +struct stl { + char buf[STL_BUFMAX]; + size_t buflen; + + const void *chunk; + const void *pos; + size_t nleft; + + struct stl_triangle tri; + + int type; + union { + struct { + int state; + } ascii; + struct { + int state; + size_t index; + size_t count; + } bin; + }; +}; + +void stl_init(struct stl *stl, int type); +int stl_feed(struct stl *stl, struct stl_result *res, + const void *chunk, size_t size); diff --git a/libstl.api b/libstl.api @@ -0,0 +1,2 @@ +stl_init +stl_feed diff --git a/libstl.lds b/libstl.lds @@ -0,0 +1,6 @@ +LIBSTL_1.0 { + global: + stl_init; + stl_feed; + local: *; +}; diff --git a/src/bench.c b/src/bench.c @@ -0,0 +1,57 @@ +#include "stl.h" + +#include <err.h> +#include <stdio.h> +#include <stdlib.h> + +static char buf[BUFSIZ]; + +static char *stl_err[] = { + STL_STRERROR_INIT +}; + +int +main(int argc, const char **argv) +{ + struct stl_result res; + struct stl stl; + size_t count; + int n, rc; + + stl_init(&stl, STL_TYPE_DETECT); + + count = 0; + while (1) { + n = fread(buf, 1, BUFSIZ, stdin); + if (n <= 0) errx(1, "truncated input"); + + while (1) { + rc = stl_feed(&stl, &res, buf, n); + if (rc == STL_DONE) break; + if (rc == STL_INCOMPLETE) break; + if (rc != STL_OK) errx(1, "libstl: %s", stl_err[rc]); + + switch (res.type) { + case STL_RES_FILETYPE: + printf("filetype: %s\n", + res.filetype == STL_TYPE_ASCII + ? "ascii" : "binary"); + break; + case STL_RES_HEADER: + printf("header: %s\n", res.header.str); + break; + case STL_RES_SOLID_NAME: + printf("name: %s\n", res.solid_name.str); + break; + case STL_RES_TRIANGLE: + count += 1; + break; + } + } + + if (rc == STL_DONE) + break; + } + + printf("triangles: %lu\n", count); +} diff --git a/src/stl.c b/src/stl.c @@ -0,0 +1,366 @@ +#include "stl.h" + +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) > (b) ? (b) : (a)) + +enum { + STL_ASCII_SOLID, + STL_ASCII_SOLID_NAME, + STL_ASCII_ENDSOLID_CHECK, + STL_ASCII_FACET_NORMAL, + STL_ASCII_FACET_NORMAL_I, + STL_ASCII_FACET_NORMAL_J, + STL_ASCII_FACET_NORMAL_K, + STL_ASCII_OUTER_LOOP, + STL_ASCII_VERTEX_1, + STL_ASCII_VERTEX_1_X, + STL_ASCII_VERTEX_1_Y, + STL_ASCII_VERTEX_1_Z, + STL_ASCII_VERTEX_2, + STL_ASCII_VERTEX_2_X, + STL_ASCII_VERTEX_2_Y, + STL_ASCII_VERTEX_2_Z, + STL_ASCII_VERTEX_3, + STL_ASCII_VERTEX_3_X, + STL_ASCII_VERTEX_3_Y, + STL_ASCII_VERTEX_3_Z, + STL_ASCII_ENDLOOP, + STL_ASCII_ENDFACET, + STL_ASCII_ENDSOLID +}; + +enum { + STL_BIN_HEADER, + STL_BIN_TRIANGLE +}; + +static const uint8_t ascii_fsm[] = { + [STL_ASCII_SOLID] = STL_ASCII_SOLID_NAME, + [STL_ASCII_SOLID_NAME] = STL_ASCII_ENDSOLID_CHECK, + [STL_ASCII_ENDSOLID_CHECK] = STL_ASCII_FACET_NORMAL, + [STL_ASCII_FACET_NORMAL] = STL_ASCII_FACET_NORMAL_I, + [STL_ASCII_FACET_NORMAL_I] = STL_ASCII_FACET_NORMAL_K, + [STL_ASCII_FACET_NORMAL_K] = STL_ASCII_FACET_NORMAL_J, + [STL_ASCII_FACET_NORMAL_J] = STL_ASCII_OUTER_LOOP, + [STL_ASCII_OUTER_LOOP] = STL_ASCII_VERTEX_1, + [STL_ASCII_VERTEX_1] = STL_ASCII_VERTEX_1_X, + [STL_ASCII_VERTEX_1_X] = STL_ASCII_VERTEX_1_Y, + [STL_ASCII_VERTEX_1_Y] = STL_ASCII_VERTEX_1_Z, + [STL_ASCII_VERTEX_1_Z] = STL_ASCII_VERTEX_2, + [STL_ASCII_VERTEX_2] = STL_ASCII_VERTEX_2_X, + [STL_ASCII_VERTEX_2_X] = STL_ASCII_VERTEX_2_Y, + [STL_ASCII_VERTEX_2_Y] = STL_ASCII_VERTEX_2_Z, + [STL_ASCII_VERTEX_2_Z] = STL_ASCII_VERTEX_3, + [STL_ASCII_VERTEX_3] = STL_ASCII_VERTEX_3_X, + [STL_ASCII_VERTEX_3_X] = STL_ASCII_VERTEX_3_Y, + [STL_ASCII_VERTEX_3_Y] = STL_ASCII_VERTEX_3_Z, + [STL_ASCII_VERTEX_3_Z] = STL_ASCII_ENDLOOP, + [STL_ASCII_ENDLOOP] = STL_ASCII_ENDFACET, + [STL_ASCII_ENDFACET] = STL_ASCII_ENDSOLID_CHECK, +}; + +static void +stl_skip_spn(struct stl *stl, const char *spn) +{ + const char *c; + size_t i; + + c = stl->pos; + for (i = 0; i < stl->nleft; i++, c++) { + if (!strchr(spn, *c)) + break; + } + stl->pos += i; + stl->nleft -= i; +} + +static int +stl_read_n(struct stl *stl, size_t n) +{ + size_t cnt; + + if (stl->buflen >= n) + return STL_OK; + + cnt = MIN(n - stl->buflen, stl->nleft); + memcpy(stl->buf + stl->buflen, stl->pos, cnt); + stl->buflen += cnt; + stl->pos += cnt; + stl->nleft -= cnt; + + if (stl->buflen < n) + return STL_INCOMPLETE; + + return STL_OK; +} + +static int +stl_read_expect(struct stl *stl, const char *str, size_t n) +{ + size_t i, cnt; + int rc; + + rc = stl_read_n(stl, n); + if (rc) return rc; + + if (strncmp(stl->buf, str, n)) + return STL_INVALID; + + stl->buflen = 0; + + return STL_OK; +} + +static int +stl_read_until(struct stl *stl, size_t *len, char *any, size_t max) +{ + const char *c; + size_t i; + + c = stl->pos; + for (i = 0; i < stl->nleft; i++, c++) { + if (strchr(any, *c)) { + *len = stl->buflen; + stl->buflen = 0; + return STL_OK; + } + if (stl->buflen >= max) + return STL_INVALID; + stl->buf[stl->buflen++] = *c; + stl->pos++; + stl->nleft--; + } + + return STL_INCOMPLETE; +} + +static int +stl_feed_binary(struct stl *stl, struct stl_result *res, + const void *chunk, size_t size) +{ + int rc; + + if (stl->bin.index == stl->bin.count) + return STL_DONE; + + while (stl->nleft > 0) { + switch (stl->bin.state) { + case STL_BIN_HEADER: + rc = stl_read_n(stl, 84); + if (rc) return rc; + stl->bin.count = le32toh(*(uint32_t *)(stl->buf + 80)); + stl->bin.state = STL_BIN_TRIANGLE; + res->type = STL_RES_HEADER; + res->header.str = stl->buf; + res->header.len = 80; + return STL_OK; + case STL_BIN_TRIANGLE: + rc = stl_read_n(stl, 50); + if (rc) return rc; + res->type = STL_RES_TRIANGLE; + res->tri.normal.x = le32toh(*(uint32_t *)(stl->buf + 0)); + res->tri.normal.y = le32toh(*(uint32_t *)(stl->buf + 4)); + res->tri.normal.z = le32toh(*(uint32_t *)(stl->buf + 8)); + res->tri.vtx[0].x = le32toh(*(uint32_t *)(stl->buf + 12)); + res->tri.vtx[0].y = le32toh(*(uint32_t *)(stl->buf + 16)); + res->tri.vtx[0].z = le32toh(*(uint32_t *)(stl->buf + 20)); + res->tri.vtx[1].x = le32toh(*(uint32_t *)(stl->buf + 24)); + res->tri.vtx[1].y = le32toh(*(uint32_t *)(stl->buf + 28)); + res->tri.vtx[1].z = le32toh(*(uint32_t *)(stl->buf + 32)); + res->tri.vtx[2].x = le32toh(*(uint32_t *)(stl->buf + 36)); + res->tri.vtx[2].y = le32toh(*(uint32_t *)(stl->buf + 40)); + res->tri.vtx[2].z = le32toh(*(uint32_t *)(stl->buf + 40)); + stl->bin.index++; + return STL_OK; + default: + return STL_INVALID_ARG; + } + } + + return STL_INCOMPLETE; +} + +static int +stl_feed_ascii(struct stl *stl, struct stl_result *res, + const void *chunk, size_t size) +{ + char *end; + float val; + size_t len; + int rc; + + while (stl->nleft > 0) { + switch (stl->ascii.state) { + case STL_ASCII_SOLID: + rc = stl_read_expect(stl, "solid ", 6); + if (rc) return rc; + break; + case STL_ASCII_SOLID_NAME: + rc = stl_read_until(stl, &len, "\n", STL_BUFMAX - 1); + if (rc) return rc; + stl->buf[len] = '\0'; + res->type = STL_RES_SOLID_NAME; + res->solid_name.str = stl->buf; + res->solid_name.len = len; + stl->ascii.state = STL_ASCII_FACET_NORMAL; + return STL_OK; + case STL_ASCII_FACET_NORMAL: + if (!stl->buflen) stl_skip_spn(stl, " \t\v\n\r"); + rc = stl_read_expect(stl, "facet normal ", 13); + if (rc) return rc; + break; + case STL_ASCII_OUTER_LOOP: + if (!stl->buflen) stl_skip_spn(stl, " \t\v\n\r"); + rc = stl_read_expect(stl, "outer loop", 10); + if (rc) return rc; + break; + case STL_ASCII_FACET_NORMAL_I: + case STL_ASCII_FACET_NORMAL_J: + case STL_ASCII_FACET_NORMAL_K: + case STL_ASCII_VERTEX_1_X: + case STL_ASCII_VERTEX_1_Y: + case STL_ASCII_VERTEX_1_Z: + case STL_ASCII_VERTEX_2_X: + case STL_ASCII_VERTEX_2_Y: + case STL_ASCII_VERTEX_2_Z: + case STL_ASCII_VERTEX_3_X: + case STL_ASCII_VERTEX_3_Y: + case STL_ASCII_VERTEX_3_Z: + if (!stl->buflen) stl_skip_spn(stl, " \t"); + rc = stl_read_until(stl, &len, " \n", STL_BUFMAX - 1); + if (rc) return rc; + stl->buf[len] = '\0'; + val = strtof(stl->buf, &end); + if (end && *end) return STL_INVALID; + switch (stl->ascii.state) { + case STL_ASCII_FACET_NORMAL_I: + stl->tri.normal.x = val; + break; + case STL_ASCII_FACET_NORMAL_J: + stl->tri.normal.y = val; + break; + case STL_ASCII_FACET_NORMAL_K: + stl->tri.normal.z = val; + break; + case STL_ASCII_VERTEX_1_X: + stl->tri.vtx[0].x = val; + break; + case STL_ASCII_VERTEX_1_Y: + stl->tri.vtx[0].y = val; + break; + case STL_ASCII_VERTEX_1_Z: + stl->tri.vtx[0].z = val; + break; + case STL_ASCII_VERTEX_2_X: + stl->tri.vtx[1].x = val; + break; + case STL_ASCII_VERTEX_2_Y: + stl->tri.vtx[1].y = val; + break; + case STL_ASCII_VERTEX_2_Z: + stl->tri.vtx[1].z = val; + break; + case STL_ASCII_VERTEX_3_X: + stl->tri.vtx[2].x = val; + break; + case STL_ASCII_VERTEX_3_Y: + stl->tri.vtx[2].y = val; + break; + case STL_ASCII_VERTEX_3_Z: + stl->tri.vtx[2].z = val; + break; + } + break; + case STL_ASCII_VERTEX_1: + case STL_ASCII_VERTEX_2: + case STL_ASCII_VERTEX_3: + if (!stl->buflen) stl_skip_spn(stl, " \t\v\n\r"); + rc = stl_read_expect(stl, "vertex", 6); + if (rc) return rc; + break; + case STL_ASCII_ENDLOOP: + if (!stl->buflen) stl_skip_spn(stl, " \t\v\n\r"); + rc = stl_read_expect(stl, "endloop", 7); + if (rc) return rc; + break; + case STL_ASCII_ENDFACET: + if (!stl->buflen) stl_skip_spn(stl, " \t\v\n\r"); + rc = stl_read_expect(stl, "endfacet", 8); + if (rc) return rc; + res->type = STL_RES_TRIANGLE; + res->tri = stl->tri; + stl->ascii.state = STL_ASCII_ENDSOLID_CHECK; + return STL_OK; + case STL_ASCII_ENDSOLID_CHECK: + if (!stl->buflen) stl_skip_spn(stl, " \t\v\n\r"); + rc = stl_read_expect(stl, "endsolid", 8); + if (!rc) return STL_DONE; + break; + defualt: + return STL_INVALID_ARG; + }; + stl->ascii.state = ascii_fsm[stl->ascii.state]; + } + + return STL_INCOMPLETE; +} + +void +stl_init(struct stl *stl, int type) +{ + stl->type = type; + stl->buflen = 0; + stl->chunk = NULL; + stl->pos = NULL; + stl->nleft = 0; +} + +int +stl_feed(struct stl *stl, struct stl_result *res, + const void *chunk, size_t size) +{ + size_t cnt; + int rc; + + if (stl->chunk != chunk) { + if (stl->nleft > 0) + return STL_INVALID; + stl->chunk = chunk; + stl->pos = stl->chunk; + stl->nleft = size; + } + + if (stl->type == STL_TYPE_DETECT) { + rc = stl_read_expect(stl, "solid ", 6); + if (rc == STL_INCOMPLETE) + return STL_INCOMPLETE; + if (rc == STL_OK) { + printf("ascii file\n"); + stl->type = STL_TYPE_ASCII; + stl->ascii.state = STL_ASCII_SOLID_NAME; + } else { + printf("binary file\n"); + stl->type = STL_TYPE_BINARY; + stl->bin.state = STL_BIN_HEADER; + stl->bin.index = 0; + stl->bin.count = SIZE_MAX; + } + res->type = STL_RES_FILETYPE; + res->filetype = stl->type; + return STL_OK; + } + + switch (stl->type) { + case STL_TYPE_ASCII: + return stl_feed_ascii(stl, res, chunk, size); + case STL_TYPE_BINARY: + return stl_feed_binary(stl, res, chunk, size); + default: + return STL_INVALID_ARG; + } +}