├── LICENSE ├── README.md ├── ST_NICCC ├── ST_NICCC.c ├── graphics.c ├── graphics.h ├── io.c ├── io.h ├── makeit.sh ├── test_ST_NICCC.c └── test_gen_anim.c ├── ST_NICCC_MOVIES ├── ST_NICCC.bin └── bad_apple.bin ├── TRIANGULATE ├── Delaunay_psm.cpp ├── Delaunay_psm.h ├── makeit.sh └── triangulate.cpp ├── animate.lua └── vectorize.sh /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Bruno Levy 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vectorizer 2 | Generate animated vector graphics for old-school 90's demos, like ST_NICCC 3 | -------------------------------------------------------------------------------- /ST_NICCC/ST_NICCC.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Player for ST-NICCC 3 | */ 4 | 5 | #include "graphics.h" 6 | #include "io.h" 7 | #include 8 | #include 9 | 10 | ST_NICCC_IO io; 11 | ST_NICCC_FRAME frame; 12 | ST_NICCC_POLYGON polygon; 13 | 14 | int main(int argc, char** argv) { 15 | const char* scene_file = "scene1.bin"; 16 | if(argc >= 2) { 17 | scene_file = argv[1]; 18 | } 19 | if(!st_niccc_open(&io,scene_file,ST_NICCC_READ)) { 20 | fprintf(stderr,"could not open data file\n"); 21 | exit(-1); 22 | } 23 | gfx_init(); 24 | if(argc >= 3) { 25 | gfx_wireframe=!strcmp(argv[2],"-wireframe"); 26 | } 27 | for(;;) { 28 | st_niccc_rewind(&io); 29 | while(st_niccc_read_frame(&io,&frame)) { 30 | if(gfx_wireframe || frame.flags & CLEAR_BIT) { 31 | gfx_clear(); 32 | } 33 | while(st_niccc_read_polygon(&io,&frame,&polygon)) { 34 | uint8_t color = polygon.color; 35 | gfx_setcolor( 36 | gfx_wireframe ? 255 : frame.cmap_r[color], 37 | gfx_wireframe ? 255 : frame.cmap_g[color], 38 | gfx_wireframe ? 255 : frame.cmap_b[color] 39 | ); 40 | gfx_fillpoly( 41 | polygon.nb_vertices, 42 | polygon.XY 43 | ); 44 | } 45 | gfx_swapbuffers(); 46 | } 47 | gfx_wireframe = !gfx_wireframe; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ST_NICCC/graphics.c: -------------------------------------------------------------------------------- 1 | #include "graphics.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #ifdef __linux__ 7 | #include 8 | #endif 9 | 10 | #define MIN(x,y) ((x) < (y) ? (x) : (y)) 11 | #define MAX(x,y) ((x) > (y) ? (x) : (y)) 12 | 13 | int gfx_wireframe = 0; 14 | int gfx_color = 0; 15 | 16 | #define GFX_SIZE 256 17 | 18 | /************************************************************/ 19 | 20 | #ifdef GFX_BACKEND_GLFW 21 | 22 | #include 23 | GLFWwindow* gfx_window_; 24 | unsigned short gfx_framebuffer_[GFX_SIZE*GFX_SIZE]; 25 | 26 | static inline void gfx_setpixel_internal(int x, int y) { 27 | assert(x >= 0 && x < GFX_SIZE); 28 | assert(y >= 0 && y < GFX_SIZE); 29 | gfx_framebuffer_[(GFX_SIZE-1-y)*GFX_SIZE+x] = gfx_color; 30 | } 31 | 32 | static inline void gfx_hline_internal(int x1, int x2, int y) { 33 | assert(x1 >= 0 && x1 < GFX_SIZE); 34 | assert(x2 >= 0 && x2 < GFX_SIZE); 35 | assert(y >= 0 && y < GFX_SIZE); 36 | if(x2 < x1) { 37 | int tmp = x1; 38 | x1 = x2; 39 | x2 = tmp; 40 | } 41 | for(int x=x1; x>5; 78 | int g3 = g>>5; 79 | int b3 = b>>5; 80 | gfx_color = (b3 << 2) | (g3 << 8) | (r3 << 13); 81 | } 82 | 83 | #endif 84 | 85 | /************************************************************/ 86 | 87 | #ifdef GFX_BACKEND_ANSI 88 | 89 | static inline void gfx_setpixel_internal(int x, int y) { 90 | assert(x >= 0 && x < GFX_SIZE); 91 | assert(y >= 0 && y < GFX_SIZE); 92 | x = x >> 1; 93 | y = y >> 2; 94 | printf("\033[%d;%dH ",y,x); // move cursor to x,y and print space 95 | } 96 | 97 | static inline void gfx_hline_internal(int x1, int x2, int y) { 98 | assert(x1 >= 0 && x1 < GFX_SIZE); 99 | assert(x2 >= 0 && x2 < GFX_SIZE); 100 | assert(y >= 0 && y < GFX_SIZE); 101 | x1 = x1 >> 1; 102 | x2 = x2 >> 1; 103 | y = y >> 2; 104 | if(x2 < x1) { 105 | int tmp = x1; 106 | x1 = x2; 107 | x2 = tmp; 108 | } 109 | printf("\033[%d;%dH",y,x1); // move cursor to x1,y 110 | for(int x=x1; x>5; 136 | int g3 = g>>5; 137 | int b3 = b>>5; 138 | // Re-encode r,g,b as ANSI 8-bits color 139 | b3 = b3 * 6 / 8; 140 | g3 = g3 * 6 / 8; 141 | r3 = r3 * 6 / 8; 142 | int new_color = 16 + b3 + 6*(g3 + 6*r3); 143 | if(new_color != gfx_color) { 144 | printf("\033[48;5;%dm",new_color); 145 | } 146 | gfx_color = new_color; 147 | } 148 | 149 | #endif 150 | 151 | /************************************************************/ 152 | 153 | void gfx_line(int x1, int y1, int x2, int y2) { 154 | int x,y,dx,dy,sy,tmp; 155 | 156 | /* Swap both extremities to ensure x increases */ 157 | if(x2 < x1) { 158 | tmp = x2; 159 | x2 = x1; 160 | x1 = tmp; 161 | tmp = y2; 162 | y2 = y1; 163 | y1 = tmp; 164 | } 165 | 166 | /* Bresenham line drawing */ 167 | dy = y2 - y1; 168 | sy = 1; 169 | if(dy < 0) { 170 | sy = -1; 171 | dy = -dy; 172 | } 173 | 174 | dx = x2 - x1; 175 | 176 | x = x1; 177 | y = y1; 178 | 179 | if(dy > dx) { 180 | int ex = (dx << 1) - dy; 181 | for(int u=0; u= 0) { 185 | x++; 186 | ex -= dy << 1; 187 | gfx_setpixel_internal(x,y); 188 | } 189 | while(ex >= 0) { 190 | x++; 191 | ex -= dy << 1; 192 | putchar(' '); 193 | } 194 | ex += dx << 1; 195 | } 196 | } else { 197 | int ey = (dy << 1) - dx; 198 | for(int u=0; u= 0) { 202 | y += sy; 203 | ey -= dx << 1; 204 | gfx_setpixel_internal(x,y); 205 | } 206 | ey += dy << 1; 207 | } 208 | } 209 | } 210 | 211 | void gfx_fillpoly(int nb_pts, int* points) { 212 | int x_left[GFX_SIZE]; 213 | int x_right[GFX_SIZE]; 214 | 215 | /* Determine clockwise, miny, maxy */ 216 | int clockwise = 0; 217 | int miny = 1024; 218 | int maxy = -1024; 219 | 220 | for(int i1=0; i1 0) ^ (y2 > y1)) ? x_left : x_right; 249 | 250 | // ensure consistent rasterization of neighboring edges in 251 | // a triangulation, avoid small gaps 252 | if(y2 < y1) { 253 | int tmp = y1; 254 | y1 = y2; 255 | y2 = tmp; 256 | tmp = x1; 257 | x1 = x2; 258 | x2 = tmp; 259 | } 260 | 261 | int dx = x2 - x1; 262 | int sx = 1; 263 | int dy = y2 - y1; 264 | int sy = 1; 265 | int x = x1; 266 | int y = y1; 267 | int ex; 268 | 269 | if(dx < 0) { 270 | sx = -1; 271 | dx = -dx; 272 | } 273 | 274 | if(dy < 0) { 275 | sy = -1; 276 | dy = -dy; 277 | } 278 | 279 | if(y1 == y2) { 280 | x_left[y1] = MIN(x1,x2); 281 | x_right[y1] = MAX(x1,x2); 282 | continue; 283 | } 284 | 285 | ex = (dx << 1) - dy; 286 | 287 | for(int u=0; u <= dy; ++u) { 288 | x_buffer[y] = x; 289 | y += sy; 290 | while(ex >= 0) { 291 | x += sx; 292 | ex -= dy << 1; 293 | } 294 | ex += dx << 1; 295 | } 296 | } 297 | 298 | if(!gfx_wireframe) { 299 | for(int y = miny; y <= maxy; ++y) { 300 | gfx_hline_internal( 301 | x_left[y], x_right[y], y 302 | ); 303 | } 304 | } 305 | } 306 | 307 | -------------------------------------------------------------------------------- /ST_NICCC/graphics.h: -------------------------------------------------------------------------------- 1 | #ifndef GRAPHICS_H 2 | #define GRAPHICS_H 3 | 4 | #include 5 | 6 | /* Uncomment one of them or define on command line */ 7 | // #define GFX_BACKEND_ANSI 8 | // #define GFX_BACKEND_GLFW 9 | 10 | extern int gfx_wireframe; 11 | 12 | void gfx_init(); 13 | void gfx_swapbuffers(); 14 | void gfx_clear(); 15 | void gfx_setcolor(int r, int g, int b); 16 | void gfx_line(int x1, int y1, int x2, int y2); 17 | void gfx_fillpoly(int nb_pts, int* points); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /ST_NICCC/io.c: -------------------------------------------------------------------------------- 1 | #include "io.h" 2 | #include 3 | #include 4 | 5 | int st_niccc_open( 6 | ST_NICCC_IO* io, const char* filename, int mode 7 | ){ 8 | io->f = fopen( 9 | filename, 10 | (mode == ST_NICCC_WRITE) ? "wb" : "rb" 11 | ); 12 | if(io->f == NULL) { 13 | return 0; 14 | } 15 | io->mode = mode; 16 | st_niccc_rewind(io); 17 | return 1; 18 | } 19 | 20 | void st_niccc_close(ST_NICCC_IO* io){ 21 | fclose(io->f); 22 | io->f = NULL; 23 | } 24 | 25 | void st_niccc_rewind(ST_NICCC_IO* io){ 26 | io->addr = 0; 27 | io->word_addr = (uint32_t)(-1); 28 | io->eof = 0; 29 | fseek(io->f, 0, SEEK_SET); 30 | } 31 | 32 | uint8_t st_niccc_read_byte(ST_NICCC_IO* io){ 33 | uint8_t result; 34 | if(io->word_addr != io->addr >> 2) { 35 | io->word_addr = io->addr >> 2; 36 | fseek(io->f, io->word_addr*4, SEEK_SET); 37 | size_t nb_read = fread(&(io->u.word), 4, 1, io->f); 38 | if(nb_read != 1) { 39 | // TODO: 40 | // Seems that sometimes I got an error, this 41 | // appeared when I started to merge triangles 42 | // into polygons (for now, ignored, so that 43 | // player can loop) 44 | // 45 | // fprintf(stderr,"ST_NICCC: read error\n"); 46 | // abort(); 47 | // exit(-1); 48 | return END_OF_STREAM; 49 | } 50 | } 51 | result = io->u.bytes[(io->addr)&3]; 52 | ++(io->addr); 53 | return result; 54 | } 55 | 56 | uint16_t st_niccc_read_word(ST_NICCC_IO* io){ 57 | /* In the ST-NICCC file, 58 | * words are stored in big endian format. 59 | * (see DATA/scene_description.txt). 60 | */ 61 | uint16_t hi = (uint16_t)st_niccc_read_byte(io); 62 | uint16_t lo = (uint16_t)st_niccc_read_byte(io); 63 | return (hi << 8) | lo; 64 | } 65 | 66 | void st_niccc_write_byte(ST_NICCC_IO* io, uint8_t b) { 67 | fwrite(&b, 1, 1, io->f); 68 | ++(io->addr); 69 | } 70 | 71 | void st_niccc_write_word(ST_NICCC_IO* io, uint16_t w) { 72 | uint8_t hi = (uint8_t)(w >> 8); 73 | uint8_t lo = (uint8_t)(w & 255); 74 | st_niccc_write_byte(io,hi); 75 | st_niccc_write_byte(io,lo); 76 | } 77 | 78 | void st_niccc_next_block(ST_NICCC_IO* io) { 79 | if(io->mode == ST_NICCC_WRITE) { 80 | while(io->addr & 65535) { 81 | st_niccc_write_byte(io,0); 82 | } 83 | } else { 84 | io->addr &= ~65535; 85 | io->addr += 65536; 86 | } 87 | } 88 | 89 | /*********************************************************************/ 90 | 91 | int st_niccc_read_frame( 92 | ST_NICCC_IO* io, ST_NICCC_FRAME* frame 93 | ) { 94 | if(io->eof) { 95 | return 0; 96 | } 97 | frame->flags = st_niccc_read_byte(io); 98 | 99 | // Load palette data 100 | if(frame->flags & PALETTE_BIT) { 101 | frame->cmap_flags = st_niccc_read_word(io); 102 | for(int b=15; b>=0; --b) { 103 | if(frame->cmap_flags & (1 << b)) { 104 | int rgb = st_niccc_read_word(io); 105 | // Get the three 3-bits per component R,G,B 106 | int b3 = (rgb & 0x007); 107 | int g3 = (rgb & 0x070) >> 4; 108 | int r3 = (rgb & 0x700) >> 8; 109 | frame->cmap_r[15-b] = r3 << 5; 110 | frame->cmap_g[15-b] = g3 << 5; 111 | frame->cmap_b[15-b] = b3 << 5; 112 | } 113 | } 114 | } 115 | 116 | // Load vertices 117 | if(frame->flags & INDEXED_BIT) { 118 | frame->nb_vertices = st_niccc_read_byte(io); 119 | for(int v=0; vnb_vertices; ++v) { 120 | frame->X[v] = st_niccc_read_byte(io); 121 | frame->Y[v] = st_niccc_read_byte(io); 122 | } 123 | } 124 | return 1; 125 | } 126 | 127 | int st_niccc_read_polygon( 128 | ST_NICCC_IO* io, ST_NICCC_FRAME* frame, ST_NICCC_POLYGON* polygon 129 | ) { 130 | uint8_t poly_desc = st_niccc_read_byte(io); 131 | if(poly_desc == END_OF_FRAME) { 132 | return 0; 133 | } 134 | if(poly_desc == NEXT_BLOCK) { 135 | st_niccc_next_block(io); 136 | return 0; 137 | } 138 | if(poly_desc == END_OF_STREAM) { 139 | io->eof = 1; 140 | return 0; 141 | } 142 | polygon->nb_vertices = poly_desc & 15; 143 | polygon->color = poly_desc >> 4; 144 | for(int i=0; inb_vertices; ++i) { 145 | if(frame->flags & INDEXED_BIT) { 146 | uint8_t index = st_niccc_read_byte(io); 147 | polygon->XY[2*i] = frame->X[index]; 148 | polygon->XY[2*i+1] = frame->Y[index]; 149 | } else { 150 | polygon->XY[2*i] = st_niccc_read_byte(io); 151 | polygon->XY[2*i+1] = st_niccc_read_byte(io); 152 | } 153 | } 154 | return 1; 155 | } 156 | 157 | /*********************************************************************/ 158 | 159 | void st_niccc_frame_init( 160 | ST_NICCC_FRAME* frame 161 | ) { 162 | frame->flags = 0; 163 | frame->cmap_flags = 0; 164 | frame->nb_vertices = 0; 165 | } 166 | 167 | void st_niccc_frame_clear( 168 | ST_NICCC_FRAME* frame 169 | ) { 170 | frame->flags |= CLEAR_BIT; 171 | } 172 | 173 | void st_niccc_frame_set_color( 174 | ST_NICCC_FRAME* frame, 175 | uint8_t index, uint8_t r, uint8_t g, uint8_t b 176 | ) { 177 | frame->flags |= PALETTE_BIT; 178 | frame->cmap_r[index] = r; 179 | frame->cmap_g[index] = g; 180 | frame->cmap_b[index] = b; 181 | frame->cmap_flags |= (1 << (int)(15-index)); 182 | } 183 | 184 | void st_niccc_frame_set_vertex( 185 | ST_NICCC_FRAME* frame, 186 | uint8_t index, uint8_t x, uint8_t y 187 | ) { 188 | frame->flags |= INDEXED_BIT; 189 | frame->X[index] = x; 190 | frame->Y[index] = y; 191 | if(index+1 > frame->nb_vertices) { 192 | frame->nb_vertices = index+1; 193 | } 194 | } 195 | 196 | void st_niccc_write_frame_header( 197 | ST_NICCC_IO* io, ST_NICCC_FRAME* frame 198 | ) { 199 | st_niccc_write_byte(io,frame->flags); 200 | 201 | // write palette data 202 | if(frame->flags & PALETTE_BIT) { 203 | st_niccc_write_word(io, frame->cmap_flags); 204 | if(frame->flags & PALETTE_BIT) { 205 | for(int b=15; b>=0; --b) { 206 | if(frame->cmap_flags & (1 << b)) { 207 | uint16_t r3 = frame->cmap_r[15-b]>>5; 208 | uint16_t g3 = frame->cmap_g[15-b]>>5; 209 | uint16_t b3 = frame->cmap_b[15-b]>>5; 210 | uint16_t rgb = b3 | (g3 << 4) | (r3 << 8); 211 | st_niccc_write_word(io,rgb); 212 | } 213 | } 214 | } 215 | } 216 | 217 | // write vertices 218 | if(frame->flags & INDEXED_BIT) { 219 | st_niccc_write_byte(io,frame->nb_vertices); 220 | for(int v=0; vnb_vertices; ++v) { 221 | st_niccc_write_byte(io,frame->X[v]); 222 | st_niccc_write_byte(io,frame->Y[v]); 223 | } 224 | } 225 | } 226 | 227 | void st_niccc_write_polygon_indexed( 228 | ST_NICCC_IO* io, 229 | uint8_t color, uint8_t nb_vertices, uint8_t* vertices 230 | ) { 231 | assert(nb_vertices <= 15); 232 | st_niccc_write_byte(io, nb_vertices | (color << 4)); 233 | for(int i=0; i 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | /* 12 | * Masks for frame flags. 13 | */ 14 | #define CLEAR_BIT 1 15 | #define PALETTE_BIT 2 16 | #define INDEXED_BIT 4 17 | 18 | /* 19 | * Special polygon codes 20 | */ 21 | #define END_OF_FRAME 0xff 22 | #define NEXT_BLOCK 0xfe 23 | #define END_OF_STREAM 0xfd 24 | 25 | /* 26 | * Constants for st_niccc_open() 27 | */ 28 | #define ST_NICCC_READ 1 29 | #define ST_NICCC_WRITE 2 30 | 31 | /*******************************************************************/ 32 | 33 | /* 34 | * Low-level IO 35 | */ 36 | 37 | typedef struct { 38 | FILE* f; 39 | uint32_t addr; 40 | uint32_t word_addr; 41 | union { 42 | uint32_t word; 43 | uint8_t bytes[4]; 44 | } u; 45 | int mode; 46 | int eof; 47 | } ST_NICCC_IO ; 48 | 49 | int st_niccc_open(ST_NICCC_IO* io, const char* filename, int mode); 50 | void st_niccc_close(ST_NICCC_IO* io); 51 | void st_niccc_rewind(ST_NICCC_IO* io); 52 | uint8_t st_niccc_read_byte(ST_NICCC_IO* io); 53 | uint16_t st_niccc_read_word(ST_NICCC_IO* io); 54 | void st_niccc_write_byte(ST_NICCC_IO* io, uint8_t b); 55 | void st_niccc_write_word(ST_NICCC_IO* io, uint16_t w); 56 | void st_niccc_next_block(ST_NICCC_IO* io); 57 | 58 | /*******************************************************************/ 59 | 60 | /* 61 | * High-level IO 62 | */ 63 | 64 | typedef struct { 65 | uint8_t flags; 66 | uint16_t cmap_flags; 67 | uint8_t cmap_r[16]; 68 | uint8_t cmap_g[16]; 69 | uint8_t cmap_b[16]; 70 | uint8_t nb_vertices; 71 | uint8_t X[256]; 72 | uint8_t Y[256]; 73 | } ST_NICCC_FRAME; 74 | 75 | typedef struct { 76 | uint8_t nb_vertices; 77 | uint8_t color; 78 | int XY[32]; // interleaved x,y 79 | } ST_NICCC_POLYGON; 80 | 81 | int st_niccc_read_frame( 82 | ST_NICCC_IO* io, ST_NICCC_FRAME* frame 83 | ); 84 | 85 | int st_niccc_read_polygon( 86 | ST_NICCC_IO* io, ST_NICCC_FRAME* frame, ST_NICCC_POLYGON* polygon 87 | ); 88 | 89 | /***********/ 90 | 91 | void st_niccc_frame_init(ST_NICCC_FRAME* frame); 92 | void st_niccc_frame_clear(ST_NICCC_FRAME* frame); 93 | 94 | void st_niccc_frame_set_color( 95 | ST_NICCC_FRAME* frame, 96 | uint8_t index, uint8_t r, uint8_t g, uint8_t b 97 | ); 98 | 99 | void st_niccc_frame_set_vertex( 100 | ST_NICCC_FRAME* frame, 101 | uint8_t index, uint8_t x, uint8_t y 102 | ); 103 | 104 | void st_niccc_write_frame_header( 105 | ST_NICCC_IO* io, ST_NICCC_FRAME* frame 106 | ); 107 | 108 | void st_niccc_write_end_of_frame(ST_NICCC_IO* io); 109 | 110 | void st_niccc_write_end_of_stream(ST_NICCC_IO* io); 111 | 112 | void st_niccc_write_polygon_indexed( 113 | ST_NICCC_IO* io, 114 | uint8_t color, uint8_t nb_vertices, uint8_t* vertices 115 | ); 116 | 117 | void st_niccc_write_polygon( 118 | ST_NICCC_IO* io, 119 | uint8_t color, uint8_t nb_vertices, uint8_t* x, uint8_t* y 120 | ); 121 | 122 | void st_niccc_write_triangle_indexed( 123 | ST_NICCC_IO* io, 124 | uint8_t color, uint8_t v1, uint8_t v2, uint8_t v3 125 | ); 126 | 127 | void st_niccc_write_triangle( 128 | ST_NICCC_IO* io, 129 | uint8_t color, 130 | uint8_t x1, uint8_t y1, 131 | uint8_t x2, uint8_t y2, 132 | uint8_t x3, uint8_t y3 133 | ); 134 | 135 | 136 | /*******************************************************************/ 137 | 138 | #ifdef __cplusplus 139 | } 140 | #endif 141 | 142 | #endif 143 | -------------------------------------------------------------------------------- /ST_NICCC/makeit.sh: -------------------------------------------------------------------------------- 1 | 2 | # The ST-NICCC Demo 3 | # http://leonard.oxg.free.fr/stniccc/stniccc.html 4 | # http://www.pouet.net/topic.php?which=11559&page=1 5 | # 6 | # Data file and information: 7 | # http://arsantica-online.com/st-niccc-competition/ 8 | 9 | 10 | # Download ST_NICCC data file 11 | if [ ! -f scene1.bin ]; then 12 | wget http://www.evilshirt.com/_ataristuff/scene_data_file.zip 13 | unzip scene_data_file.zip 14 | rm scene_data_file.zip 15 | else 16 | echo " Using cached scene1.bin" 17 | fi 18 | 19 | CFLAGS="-Wall -Wpedantic -g" 20 | 21 | gcc $CFLAGS -DGFX_BACKEND_GLFW ST_NICCC.c graphics.c io.c -lglfw -lGL -o ST_NICCC_glfw 22 | gcc $CFLAGS -DGFX_BACKEND_ANSI ST_NICCC.c graphics.c io.c -o ST_NICCC_console 23 | gcc $CFLAGS test_gen_anim.c io.c -lm -o test_gen_anim 24 | gcc $CFLAGS test_ST_NICCC.c -o test_ST_NICCC 25 | -------------------------------------------------------------------------------- /ST_NICCC/test_ST_NICCC.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Standalone test program that reads scene1.bin 3 | * and that displays what it finds in there, 4 | * see scene_description.txt 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | typedef unsigned char uint8_t; 11 | typedef unsigned short uint16_t; 12 | typedef unsigned int uint32_t; 13 | 14 | #define GL_RGB(R,G,B) ((((((R) & 0xF8) << 2) | ((G) & 0xF8)) << 6) | ((B) >> 3)) 15 | 16 | FILE* stream = NULL; 17 | 18 | /* 19 | * Reading the ST-NICCC megademo data. 20 | * (compared with the version in C_EXAMPLES, this version 21 | * reads a file instead of reading from the SPI Flash memory). 22 | */ 23 | 24 | int cur_spi = 0; 25 | 26 | uint8_t next_spi_byte() { 27 | uint8_t result; 28 | fread(&result, 1, 1, stream); 29 | ++cur_spi; 30 | return result; 31 | } 32 | 33 | 34 | uint16_t next_spi_word() { 35 | /* words are stored in big endian format */ 36 | uint16_t hi = (uint16_t)next_spi_byte(); 37 | uint16_t low = (uint16_t)next_spi_byte(); 38 | return low | (hi << 8); 39 | } 40 | 41 | void printb(int x) { 42 | for(int s=15; s>=0; --s) { 43 | putchar(x & (1 << s) ? '1' : '0'); 44 | } 45 | } 46 | 47 | 48 | uint16_t cmap[16]; 49 | int X[255]; 50 | int Y[255]; 51 | int poly[30]; 52 | 53 | #define CLEAR_BIT 1 54 | #define PALETTE_BIT 2 55 | #define INDEXED_BIT 4 56 | 57 | 58 | /* returns 0 at last frame */ 59 | int read_frame() { 60 | static int frame = 0; 61 | 62 | printf("================Frame %d\n", frame); 63 | ++frame; 64 | 65 | uint8_t frame_flags = next_spi_byte(); 66 | 67 | printf( 68 | "Frame flags: %d:%c%c%c\n", 69 | frame_flags, 70 | (frame_flags & CLEAR_BIT) ? 'C' : '_', 71 | (frame_flags & PALETTE_BIT) ? 'P' : '_', 72 | (frame_flags & INDEXED_BIT) ? 'I' : '_' 73 | ); 74 | 75 | if(frame_flags & CLEAR_BIT) { 76 | /* 77 | * clear the screen now: 78 | * GL_clear(); 79 | */ 80 | } 81 | 82 | if(frame_flags & PALETTE_BIT) { 83 | uint16_t colors = next_spi_word(); 84 | printf("mask="); 85 | printb(colors); 86 | printf("\n"); 87 | for(int b=0; b<16; ++b) { 88 | if(colors & (1 << b)) { 89 | uint16_t rgb = next_spi_word(); 90 | cmap[15-b] = rgb; 91 | printf("CMAP %d:",15-b); 92 | printf(" "); 93 | printb(rgb); 94 | printf(" "); 95 | 96 | int b3 = (rgb & 0x007); 97 | int g3 = (rgb & 0x070) >> 4; 98 | int r3 = (rgb & 0x700) >> 8; 99 | printf(" ->RGB: %d %d %d\n", r3<<5,g3<<5,b3<<5); 100 | } 101 | } 102 | } 103 | 104 | if(frame_flags & CLEAR_BIT) { 105 | } 106 | 107 | for(int i=0; i<255; ++i) { 108 | X[i] = 65536; 109 | Y[i] = 65536; 110 | } 111 | 112 | 113 | if(frame_flags & INDEXED_BIT) { 114 | uint8_t nb_vertices = next_spi_byte(); 115 | printf("nb vrtx:%d\n", nb_vertices); 116 | for(int v=0; v> 1; 118 | Y[v] = next_spi_byte() >> 1; 119 | printf(" vrtx %d: %d %d\n", v, X[v], Y[v]); 120 | } 121 | } 122 | 123 | for(;;) { 124 | uint8_t poly_desc = next_spi_byte(); 125 | if(poly_desc == 0xff) { 126 | break; // end of frame 127 | } 128 | if(poly_desc == 0xfe) { 129 | printf("=====>Skipping to 64k boundary\n"); 130 | while(cur_spi & 65535) { 131 | next_spi_byte(); 132 | } 133 | return 1; 134 | } 135 | if(poly_desc == 0xfd) { 136 | printf("End of stream \n"); 137 | return 0; 138 | } 139 | uint8_t nvrtx = poly_desc & 15; 140 | uint8_t poly_col = poly_desc >> 4; 141 | printf("POLY col:%d nv:%d ", poly_col, nvrtx); 142 | for(int i=0; i> 1; 150 | poly[2*i+1] = next_spi_byte() >> 1; 151 | printf("%d,%d ", poly[2*i], poly[2*i+1]); 152 | } 153 | } 154 | printf("\n"); 155 | 156 | for(int i=0; i<2*nvrtx; ++i) { 157 | if(poly[i] < 0 ||poly[i] > 127) { 158 | printf("ERROR =====>\n"); 159 | } 160 | } 161 | 162 | /* 163 | * Draw the polygon now: 164 | * GL_fill_poly(nvrtx,poly,cmap[poly_col]); 165 | */ 166 | } 167 | return 1; 168 | } 169 | 170 | 171 | int main(int argc, char** argv) { 172 | stream = fopen(argc == 2 ? argv[1] : "scene1.bin","rb"); 173 | while(read_frame()) { 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /ST_NICCC/test_gen_anim.c: -------------------------------------------------------------------------------- 1 | #include "io.h" 2 | #include 3 | 4 | ST_NICCC_IO io; 5 | ST_NICCC_FRAME frame; 6 | 7 | int main() { 8 | st_niccc_open(&io, "test.bin", ST_NICCC_WRITE); 9 | st_niccc_frame_init(&frame); 10 | st_niccc_frame_clear(&frame); 11 | st_niccc_frame_set_color(&frame, 0, 0, 0, 0); 12 | st_niccc_frame_set_color(&frame, 1, 0, 255, 0); 13 | st_niccc_frame_set_color(&frame, 2, 0, 0, 255); 14 | st_niccc_write_frame_header(&io, &frame); 15 | st_niccc_write_end_of_frame(&io); 16 | 17 | int N = 20; 18 | int Nframe = 100; 19 | 20 | for(int f=0; f 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | namespace GEO { 14 | 15 | 16 | class Triangulation : public ExactCDT2d { 17 | public: 18 | index_t insert(double x, double y) { 19 | return ExactCDT2d::insert(exact::vec2h(x,y,1.0)); 20 | } 21 | 22 | void classify() { 23 | classify_triangles("union",true); // classify only 24 | T_region_.assign(nT(),-1); 25 | for(index_t t=0; t& P 57 | ) { 58 | P.resize(0); 59 | P.push_back(Tv(t,0)); 60 | P.push_back(Tv(t,1)); 61 | P.push_back(Tv(t,2)); 62 | DList S(*this, DLIST_S_ID); 63 | Tset_flag(t, T_MARKED_FLAG); 64 | S.push_back(t); 65 | 66 | while(!S.empty() && P.size()<15) { 67 | index_t t1 = S.front(); 68 | S.pop_front(); 69 | for(index_t le1=0; (le1<3 && P.size()<15); ++le1) { 70 | index_t t2 = Tadj(t1,le1); 71 | 72 | if(t2 == index_t(-1)) { 73 | continue; 74 | } 75 | 76 | if(Tflag_is_set(t2,T_MARKED_FLAG)) { 77 | continue; 78 | } 79 | 80 | if(Tregion(t1) != Tregion(t2)) { 81 | continue; 82 | } 83 | 84 | index_t le2 = Tadj_find(t2,t1); 85 | index_t v1 = Tv(t1,(le1+1)%3); 86 | index_t v2 = Tv(t1,(le1+2)%3); 87 | index_t v3 = Tv(t2,le2); 88 | 89 | if(false) { 90 | std::cerr << "v1=" << v1 91 | << " v2=" << v2 92 | << " v3=" << v3 << std::endl; 93 | std::cerr << "P=["; 94 | for(index_t i=0; i= 0 && 115 | orient2d(v3,P[i2],P[i2_next]) >= 0 116 | ) { 117 | Tset_flag(t2,T_MARKED_FLAG); 118 | S.push_back(t2); 119 | P.insert(P.begin()+i2,v3); 120 | } 121 | } 122 | } 123 | } 124 | 125 | private: 126 | vector T_region_; 127 | }; 128 | 129 | } 130 | 131 | 132 | /** 133 | * \brief Generates a string of length \p len from integer \p i 134 | * padded with zeroes. 135 | */ 136 | std::string to_string(int i, int len) { 137 | std::ostringstream out; 138 | out << i; 139 | std::string result = out.str(); 140 | while(int(result.length()) < len) { 141 | result = "0" + result; 142 | } 143 | return result; 144 | } 145 | 146 | 147 | // Parse .fig file and append content to ST_NICCC file 148 | // Reference: https://mcj.sourceforge.net/fig-format.html 149 | bool fig_2_ST_NICCC(const std::string& filename, ST_NICCC_IO* io) { 150 | 151 | GEO::Triangulation triangulation; 152 | triangulation.set_delaunay(true); 153 | 154 | int xmin = 0; 155 | int xmax = 0; 156 | int ymin = 0; 157 | int ymax = 0; 158 | int L = 0; 159 | 160 | static int win_xmin = 1000; 161 | static int win_xmax = -1; 162 | static int win_ymin = 1000; 163 | static int win_ymax = -1; 164 | 165 | triangulation.create_enclosing_rectangle(0,0,255,255); 166 | 167 | std::ifstream in(filename); 168 | if(!in) { 169 | return false; 170 | } 171 | std::cerr << "Loading " << filename << std::endl; 172 | int nb_paths = 0; 173 | 174 | // Read xfig file and send contents to constrained Delaunay 175 | // triangulation 176 | { 177 | std::string line; 178 | while(std::getline(in,line)) { 179 | int object_code; // always 3 180 | int sub_type; // 0: open approximated spline 181 | // 1: closed approximated spline 182 | // 2: open interpolated spline 183 | // 3: closed interpolated spline 184 | // 4: open x-spline 185 | // 5: closed x-spline 186 | int line_style; // enumeration type, solid, dash, dotted, etc. 187 | int thickness; // 1/80 inch 188 | int pen_color; // enumeration type, pen color 189 | int fill_color; // enumeration type, fill color 190 | int depth; // enumeration type 191 | int pen_style; // pen style, not used 192 | int area_fill; // enumeration type, -1 = no fill 193 | float style_val; // 1/80 inch,specification for dash/dotted lines 194 | int cap_style; // enumeration type, only used for open splines 195 | int forward_arrow; // 0: off, 1: on 196 | int backward_arrow; // 0: off, 1: on 197 | int npoints ; // number of control points in spline 198 | 199 | int xmin_tmp, ymin_tmp, xmax_tmp, ymax_tmp; 200 | 201 | if( // object 3: polyline 202 | sscanf( 203 | line.c_str(), 204 | "%d %d %d %d %d %d %d %d %d %f %d %d %d %d", 205 | &object_code, &sub_type, &line_style, &thickness, 206 | &pen_color, &fill_color, &depth, &pen_style, &area_fill, 207 | &style_val, &cap_style, &forward_arrow, &backward_arrow, 208 | &npoints 209 | ) == 14 && 210 | object_code==3 211 | ) { 212 | std::vector vertices; 213 | bool update_win = (win_xmax == -1 && win_ymax == -1); 214 | for(int i=0; i P; 282 | uint8_t P8[15]; 283 | for(GEO::index_t t=0; t P; 300 | for(GEO::index_t t=0; t filenames; 337 | 338 | if( 339 | !GEO::CmdLine::parse( 340 | argc, argv, filenames, "inputdir " 341 | ) 342 | ) { 343 | return 1; 344 | } 345 | 346 | 347 | if(filenames.size() < 1) { 348 | std::cerr << argv[0] << ": missing input directory" 349 | << std::endl; 350 | return 2; 351 | } 352 | 353 | std::string basename = filenames[0] + "/frame"; 354 | std::string output_filename = "stream.bin"; 355 | 356 | if(filenames.size() >= 2) { 357 | output_filename = filenames[1]; 358 | } 359 | 360 | 361 | int first_frame = GEO::CmdLine::get_arg_int("first_frame"); 362 | int last_frame = GEO::CmdLine::get_arg_int("last_frame"); 363 | 364 | int id=first_frame; 365 | 366 | 367 | ST_NICCC_IO io; 368 | 369 | st_niccc_open(&io,output_filename.c_str(),ST_NICCC_WRITE); 370 | 371 | ST_NICCC_FRAME frame; 372 | st_niccc_frame_init(&frame); 373 | st_niccc_frame_set_color(&frame, 0, 0, 0, 0); 374 | st_niccc_frame_set_color(&frame, 1, 255, 255, 255); 375 | st_niccc_write_frame_header(&io,&frame); 376 | st_niccc_write_end_of_frame(&io); 377 | 378 | while(fig_2_ST_NICCC(basename+to_string(id,4)+".fig",&io)) { 379 | ++id; 380 | if(last_frame != 0 && id >= last_frame) { 381 | std::cerr << "Reached last frame=" << last_frame << std::endl; 382 | break; 383 | } 384 | } 385 | 386 | st_niccc_frame_init(&frame); 387 | st_niccc_write_frame_header(&io,&frame); 388 | st_niccc_write_end_of_stream(&io); 389 | 390 | } 391 | -------------------------------------------------------------------------------- /animate.lua: -------------------------------------------------------------------------------- 1 | -- Lua (keep this comment, it is an indication for editor's 'run' command) 2 | -- script for Graphite (https://github.com/BrunoLevy/GraphiteThree) 3 | -- displays an "animation" where each object of the scene graph is a frame 4 | -- usage: graphite PATHS/*.obj animate.lua 5 | 6 | camera.draw_selected_only=true 7 | scene_graph.current().shader.edges_style='true; 1 0 1 1; 1' 8 | scene_graph.current().shader.vertices_style='true; 1 0 1 1; 1' 9 | scene_graph.scene_graph_shader_manager.apply_to_scene_graph() 10 | 11 | function sleep(t) 12 | local ntime = os.clock()+t/10 13 | repeat until os.clock() > ntime 14 | end 15 | 16 | for i = 0,scene_graph.nb_children-1,1 do 17 | scene_graph.current_object = scene_graph.ith_child(i).name 18 | main.draw() 19 | sleep(0.3) 20 | end 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /vectorize.sh: -------------------------------------------------------------------------------- 1 | 2 | # Vectorizer: converts a black and white video into triangulations (WIP) 3 | # Bruno Levy, April 2023 4 | # License: BSD 3 clauses 5 | 6 | VIDEOSOURCE=https://ia802905.us.archive.org/19/items/TouhouBadApple/Touhou%20-%20Bad%20Apple.mp4 7 | INPUT_VIDEOFILE=VIDEO/video.mp4 8 | #FPS=5 9 | #RESOLUTION=256 10 | 11 | FPS=12 12 | RESOLUTION=128 13 | TOLERANCE=20.0 14 | NB_COLORS=2 15 | 16 | #################################################################### 17 | 18 | # fig2obj: converts an xfig file with paths into an alias|wavefront file 19 | # usage: fig2obj input.fig output.obj 20 | fig2obj() { 21 | awk < $1 ' 22 | BEGIN { 23 | v_offset=1; 24 | nb_in_polyline=0; 25 | nb_remaining=0; 26 | } { 27 | if($1 == 3 && NF== 14) { 28 | nb_in_polyline = $14 29 | nb_remaining = $14 30 | } else if(nb_remaining != 0) { 31 | printf("v %d %d\n", $1, -$2) 32 | --nb_remaining 33 | if(nb_remaining == 0) { 34 | for(i=0; i $2 43 | 44 | #################################################################### 45 | 46 | fig2movetolineto() { 47 | awk < $1 ' 48 | BEGIN { 49 | v_offset=1; 50 | nb_in_polyline=0; 51 | nb_remaining=0; 52 | } { 53 | 54 | if ($1 == 6 && NF == 5) 55 | { 56 | minx = $2 57 | miny = $3 58 | maxx = $4 59 | maxy = $5 60 | 61 | dx = maxx - minx 62 | dy = maxy - miny 63 | } 64 | else 65 | 66 | if($1 == 3 && NF == 14) { 67 | nb_in_polyline = $14 68 | nb_remaining = $14 69 | } else if(nb_remaining != 0) { 70 | if (nb_in_polyline == nb_remaining) 71 | { 72 | startx = $1 73 | starty = $2 74 | printf("moveto %d %d\n", $1-minx, dy - ($2-miny)) 75 | } 76 | else printf("lineto %d %d\n", $1-minx, dy - ($2-miny)) 77 | 78 | --nb_remaining 79 | if(nb_remaining == 0) printf("lineto %d %d\n\n", startx-minx, dy - (starty-miny)) 80 | } 81 | } 82 | ' 83 | } > $2 84 | 85 | #################################################################### 86 | 87 | vectorize_BW() { 88 | BASENAME=`basename $1 .pgm` 89 | FIGFRAME=PATHS/$BASENAME.fig 90 | OBJFRAME=PATHS/$BASENAME.obj 91 | VECTORFRAME=PATHS/$BASENAME.vec 92 | potrace -b xfig -a 0 -O $TOLERANCE -r $RESOLUTION"x"$RESOLUTION $1 -o $FIGFRAME 93 | fig2obj $FIGFRAME $OBJFRAME 94 | fig2movetolineto $FIGFRAME $VECTORFRAME 95 | } 96 | 97 | #################################################################### 98 | 99 | # range from to 100 | # prints the range of numbers from from+1 ... to 101 | range() { 102 | echo | awk "BEGIN{ from=$1; to=$2}"' { 103 | for(i=from; i<=to; i++) { 104 | printf("%d ",i) 105 | } 106 | }' 107 | } 108 | 109 | # select_color colormap nb_colors entry 110 | # prints the imagemagic command option to select a given color in an image 111 | select_color() { 112 | echo "$COLORMAP" | awk "BEGIN{ nb=$1; entry=$2}"' { 113 | for(i=0; i