/* ISC License */ #include #include #include #include #include #include #include #include #define PI 3.14159265358979323846 #define die(code, msg) do { fprintf(stderr, "pineapple: %s\n", msg); exit(code); } while (0) #define diesys(code, msg) do { fprintf(stderr, "pineapple: %s: %s\n", msg, strerror(errno)); exit(code); } while (0) #define USAGE "pineapple [-v] [-w width] [-h height] [-f | -a]" #define dieusage() die(100, "usage: " USAGE) #define ASCII_CHARS "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. " typedef struct { float x, y, z; } vec3; struct triangle { vec3 n; vec3 a, b, c; }; struct __attribute__((__packed__)) triangle_raw { vec3 n; vec3 a, b, c; uint16_t _attr_count; /* unused */ }; static inline vec3 vadd(vec3 a, vec3 b) { return (vec3){ a.x + b.x, a.y + b.y, a.z + b.z }; } static inline vec3 vsub(vec3 a, vec3 b) { return (vec3){ a.x - b.x, a.y - b.y, a.z - b.z }; } static inline vec3 smul(vec3 v, float s) { return (vec3){ v.x * s, v.y * s, v.z * s }; } static inline float dot(vec3 a, vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; } static inline vec3 cross(vec3 a, vec3 b) { return (vec3){ a.y*b.z - b.y*a.z, a.z*b.x - b.z*a.x, a.x*b.y - b.x*a.y }; } static inline float magnitude(vec3 v) { return sqrt(dot(v, v)); } static inline vec3 vnorm(vec3 v) { float mag = magnitude(v); return (vec3){ v.x / mag, v.y / mag, v.z / mag }; } float ray_triangle_intersect(vec3 P, vec3 d, struct triangle tri) { d = vnorm(d); vec3 n = vnorm(tri.n); vec3 A = tri.a, B = tri.b, C = tri.c; if (dot(n, d) == 0.0) return NAN; float t = (dot(n, A) - dot(n, P)) / dot(n, d); vec3 Q = vadd(P, smul(d, t)); if (dot(cross(vsub(B, A), vsub(Q, A)), n) >= 0.0 && dot(cross(vsub(C, B), vsub(Q, B)), n) >= 0.0 && dot(cross(vsub(A, C), vsub(Q, C)), n) >= 0.0) return t; return NAN; } int main(int argc, char *argv[]) { int verbosity = 0; uint32_t width = 80; uint32_t height = 40; enum { ASCII, FARBFELD } mode = ASCII; int c; while ((c = getopt(argc, argv, "afvh:w:")) > 0) { errno = 0; switch (c) { case 'a': mode = ASCII; break; case 'f': mode = FARBFELD; break; case 'v': verbosity = 1; break; case 'h': height = strtoul(optarg, NULL, 10); if (errno) dieusage(); break; case 'w': width = strtoul(optarg, NULL, 10); if (errno) dieusage(); break; default: dieusage(); } } argv += optind; argc -= optind; if (argc > 0) dieusage(); char header[80]; if (fread(header, 1, 80, stdin) < 80) diesys(111, "fatal: unable to read STL header from stdin"); uint32_t len; if (fread(&len, 4, 1, stdin) < 1) diesys(111, "fatal: unable to read STL length from stdin"); len = le32toh(len); struct triangle *triangles = calloc(len, sizeof(struct triangle)); if (!triangles) diesys(111, "fatal: unable to allocate enough memory for STL facets"); struct triangle_raw raw; for (size_t i = 0; i < len; ++i) { if (fread(&raw, sizeof(raw), 1, stdin) < 1) diesys(111, "fatal: unable to read STL facets"); triangles[i] = (struct triangle){ .n = raw.n, .a = raw.a, .b = raw.b, .c = raw.c }; } if (verbosity >= 1) fprintf(stderr, "finished reading STL: %d facets\n", len); vec3 camera_pos = { -70.0, 60.0, 60.0 }; float camera_phi = PI * 0.8; float camera_theta = PI * 0.5; float fov = 80.0 * PI / 180.0; vec3 light = { 0.0, 0.0, -1.0 }; if (mode == FARBFELD) { uint32_t wbe = htobe32(width); uint32_t hbe = htobe32(height); fwrite("farbfeld", 1, 8, stdout); fwrite(&wbe, 1, 4, stdout); fwrite(&hbe, 1, 4, stdout); } for (int h = height - 1; h >= 0; --h) { if (verbosity >= 1) fprintf(stderr, "row %d/%d\r", height - h - 1, height); for (uint32_t w = 0; w < width; ++w) { float theta = camera_theta - fov/2.0 + fov/height*h; float phi = camera_phi - fov/2.0 + fov/width*w; vec3 dir = { sinf(theta) * cosf(phi), sinf(theta) * sinf(phi), cosf(theta) }; dir = vnorm(dir); float shortest = INFINITY; vec3 shortest_norm = {0}; for (size_t i = 0; i < len; ++i) { float d = ray_triangle_intersect(camera_pos, dir, triangles[i]); if (d != NAN && fabs(d) < fabs(shortest)) { shortest = d; shortest_norm = triangles[i].n; } } float brightness = 0.0; if (shortest != INFINITY) { float light_angle = acosf(dot(shortest_norm, light) / magnitude(shortest_norm) / magnitude(light)); brightness = light_angle / PI; } if (mode == ASCII) { printf("%c", ASCII_CHARS[strlen(ASCII_CHARS) - 1 - (int)(brightness * strlen(ASCII_CHARS))]); } else if (mode == FARBFELD) { uint16_t x = htobe16((uint16_t)(brightness * 0xffff)); uint16_t pixel[4] = { x, x, x, htobe16(0xffff) }; fwrite(&pixel, 2, 4, stdout); } } if (mode == ASCII) printf("\n"); } return 0; }