diff options
| author | Sam Nystrom <sam@samnystrom.dev> | 2024-02-13 22:21:05 -0500 |
|---|---|---|
| committer | Sam Nystrom <sam@samnystrom.dev> | 2024-02-13 22:21:05 -0500 |
| commit | 644a7e3014485676bc2b95c921a14716186ef4cf (patch) | |
| tree | 89954eb4e018a3ba0dd0871971c2f6ff2086146b | |
init
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Makefile | 16 | ||||
| -rw-r--r-- | pineapple.c | 176 | ||||
| -rw-r--r-- | pineapple.stl | bin | 0 -> 3317184 bytes |
4 files changed, 195 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0edfe1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.ff +*.png +pineapple diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..78828dc --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +.POSIX: +.SUFFIXES: + +CFLAGS = -O2 -Wall -Wextra -pedantic +LDFLAGS = -static +LDLIBS = -lm + +all: pineapple + +pineapple: pineapple.c + $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(LDLIBS) + +clean: + rm -f pineapple + +.PHONY: all clean 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 diff --git a/pineapple.stl b/pineapple.stl Binary files differnew file mode 100644 index 0000000..d81d5b0 --- /dev/null +++ b/pineapple.stl |
