summaryrefslogtreecommitdiff
path: root/pineapple.c
diff options
context:
space:
mode:
Diffstat (limited to 'pineapple.c')
-rw-r--r--pineapple.c176
1 files changed, 176 insertions, 0 deletions
diff --git a/pineapple.c b/pineapple.c
new file mode 100644
index 0000000..8911622
--- /dev/null
+++ b/pineapple.c
@@ -0,0 +1,176 @@
+#include <endian.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define PI 3.14159265358979323846
+
+#define die(code, msg) do { fprintf(stderr, "pineapple: %s\n", msg); exit(code); } while (0);
+
+#define USAGE "pineapple [-v] [-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 __attribute__((__packed__)) triangle {
+ vec3 normal;
+ vec3 vertices[3];
+ 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)
+{
+ vec3 n = tri.normal, A = tri.vertices[0], B = tri.vertices[1], C = tri.vertices[2];
+ d = vnorm(d);
+ n = vnorm(n);
+ 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 c;
+ int verbosity = 0;
+ enum { ASCII, FARBFELD } mode = ASCII;
+ while ((c = getopt(argc, argv, "vfa")) > 0) {
+ switch (c) {
+ case 'v': verbosity++; break;
+ case 'f': mode = FARBFELD; break;
+ case 'a': mode = ASCII; break;
+ default: dieusage();
+ }
+ }
+ argv += optind; argc -= optind;
+ if (argc > 0) dieusage();
+
+ const char *stl;
+ {
+ int stl_fd = open("pineapple.stl", O_RDONLY);
+ if (stl_fd < 0) die(111, "fatal: unable to open pineapple.stl");
+ struct stat stl_stat;
+ if (fstat(stl_fd, &stl_stat) < 0) die(111, "fatal: unable to stat pineapple.stl");
+ stl = mmap(NULL, stl_stat.st_size, PROT_READ, MAP_PRIVATE, stl_fd, 0);
+ close(stl_fd);
+ if (!stl) die(111, "fatal: unable to mmap pineapple.stl");
+ }
+ stl += 80;
+ uint32_t len = le32toh(*(uint32_t *)stl);
+ stl += sizeof(uint32_t);
+ const struct triangle *triangles = (const struct triangle *)stl;
+
+ 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 };
+
+ uint32_t width = 106;
+ uint32_t height = 53;
+
+ 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].normal;
+ }
+ }
+
+ float brightness = 0.0;
+ if (shortest != INFINITY) {
+ float light_angle = acosf(dot(shortest_norm, light) / magnitude(shortest_norm) / magnitude(light));
+ brightness = 1 - 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 a = htobe16(0xffff);
+ fwrite(&x, 1, 2, stdout);
+ fwrite(&x, 1, 2, stdout);
+ fwrite(&x, 1, 2, stdout);
+ fwrite(&a, 1, 2, stdout);
+ }
+ }
+ if (mode == ASCII) printf("\n");
+ }
+
+ return 0;
+} \ No newline at end of file