├── .gitignore ├── README.md ├── TODO.md ├── assets ├── curves │ └── sigmoid.txt ├── fonts │ ├── LICENSE.iosevka.md │ ├── Vollkorn-Regular.ttf │ ├── iosevka-bold.ttf │ └── iosevka-regular.ttf ├── images │ ├── 100.png │ ├── 100.svg │ ├── eggplant.png │ ├── eggplant.svg │ ├── fire.png │ ├── fire.svg │ ├── joy.png │ ├── joy.svg │ ├── ok.png │ ├── ok.svg │ └── tsodinZezin.png └── sounds │ └── plant-bomb.wav ├── nob.c ├── nob.h ├── panim ├── env.h ├── ffmpeg.h ├── ffmpeg_linux.c ├── panim.c └── plug.h ├── plugs ├── bezier │ ├── arena.h │ ├── der │ │ └── bezier.tex │ ├── interpolators.h │ └── plug.c ├── c3 │ ├── future.c3 │ ├── plug.c3 │ └── raylib.c3i ├── cpp │ ├── interpolators.h │ └── plug.cpp ├── squares │ ├── arena.h │ ├── interpolators.h │ ├── plug.c │ ├── tasks.c │ └── tasks.h ├── template │ └── plug.c └── tm │ ├── arena.h │ ├── interpolators.h │ ├── plug.c │ ├── tasks.c │ └── tasks.h └── raylib └── raylib-5.0_linux_amd64 ├── CHANGELOG ├── LICENSE ├── README.md ├── include ├── raylib.h ├── raymath.h └── rlgl.h └── lib ├── libraylib.a ├── libraylib.so ├── libraylib.so.5.0.0 └── libraylib.so.500 /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | nob 3 | nob.old 4 | *.mp4 5 | *.wav 6 | *~ 7 | 8 | # LaTeX 9 | *.aux 10 | *.log 11 | *.pdf 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Panim 2 | 3 | Programming Animation Engine. Heavily inspired by (but not based on) [Manim](https://github.com/3b1b/manim) 4 | 5 | ## Quick Start 6 | 7 | ```console 8 | $ cc -o nob nob.c 9 | $ ./nob 10 | $ ./build/panim ./build/libplug.so 11 | ``` 12 | 13 | ## Architecture 14 | 15 | The whole engine consists of two parts: 16 | 1. panim executable - the engine itself 17 | 2. the animation dynamic library (a.k.a. `libplug.so`) that you as the user of Panim develops 18 | 19 | Panim the Executable enables you to control your animation: pause it, replay it, and, most importantly, render it into the final video with FFmpeg. It also allows you to dynamically reload the animation library without restarting the whole Engine which improves the feedback loop during the development of the animation. 20 | 21 | ### Assets vs State 22 | 23 | While developing your animation dynamic library it's good to separate your things into 2 lifetimes: 24 | 25 | 1. Assets - things that never change throughout the animation, but reloaded when the `libplug.so` is reloaded 26 | 2. State - things that survive the `libplug.so` reload, but are reset on `plug_reset()`. 27 | 28 | You can safely assume that string literals reside in the Assets lifetime. So if a string literal cross a "lifetime boundary" from Asset to State it has to be copied to an appropriet region of memory. Something like an arena works well here. 29 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [x] Sounds in rendered videos 2 | - [x] Scale delta_time in preview 3 | - [ ] Plugin state snapshots 4 | -------------------------------------------------------------------------------- /assets/curves/sigmoid.txt: -------------------------------------------------------------------------------- 1 | 0.000000 0.000000 2 | 0.500000 0.000000 3 | 0.500000 1.000000 4 | 1.000000 1.000000 5 | -------------------------------------------------------------------------------- /assets/fonts/LICENSE.iosevka.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2024, Renzhi Li (aka. Belleve Invis, belleve@typeof.net) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | -------------------------- 9 | 10 | 11 | SIL Open Font License v1.1 12 | ==================================================== 13 | 14 | 15 | Preamble 16 | ---------- 17 | 18 | The goals of the Open Font License (OFL) are to stimulate worldwide 19 | development of collaborative font projects, to support the font creation 20 | efforts of academic and linguistic communities, and to provide a free and 21 | open framework in which fonts may be shared and improved in partnership 22 | with others. 23 | 24 | The OFL allows the licensed fonts to be used, studied, modified and 25 | redistributed freely as long as they are not sold by themselves. The 26 | fonts, including any derivative works, can be bundled, embedded, 27 | redistributed and/or sold with any software provided that any reserved 28 | names are not used by derivative works. The fonts and derivatives, 29 | however, cannot be released under any other type of license. The 30 | requirement for fonts to remain under this license does not apply 31 | to any document created using the fonts or their derivatives. 32 | 33 | 34 | Definitions 35 | ------------- 36 | 37 | `"Font Software"` refers to the set of files released by the Copyright 38 | Holder(s) under this license and clearly marked as such. This may 39 | include source files, build scripts and documentation. 40 | 41 | `"Reserved Font Name"` refers to any names specified as such after the 42 | copyright statement(s). 43 | 44 | `"Original Version"` refers to the collection of Font Software components as 45 | distributed by the Copyright Holder(s). 46 | 47 | `"Modified Version"` refers to any derivative made by adding to, deleting, 48 | or substituting -- in part or in whole -- any of the components of the 49 | Original Version, by changing formats or by porting the Font Software to a 50 | new environment. 51 | 52 | `"Author"` refers to any designer, engineer, programmer, technical 53 | writer or other person who contributed to the Font Software. 54 | 55 | 56 | Permission & Conditions 57 | ------------------------ 58 | 59 | Permission is hereby granted, free of charge, to any person obtaining 60 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 61 | redistribute, and sell modified and unmodified copies of the Font 62 | Software, subject to the following conditions: 63 | 64 | 1. Neither the Font Software nor any of its individual components, 65 | in Original or Modified Versions, may be sold by itself. 66 | 67 | 2. Original or Modified Versions of the Font Software may be bundled, 68 | redistributed and/or sold with any software, provided that each copy 69 | contains the above copyright notice and this license. These can be 70 | included either as stand-alone text files, human-readable headers or 71 | in the appropriate machine-readable metadata fields within text or 72 | binary files as long as those fields can be easily viewed by the user. 73 | 74 | 3. No Modified Version of the Font Software may use the Reserved Font 75 | Name(s) unless explicit written permission is granted by the corresponding 76 | Copyright Holder. This restriction only applies to the primary font name as 77 | presented to the users. 78 | 79 | 4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font 80 | Software shall not be used to promote, endorse or advertise any 81 | Modified Version, except to acknowledge the contribution(s) of the 82 | Copyright Holder(s) and the Author(s) or with their explicit written 83 | permission. 84 | 85 | 5. The Font Software, modified or unmodified, in part or in whole, 86 | must be distributed entirely under this license, and must not be 87 | distributed under any other license. The requirement for fonts to 88 | remain under this license does not apply to any document created 89 | using the Font Software. 90 | 91 | 92 | 93 | Termination 94 | ----------- 95 | 96 | This license becomes null and void if any of the above conditions are 97 | not met. 98 | 99 | 100 | DISCLAIMER 101 | 102 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 103 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 104 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 105 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 106 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 107 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 108 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 109 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 110 | OTHER DEALINGS IN THE FONT SOFTWARE. 111 | -------------------------------------------------------------------------------- /assets/fonts/Vollkorn-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/assets/fonts/Vollkorn-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/iosevka-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/assets/fonts/iosevka-bold.ttf -------------------------------------------------------------------------------- /assets/fonts/iosevka-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/assets/fonts/iosevka-regular.ttf -------------------------------------------------------------------------------- /assets/images/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/assets/images/100.png -------------------------------------------------------------------------------- /assets/images/100.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/eggplant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/assets/images/eggplant.png -------------------------------------------------------------------------------- /assets/images/eggplant.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/assets/images/fire.png -------------------------------------------------------------------------------- /assets/images/fire.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/joy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/assets/images/joy.png -------------------------------------------------------------------------------- /assets/images/joy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/assets/images/ok.png -------------------------------------------------------------------------------- /assets/images/ok.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/tsodinZezin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/assets/images/tsodinZezin.png -------------------------------------------------------------------------------- /assets/sounds/plant-bomb.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/assets/sounds/plant-bomb.wav -------------------------------------------------------------------------------- /nob.c: -------------------------------------------------------------------------------- 1 | #define NOB_IMPLEMENTATION 2 | #include "nob.h" 3 | 4 | // Folder must with with forward slash / 5 | #define BUILD_DIR "./build/" 6 | #define PANIM_DIR "./panim/" 7 | #define PLUGS_DIR "./plugs/" 8 | 9 | void cflags(Nob_Cmd *cmd) 10 | { 11 | nob_cmd_append(cmd, "-Wall", "-Wextra", "-ggdb"); 12 | nob_cmd_append(cmd, "-I./raylib/raylib-5.0_linux_amd64/include"); 13 | nob_cmd_append(cmd, "-I"PANIM_DIR); 14 | nob_cmd_append(cmd, "-I."); 15 | } 16 | 17 | void cc(Nob_Cmd *cmd) 18 | { 19 | nob_cmd_append(cmd, "cc"); 20 | cflags(cmd); 21 | } 22 | 23 | void cxx(Nob_Cmd *cmd) 24 | { 25 | nob_cmd_append(cmd, "g++"); 26 | nob_cmd_append(cmd, "-Wno-missing-field-initializers"); // Very common warning when compiling raymath.h as C++ 27 | cflags(cmd); 28 | } 29 | 30 | void libs(Nob_Cmd *cmd) 31 | { 32 | nob_cmd_append(cmd, "-Wl,-rpath=./raylib/raylib-5.0_linux_amd64/lib/"); 33 | nob_cmd_append(cmd, "-Wl,-rpath="PANIM_DIR); 34 | nob_cmd_append(cmd, "-L./raylib/raylib-5.0_linux_amd64/lib"); 35 | nob_cmd_append(cmd, "-l:libraylib.so", "-lm", "-ldl", "-lpthread"); 36 | } 37 | 38 | bool build_plug_c3(bool force, Nob_Cmd *cmd, const char *output_path, const char **source_paths, size_t source_paths_count) 39 | { 40 | int rebuild_is_needed = nob_needs_rebuild(nob_temp_sprintf("%s.so", output_path), source_paths, source_paths_count); 41 | if (rebuild_is_needed < 0) return false; 42 | if (force || rebuild_is_needed) { 43 | // TODO: check if c3c compile even exists 44 | // otherwise this is not buildable 45 | nob_cmd_append(cmd, "c3c", "dynamic-lib", "-o", output_path); 46 | nob_da_append_many(cmd, source_paths, source_paths_count); 47 | if (!nob_cmd_run_sync_and_reset(cmd)) return false; 48 | } 49 | 50 | return true; 51 | } 52 | 53 | bool build_plug_c(bool force, Nob_Cmd *cmd, const char *source_path, const char *output_path) 54 | { 55 | int rebuild_is_needed = nob_needs_rebuild1(output_path, source_path); 56 | if (rebuild_is_needed < 0) return false; 57 | 58 | if (force || rebuild_is_needed) { 59 | cc(cmd); 60 | nob_cmd_append(cmd, "-fPIC", "-shared", "-Wl,--no-undefined"); 61 | nob_cmd_append(cmd, "-o", output_path); 62 | nob_cmd_append(cmd, source_path); 63 | libs(cmd); 64 | return nob_cmd_run_sync_and_reset(cmd); 65 | } 66 | 67 | nob_log(NOB_INFO, "%s is up-to-date", output_path); 68 | return true; 69 | } 70 | 71 | bool build_plug_cxx(bool force, Nob_Cmd *cmd, const char *source_path, const char *output_path) 72 | { 73 | int rebuild_is_needed = nob_needs_rebuild1(output_path, source_path); 74 | if (rebuild_is_needed < 0) return false; 75 | 76 | if (force || rebuild_is_needed) { 77 | cxx(cmd); 78 | nob_cmd_append(cmd, "-fPIC", "-shared", "-Wl,--no-undefined"); 79 | nob_cmd_append(cmd, "-o", output_path); 80 | nob_cmd_append(cmd, source_path); 81 | libs(cmd); 82 | return nob_cmd_run_sync_and_reset(cmd); 83 | } 84 | 85 | nob_log(NOB_INFO, "%s is up-to-date", output_path); 86 | return true; 87 | } 88 | 89 | bool build_exe(bool force, Nob_Cmd *cmd, const char **input_paths, size_t input_paths_len, const char *output_path) 90 | { 91 | int rebuild_is_needed = nob_needs_rebuild(output_path, input_paths, input_paths_len); 92 | if (rebuild_is_needed < 0) return false; 93 | 94 | if (force || rebuild_is_needed) { 95 | cc(cmd); 96 | nob_cmd_append(cmd, "-o", output_path); 97 | nob_da_append_many(cmd, input_paths, input_paths_len); 98 | libs(cmd); 99 | return nob_cmd_run_sync_and_reset(cmd); 100 | } 101 | 102 | nob_log(NOB_INFO, "%s is up-to-date", output_path); 103 | return true; 104 | } 105 | 106 | int main(int argc, char **argv) 107 | { 108 | NOB_GO_REBUILD_URSELF(argc, argv); 109 | 110 | const char *program_name = nob_shift_args(&argc, &argv); 111 | (void) program_name; 112 | 113 | bool force = false; 114 | while (argc > 0) { 115 | const char *flag = nob_shift_args(&argc, &argv); 116 | if (strcmp(flag, "-f") == 0) { 117 | force = true; 118 | } else { 119 | nob_log(NOB_ERROR, "Unknown flag %s", flag); 120 | return 1; 121 | } 122 | } 123 | 124 | if (!nob_mkdir_if_not_exists(BUILD_DIR)) return 1; 125 | 126 | Nob_Cmd cmd = {0}; 127 | if (!build_plug_c(force, &cmd, PLUGS_DIR"tm/plug.c", BUILD_DIR"libtm.so")) return 1; 128 | if (!build_plug_c(force, &cmd, PLUGS_DIR"template/plug.c", BUILD_DIR"libtemplate.so")) return 1; 129 | if (!build_plug_c(force, &cmd, PLUGS_DIR"squares/plug.c", BUILD_DIR"libsquare.so")) return 1; 130 | if (!build_plug_c(force, &cmd, PLUGS_DIR"bezier/plug.c", BUILD_DIR"libbezier.so")) return 1; 131 | if (!build_plug_cxx(force, &cmd, PLUGS_DIR"cpp/plug.cpp", BUILD_DIR"libcpp.so")) return 1; 132 | { 133 | const char *output_path = BUILD_DIR"libc3"; 134 | const char *source_paths[] = { 135 | PLUGS_DIR"c3/plug.c3", 136 | PLUGS_DIR"c3/raylib.c3i", 137 | PLUGS_DIR"c3/future.c3" 138 | }; 139 | size_t source_paths_count = NOB_ARRAY_LEN(source_paths); 140 | 141 | if (!build_plug_c3(force, &cmd, output_path, source_paths, source_paths_count)) return 1; 142 | } 143 | 144 | { 145 | const char *output_path = BUILD_DIR"panim"; 146 | const char *input_paths[] = { 147 | PANIM_DIR"panim.c", 148 | PANIM_DIR"ffmpeg_linux.c" 149 | }; 150 | size_t input_paths_len = NOB_ARRAY_LEN(input_paths); 151 | if (!build_exe(force, &cmd, input_paths, input_paths_len, output_path)) return 1; 152 | } 153 | 154 | return 0; 155 | } 156 | -------------------------------------------------------------------------------- /panim/env.h: -------------------------------------------------------------------------------- 1 | #ifndef ENV_H_ 2 | #define ENV_H_ 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | float delta_time; 9 | float screen_width; 10 | float screen_height; 11 | bool rendering; 12 | void (*play_sound)(Sound sound, Wave wave); 13 | } Env; 14 | 15 | #endif // ENV_H_ 16 | -------------------------------------------------------------------------------- /panim/ffmpeg.h: -------------------------------------------------------------------------------- 1 | #ifndef FFMPEG_H_ 2 | #define FFMPEG_H_ 3 | 4 | #include 5 | #include 6 | 7 | typedef struct FFMPEG FFMPEG; 8 | 9 | FFMPEG *ffmpeg_start_rendering_video(const char *output_path, size_t width, size_t height, size_t fps); 10 | FFMPEG *ffmpeg_start_rendering_audio(const char *output_path); 11 | bool ffmpeg_send_frame_flipped(FFMPEG *ffmpeg, void *data, size_t width, size_t height); 12 | bool ffmpeg_send_sound_samples(FFMPEG *ffmpeg, void *data, size_t size); 13 | bool ffmpeg_end_rendering(FFMPEG *ffmpeg, bool cancel); 14 | 15 | #endif // FFMPEG_H_ 16 | -------------------------------------------------------------------------------- /panim/ffmpeg_linux.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "ffmpeg.h" 15 | 16 | #define READ_END 0 17 | #define WRITE_END 1 18 | 19 | struct FFMPEG { 20 | int pipe; 21 | pid_t pid; 22 | }; 23 | 24 | FFMPEG *ffmpeg_start_rendering_video(const char *output_path, size_t width, size_t height, size_t fps) 25 | { 26 | int pipefd[2]; 27 | 28 | if (pipe(pipefd) < 0) { 29 | TraceLog(LOG_ERROR, "FFMPEG: Could not create a pipe: %s", strerror(errno)); 30 | return NULL; 31 | } 32 | 33 | pid_t child = fork(); 34 | if (child < 0) { 35 | TraceLog(LOG_ERROR, "FFMPEG: could not fork a child: %s", strerror(errno)); 36 | return NULL; 37 | } 38 | 39 | if (child == 0) { 40 | if (dup2(pipefd[READ_END], STDIN_FILENO) < 0) { 41 | TraceLog(LOG_ERROR, "FFMPEG CHILD: could not reopen read end of pipe as stdin: %s", strerror(errno)); 42 | exit(1); 43 | } 44 | close(pipefd[WRITE_END]); 45 | 46 | char resolution[64]; 47 | snprintf(resolution, sizeof(resolution), "%zux%zu", width, height); 48 | char framerate[64]; 49 | snprintf(framerate, sizeof(framerate), "%zu", fps); 50 | 51 | int ret = execlp("ffmpeg", 52 | "ffmpeg", 53 | 54 | "-loglevel", "verbose", 55 | "-y", 56 | 57 | "-f", "rawvideo", 58 | "-pix_fmt", "rgba", 59 | "-s", resolution, 60 | "-r", framerate, 61 | "-i", "-", 62 | 63 | "-c:v", "libx264", 64 | "-vb", "2500k", 65 | "-c:a", "aac", 66 | "-ab", "200k", 67 | "-pix_fmt", "yuv420p", 68 | output_path, 69 | 70 | NULL 71 | ); 72 | if (ret < 0) { 73 | TraceLog(LOG_ERROR, "FFMPEG CHILD: could not run ffmpeg as a child process: %s", strerror(errno)); 74 | exit(1); 75 | } 76 | assert(0 && "unreachable"); 77 | exit(1); 78 | } 79 | 80 | if (close(pipefd[READ_END]) < 0) { 81 | TraceLog(LOG_WARNING, "FFMPEG: could not close read end of the pipe on the parent's end: %s", strerror(errno)); 82 | } 83 | 84 | FFMPEG *ffmpeg = malloc(sizeof(FFMPEG)); 85 | assert(ffmpeg != NULL && "Buy MORE RAM lol!!"); 86 | ffmpeg->pid = child; 87 | ffmpeg->pipe = pipefd[WRITE_END]; 88 | return ffmpeg; 89 | } 90 | 91 | FFMPEG *ffmpeg_start_rendering_audio(const char *output_path) 92 | { 93 | int pipefd[2]; 94 | 95 | if (pipe(pipefd) < 0) { 96 | TraceLog(LOG_ERROR, "FFMPEG: Could not create a pipe: %s", strerror(errno)); 97 | return NULL; 98 | } 99 | 100 | pid_t child = fork(); 101 | if (child < 0) { 102 | TraceLog(LOG_ERROR, "FFMPEG: could not fork a child: %s", strerror(errno)); 103 | return NULL; 104 | } 105 | 106 | if (child == 0) { 107 | if (dup2(pipefd[READ_END], STDIN_FILENO) < 0) { 108 | TraceLog(LOG_ERROR, "FFMPEG CHILD: could not reopen read end of pipe as stdin: %s", strerror(errno)); 109 | exit(1); 110 | } 111 | close(pipefd[WRITE_END]); 112 | 113 | int ret = execlp("ffmpeg", 114 | "ffmpeg", 115 | 116 | "-loglevel", "verbose", 117 | "-y", 118 | 119 | "-f", "s16le", 120 | "-sample_rate", "44100", 121 | "-channels", "2", 122 | "-i", "-", 123 | 124 | "-c:a", "pcm_s16le", 125 | output_path, 126 | 127 | NULL 128 | ); 129 | if (ret < 0) { 130 | TraceLog(LOG_ERROR, "FFMPEG CHILD: could not run ffmpeg as a child process: %s", strerror(errno)); 131 | exit(1); 132 | } 133 | assert(0 && "unreachable"); 134 | exit(1); 135 | } 136 | 137 | if (close(pipefd[READ_END]) < 0) { 138 | TraceLog(LOG_WARNING, "FFMPEG: could not close read end of the pipe on the parent's end: %s", strerror(errno)); 139 | } 140 | 141 | FFMPEG *ffmpeg = malloc(sizeof(FFMPEG)); 142 | assert(ffmpeg != NULL && "Buy MORE RAM lol!!"); 143 | ffmpeg->pid = child; 144 | ffmpeg->pipe = pipefd[WRITE_END]; 145 | return ffmpeg; 146 | } 147 | 148 | bool ffmpeg_end_rendering(FFMPEG *ffmpeg, bool cancel) 149 | { 150 | int pipe = ffmpeg->pipe; 151 | pid_t pid = ffmpeg->pid; 152 | 153 | free(ffmpeg); 154 | 155 | if (close(pipe) < 0) { 156 | TraceLog(LOG_WARNING, "FFMPEG: could not close write end of the pipe on the parent's end: %s", strerror(errno)); 157 | } 158 | 159 | if (cancel) kill(pid, SIGKILL); 160 | 161 | for (;;) { 162 | int wstatus = 0; 163 | if (waitpid(pid, &wstatus, 0) < 0) { 164 | TraceLog(LOG_ERROR, "FFMPEG: could not wait for ffmpeg child process to finish: %s", strerror(errno)); 165 | return false; 166 | } 167 | 168 | if (WIFEXITED(wstatus)) { 169 | int exit_status = WEXITSTATUS(wstatus); 170 | if (exit_status != 0) { 171 | TraceLog(LOG_ERROR, "FFMPEG: ffmpeg exited with code %d", exit_status); 172 | return false; 173 | } 174 | 175 | return true; 176 | } 177 | 178 | if (WIFSIGNALED(wstatus)) { 179 | TraceLog(LOG_ERROR, "FFMPEG: ffmpeg got terminated by %s", strsignal(WTERMSIG(wstatus))); 180 | return false; 181 | } 182 | } 183 | 184 | assert(0 && "unreachable"); 185 | } 186 | 187 | bool ffmpeg_send_frame_flipped(FFMPEG *ffmpeg, void *data, size_t width, size_t height) 188 | { 189 | for (size_t y = height; y > 0; --y) { 190 | // TODO: write() may not necessarily write the entire row. We may want to repeat the call. 191 | if (write(ffmpeg->pipe, (uint32_t*)data + (y - 1)*width, sizeof(uint32_t)*width) < 0) { 192 | TraceLog(LOG_ERROR, "FFMPEG: failed to write frame into ffmpeg pipe: %s", strerror(errno)); 193 | return false; 194 | } 195 | } 196 | return true; 197 | } 198 | 199 | bool ffmpeg_send_sound_samples(FFMPEG *ffmpeg, void *data, size_t size) 200 | { 201 | if (write(ffmpeg->pipe, data, size) < 0) { 202 | TraceLog(LOG_ERROR, "FFMPEG: failed to write sound into ffmpeg pipe: %s", strerror(errno)); 203 | return false; 204 | } 205 | return true; 206 | } 207 | -------------------------------------------------------------------------------- /panim/panim.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #define NOB_IMPLEMENTATION 11 | #include "nob.h" 12 | #include "plug.h" 13 | #include "ffmpeg.h" 14 | 15 | // #define FFMPEG_VIDEO_WIDTH 1600 16 | // #define FFMPEG_VIDEO_HEIGHT 900 17 | // #define FFMPEG_VIDEO_FPS 30 18 | #define FFMPEG_VIDEO_WIDTH 1920 19 | #define FFMPEG_VIDEO_HEIGHT 1080 20 | #define FFMPEG_VIDEO_FPS 60 21 | #define FFMPEG_VIDEO_DELTA_TIME (1.0f/FFMPEG_VIDEO_FPS) 22 | #define FFMPEG_SOUND_SAMPLE_RATE 44100 23 | #define FFMPEG_SOUND_CHANNELS 2 24 | #define FFMPEG_SOUND_SAMPLE_SIZE_BITS 16 25 | #define FFMPEG_SOUND_SAMPLE_SIZE_BYTES (FFMPEG_SOUND_SAMPLE_SIZE_BITS/8) 26 | // SPF - Samples Per Frame 27 | #define FFMPEG_SOUND_SPF (FFMPEG_SOUND_SAMPLE_RATE/FFMPEG_VIDEO_FPS) 28 | #define RENDERING_FONT_SIZE 78 29 | #define POPUP_DISAPPER_TIME 1.5f 30 | 31 | // The state of Panim Engine 32 | static bool paused = false; 33 | static FFMPEG *ffmpeg_video = NULL; 34 | static FFMPEG *ffmpeg_audio = NULL; 35 | static RenderTexture2D screen = {0}; 36 | static Font rendering_font = {0}; 37 | static void *libplug = NULL; 38 | static Wave ffmpeg_wave = {0}; 39 | static size_t ffmpeg_wave_cursor = 0; 40 | static uint8_t silence[FFMPEG_SOUND_SPF*FFMPEG_SOUND_SAMPLE_SIZE_BYTES*FFMPEG_SOUND_CHANNELS] = {0}; 41 | 42 | static float delta_time_multiplier = 1.0f; 43 | static float delta_time_multiplier_popup = 0.0f; 44 | 45 | #define PLUG(name, ret, ...) static ret (*name)(__VA_ARGS__); 46 | LIST_OF_PLUGS 47 | #undef PLUG 48 | 49 | static bool reload_libplug(const char *libplug_path) 50 | { 51 | if (libplug != NULL) { 52 | dlclose(libplug); 53 | } 54 | 55 | libplug = dlopen(libplug_path, RTLD_NOW); 56 | if (libplug == NULL) { 57 | fprintf(stderr, "ERROR: %s\n", dlerror()); 58 | return false; 59 | } 60 | 61 | #define PLUG(name, ...) \ 62 | name = dlsym(libplug, #name); \ 63 | if (name == NULL) { \ 64 | fprintf(stderr, "ERROR: %s\n", dlerror()); \ 65 | return false; \ 66 | } 67 | LIST_OF_PLUGS 68 | #undef PLUG 69 | 70 | return true; 71 | } 72 | 73 | static void finish_ffmpeg_video_rendering(bool cancel) 74 | { 75 | SetTraceLogLevel(LOG_INFO); 76 | ffmpeg_end_rendering(ffmpeg_video, cancel); 77 | plug_reset(); 78 | paused = true; 79 | ffmpeg_video = NULL; 80 | } 81 | 82 | static void finish_ffmpeg_audio_rendering(bool cancel) 83 | { 84 | SetTraceLogLevel(LOG_INFO); 85 | ffmpeg_end_rendering(ffmpeg_audio, cancel); 86 | plug_reset(); 87 | paused = true; 88 | ffmpeg_audio = NULL; 89 | } 90 | 91 | void dummy_play_sound(Sound _sound, Wave _wave) 92 | { 93 | (void)_sound; 94 | (void)_wave; 95 | } 96 | 97 | void ffmpeg_play_sound(Sound _sound, Wave wave) 98 | { 99 | (void)_sound; 100 | 101 | if ( 102 | wave.sampleRate != FFMPEG_SOUND_SAMPLE_RATE || 103 | wave.sampleSize != FFMPEG_SOUND_SAMPLE_SIZE_BITS || 104 | wave.channels != FFMPEG_SOUND_CHANNELS 105 | ) { 106 | TraceLog(LOG_ERROR, 107 | "Animation tried to play sound with rate: %dhz, sample size: %d bits, channels: %d. " 108 | "But we only support rate: %dhz, sample size: %d bits, channels: %d for now", 109 | wave.sampleRate, wave.sampleSize, wave.channels); 110 | return; 111 | } 112 | 113 | ffmpeg_wave = wave; 114 | ffmpeg_wave_cursor = 0; 115 | } 116 | 117 | void preview_play_sound(Sound sound, Wave _wave) 118 | { 119 | (void)_wave; 120 | PlaySound(sound); 121 | } 122 | 123 | void rendering_scene(const char *text) 124 | { 125 | Color foreground_color = ColorFromHSV(0, 0, 0.95); 126 | Color background_color = ColorFromHSV(0, 0, 0.05); 127 | 128 | ClearBackground(background_color); 129 | Vector2 text_size = MeasureTextEx(rendering_font, text, RENDERING_FONT_SIZE, 0); 130 | Vector2 position = { 131 | GetScreenWidth()/2 - text_size.x/2, 132 | GetScreenHeight()/2 - text_size.y/2, 133 | }; 134 | DrawTextEx(rendering_font, text, position, RENDERING_FONT_SIZE, 0, foreground_color); 135 | 136 | float circle_radius = RENDERING_FONT_SIZE*0.2f; 137 | float ball_height = GetScreenHeight()*0.03; 138 | float ball_padding = GetScreenHeight()*0.02; 139 | float waving_speed = 2; 140 | 141 | { 142 | Vector2 center = { 143 | .x = position.x + text_size.x*0.5 - circle_radius*3, 144 | .y = position.y + RENDERING_FONT_SIZE + ball_padding + ball_height*(sinf(GetTime()*waving_speed - PI/4) + 1)*0.5, 145 | }; 146 | DrawCircleV(center, circle_radius, foreground_color); 147 | } 148 | 149 | { 150 | Vector2 center = { 151 | .x = position.x + text_size.x*0.5, 152 | .y = position.y + RENDERING_FONT_SIZE + ball_padding + ball_height*(sinf(GetTime()*waving_speed) + 1)*0.5, 153 | }; 154 | DrawCircleV(center, circle_radius, foreground_color); 155 | } 156 | 157 | { 158 | Vector2 center = { 159 | .x = position.x + text_size.x*0.5 + circle_radius*3, 160 | .y = position.y + RENDERING_FONT_SIZE + ball_padding + ball_height*(sinf(GetTime()*waving_speed + PI/4) + 1)*0.5, 161 | }; 162 | DrawCircleV(center, circle_radius, foreground_color); 163 | } 164 | } 165 | 166 | int main(int argc, char **argv) 167 | { 168 | const char *program_name = nob_shift_args(&argc, &argv); 169 | 170 | if (argc <= 0) { 171 | fprintf(stderr, "Usage: %s \n", program_name); 172 | fprintf(stderr, "ERROR: no animation dynamic library is provided\n"); 173 | return 1; 174 | } 175 | 176 | const char *libplug_path = nob_shift_args(&argc, &argv); 177 | 178 | if (!reload_libplug(libplug_path)) return 1; 179 | 180 | float factor = 100.0f; 181 | SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE); 182 | InitWindow(16*factor, 9*factor, "Panim"); 183 | InitAudioDevice(); 184 | SetTargetFPS(60); 185 | SetExitKey(KEY_NULL); 186 | plug_init(); 187 | 188 | screen = LoadRenderTexture(FFMPEG_VIDEO_WIDTH, FFMPEG_VIDEO_HEIGHT); 189 | rendering_font = LoadFontEx("./assets/fonts/Vollkorn-Regular.ttf", RENDERING_FONT_SIZE, NULL, 0); 190 | 191 | while (!WindowShouldClose()) { 192 | BeginDrawing(); 193 | if (ffmpeg_video) { 194 | if (plug_finished()) { 195 | finish_ffmpeg_video_rendering(false); 196 | } else if (IsKeyPressed(KEY_ESCAPE)) { 197 | finish_ffmpeg_video_rendering(true); 198 | } else { 199 | BeginTextureMode(screen); 200 | plug_update(CLITERAL(Env) { 201 | .screen_width = FFMPEG_VIDEO_WIDTH, 202 | .screen_height = FFMPEG_VIDEO_HEIGHT, 203 | .delta_time = FFMPEG_VIDEO_DELTA_TIME, 204 | .rendering = true, 205 | .play_sound = dummy_play_sound, 206 | }); 207 | EndTextureMode(); 208 | 209 | Image image = LoadImageFromTexture(screen.texture); 210 | if (!ffmpeg_send_frame_flipped(ffmpeg_video, image.data, image.width, image.height)) { 211 | finish_ffmpeg_video_rendering(true); 212 | } 213 | UnloadImage(image); 214 | } 215 | rendering_scene("Rendering Video"); 216 | } else if (ffmpeg_audio) { 217 | if (plug_finished()) { 218 | finish_ffmpeg_audio_rendering(false); 219 | } else if (IsKeyPressed(KEY_ESCAPE)) { 220 | finish_ffmpeg_audio_rendering(true); 221 | } else { 222 | BeginTextureMode(screen); 223 | plug_update(CLITERAL(Env) { 224 | .screen_width = FFMPEG_VIDEO_WIDTH, 225 | .screen_height = FFMPEG_VIDEO_HEIGHT, 226 | .delta_time = FFMPEG_VIDEO_DELTA_TIME, 227 | .rendering = true, 228 | .play_sound = ffmpeg_play_sound, 229 | }); 230 | EndTextureMode(); 231 | 232 | size_t frame_count = ffmpeg_wave.frameCount; 233 | size_t frame_size = FFMPEG_SOUND_SAMPLE_SIZE_BYTES*FFMPEG_SOUND_CHANNELS; 234 | size_t frames_begin = ffmpeg_wave_cursor; 235 | size_t frames_end = ffmpeg_wave_cursor + FFMPEG_SOUND_SPF; 236 | if (frames_end > frame_count) { 237 | frames_end = frame_count; 238 | } 239 | void *sound_data = (uint8_t*)ffmpeg_wave.data + frames_begin*frame_size; 240 | size_t sound_size = (frames_end - frames_begin)*frame_size; 241 | if (!ffmpeg_send_sound_samples(ffmpeg_audio, sound_data, sound_size)) { 242 | finish_ffmpeg_audio_rendering(true); 243 | } 244 | ffmpeg_wave_cursor += frames_end - frames_begin; 245 | size_t silence_size = (FFMPEG_SOUND_SPF - (frames_end - frames_begin))*frame_size; 246 | if (!ffmpeg_send_sound_samples(ffmpeg_audio, silence, silence_size)) { 247 | finish_ffmpeg_audio_rendering(true); 248 | } 249 | } 250 | rendering_scene("Rendering Audio"); 251 | } else { 252 | if (IsKeyPressed(KEY_R)) { 253 | SetTraceLogLevel(LOG_WARNING); 254 | ffmpeg_video = ffmpeg_start_rendering_video("output.mp4", FFMPEG_VIDEO_WIDTH, FFMPEG_VIDEO_HEIGHT, FFMPEG_VIDEO_FPS); 255 | plug_reset(); 256 | } else if (IsKeyPressed(KEY_T)) { 257 | SetTraceLogLevel(LOG_WARNING); 258 | ffmpeg_audio = ffmpeg_start_rendering_audio("output.wav"); 259 | plug_reset(); 260 | } else { 261 | if (IsKeyPressed(KEY_H)) { 262 | void *state = plug_pre_reload(); 263 | reload_libplug(libplug_path); 264 | plug_post_reload(state); 265 | } 266 | if (IsKeyPressed(KEY_SPACE)) { 267 | paused = !paused; 268 | } 269 | if (IsKeyPressed(KEY_Q)) { 270 | plug_reset(); 271 | } 272 | if (IsKeyPressed(KEY_PERIOD)) { 273 | delta_time_multiplier += 0.1; 274 | delta_time_multiplier_popup = 1.0f; 275 | } 276 | if (IsKeyPressed(KEY_COMMA) && delta_time_multiplier > 0.0f) { 277 | delta_time_multiplier -= 0.1; 278 | delta_time_multiplier_popup = 1.0f; 279 | } 280 | if (IsKeyPressed(KEY_ZERO)) { 281 | delta_time_multiplier = 1.0; 282 | delta_time_multiplier_popup = 1.0f; 283 | } 284 | 285 | plug_update(CLITERAL(Env) { 286 | .screen_width = GetScreenWidth(), 287 | .screen_height = GetScreenHeight(), 288 | .delta_time = paused ? 0.0 : GetFrameTime()*delta_time_multiplier, 289 | .rendering = false, 290 | .play_sound = preview_play_sound, 291 | }); 292 | 293 | const char *text = TextFormat("Delta Time Multiplier: %.2fx", delta_time_multiplier); 294 | Vector2 text_size = MeasureTextEx(rendering_font, text, RENDERING_FONT_SIZE, 0); 295 | Vector2 position = { 296 | GetScreenWidth()/2 - text_size.x/2, 297 | GetScreenHeight()/2 - text_size.y/2, 298 | }; 299 | DrawTextEx(rendering_font, text, Vector2Subtract(position, (Vector2){3, 3}), RENDERING_FONT_SIZE, 0, ColorAlpha(BLACK, delta_time_multiplier_popup)); 300 | DrawTextEx(rendering_font, text, position, RENDERING_FONT_SIZE, 0, ColorAlpha(WHITE, delta_time_multiplier_popup)); 301 | if (delta_time_multiplier_popup > 0.0f) { 302 | delta_time_multiplier_popup = (delta_time_multiplier_popup*POPUP_DISAPPER_TIME - GetFrameTime())/POPUP_DISAPPER_TIME; 303 | } 304 | } 305 | } 306 | EndDrawing(); 307 | } 308 | 309 | CloseWindow(); 310 | 311 | return 0; 312 | } 313 | -------------------------------------------------------------------------------- /panim/plug.h: -------------------------------------------------------------------------------- 1 | #ifndef PLUG_H_ 2 | #define PLUG_H_ 3 | 4 | #include "env.h" 5 | 6 | // void plug_init(void) 7 | // void *plug_pre_reload(void) 8 | // void plug_post_reload(void *state) 9 | // void plug_update(Env env) 10 | // void plug_reset(void) 11 | // bool plug_finished(void) 12 | 13 | #define LIST_OF_PLUGS \ 14 | PLUG(plug_init, void, void) /* Initialize the plugin */ \ 15 | PLUG(plug_pre_reload, void*, void) /* Notify the plugin that it's about to get reloaded */ \ 16 | PLUG(plug_post_reload, void, void*) /* Notify the plugin that it got reloaded */ \ 17 | PLUG(plug_update, void, Env) /* Render next frame of the animation */ \ 18 | PLUG(plug_reset, void, void) /* Reset the state of the animation */ \ 19 | PLUG(plug_finished, bool, void) /* Check if the animation is finished */ \ 20 | 21 | #endif // PLUG_H_ 22 | -------------------------------------------------------------------------------- /plugs/bezier/arena.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Alexey Kutepov 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | #ifndef ARENA_H_ 23 | #define ARENA_H_ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #ifndef ARENA_NOSTDIO 30 | #include 31 | #include 32 | #endif // ARENA_NOSTDIO 33 | 34 | #ifndef ARENA_ASSERT 35 | #include 36 | #define ARENA_ASSERT assert 37 | #endif 38 | 39 | #define ARENA_BACKEND_LIBC_MALLOC 0 40 | #define ARENA_BACKEND_LINUX_MMAP 1 41 | #define ARENA_BACKEND_WIN32_VIRTUALALLOC 2 42 | #define ARENA_BACKEND_WASM_HEAPBASE 3 43 | 44 | #ifndef ARENA_BACKEND 45 | #define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC 46 | #endif // ARENA_BACKEND 47 | 48 | typedef struct Region Region; 49 | 50 | struct Region { 51 | Region *next; 52 | size_t count; 53 | size_t capacity; 54 | uintptr_t data[]; 55 | }; 56 | 57 | typedef struct { 58 | Region *begin, *end; 59 | } Arena; 60 | 61 | #define REGION_DEFAULT_CAPACITY (8*1024) 62 | 63 | Region *new_region(size_t capacity); 64 | void free_region(Region *r); 65 | 66 | // TODO: snapshot/rewind capability for the arena 67 | // - Snapshot should be combination of a->end and a->end->count. 68 | // - Rewinding should be restoring a->end and a->end->count from the snapshot and 69 | // setting count-s of all the Region-s after the remembered a->end to 0. 70 | void *arena_alloc(Arena *a, size_t size_bytes); 71 | void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz); 72 | char *arena_strdup(Arena *a, const char *cstr); 73 | void *arena_memdup(Arena *a, void *data, size_t size); 74 | #ifndef ARENA_NOSTDIO 75 | char *arena_sprintf(Arena *a, const char *format, ...); 76 | #endif // ARENA_NOSTDIO 77 | 78 | void arena_reset(Arena *a); 79 | void arena_free(Arena *a); 80 | 81 | #define ARENA_DA_INIT_CAP 256 82 | 83 | #ifdef __cplusplus 84 | #define cast_ptr(ptr) (decltype(ptr)) 85 | #else 86 | #define cast_ptr(...) 87 | #endif 88 | 89 | #define arena_da_append(a, da, item) \ 90 | do { \ 91 | if ((da)->count >= (da)->capacity) { \ 92 | size_t new_capacity = (da)->capacity == 0 ? ARENA_DA_INIT_CAP : (da)->capacity*2; \ 93 | (da)->items = cast_ptr((da)->items)arena_realloc( \ 94 | (a), (da)->items, \ 95 | (da)->capacity*sizeof(*(da)->items), \ 96 | new_capacity*sizeof(*(da)->items)); \ 97 | (da)->capacity = new_capacity; \ 98 | } \ 99 | \ 100 | (da)->items[(da)->count++] = (item); \ 101 | } while (0) 102 | 103 | #endif // ARENA_H_ 104 | 105 | #ifdef ARENA_IMPLEMENTATION 106 | 107 | #if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC 108 | #include 109 | 110 | // TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region 111 | // It should be up to new_region() to decide the actual capacity to allocate 112 | Region *new_region(size_t capacity) 113 | { 114 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity; 115 | // TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned 116 | Region *r = (Region*)malloc(size_bytes); 117 | ARENA_ASSERT(r); 118 | r->next = NULL; 119 | r->count = 0; 120 | r->capacity = capacity; 121 | return r; 122 | } 123 | 124 | void free_region(Region *r) 125 | { 126 | free(r); 127 | } 128 | #elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP 129 | #include 130 | #include 131 | 132 | Region *new_region(size_t capacity) 133 | { 134 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; 135 | Region *r = mmap(NULL, size_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 136 | ARENA_ASSERT(r != MAP_FAILED); 137 | r->next = NULL; 138 | r->count = 0; 139 | r->capacity = capacity; 140 | return r; 141 | } 142 | 143 | void free_region(Region *r) 144 | { 145 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * r->capacity; 146 | int ret = munmap(r, size_bytes); 147 | ARENA_ASSERT(ret == 0); 148 | } 149 | 150 | #elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC 151 | 152 | #if !defined(_WIN32) 153 | # error "Current platform is not Windows" 154 | #endif 155 | 156 | #define WIN32_LEAN_AND_MEAN 157 | #include 158 | 159 | #define INV_HANDLE(x) (((x) == NULL) || ((x) == INVALID_HANDLE_VALUE)) 160 | 161 | Region *new_region(size_t capacity) 162 | { 163 | SIZE_T size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; 164 | Region *r = VirtualAllocEx( 165 | GetCurrentProcess(), /* Allocate in current process address space */ 166 | NULL, /* Unknown position */ 167 | size_bytes, /* Bytes to allocate */ 168 | MEM_COMMIT | MEM_RESERVE, /* Reserve and commit allocated page */ 169 | PAGE_READWRITE /* Permissions ( Read/Write )*/ 170 | ); 171 | if (INV_HANDLE(r)) 172 | ARENA_ASSERT(0 && "VirtualAllocEx() failed."); 173 | 174 | r->next = NULL; 175 | r->count = 0; 176 | r->capacity = capacity; 177 | return r; 178 | } 179 | 180 | void free_region(Region *r) 181 | { 182 | if (INV_HANDLE(r)) 183 | return; 184 | 185 | BOOL free_result = VirtualFreeEx( 186 | GetCurrentProcess(), /* Deallocate from current process address space */ 187 | (LPVOID)r, /* Address to deallocate */ 188 | 0, /* Bytes to deallocate ( Unknown, deallocate entire page ) */ 189 | MEM_RELEASE /* Release the page ( And implicitly decommit it ) */ 190 | ); 191 | 192 | if (FALSE == free_result) 193 | ARENA_ASSERT(0 && "VirtualFreeEx() failed."); 194 | } 195 | 196 | #elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE 197 | # error "TODO: WASM __heap_base backend is not implemented yet" 198 | #else 199 | # error "Unknown Arena backend" 200 | #endif 201 | 202 | // TODO: add debug statistic collection mode for arena 203 | // Should collect things like: 204 | // - How many times new_region was called 205 | // - How many times existing region was skipped 206 | // - How many times allocation exceeded REGION_DEFAULT_CAPACITY 207 | 208 | void *arena_alloc(Arena *a, size_t size_bytes) 209 | { 210 | size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t); 211 | 212 | if (a->end == NULL) { 213 | ARENA_ASSERT(a->begin == NULL); 214 | size_t capacity = REGION_DEFAULT_CAPACITY; 215 | if (capacity < size) capacity = size; 216 | a->end = new_region(capacity); 217 | a->begin = a->end; 218 | } 219 | 220 | while (a->end->count + size > a->end->capacity && a->end->next != NULL) { 221 | a->end = a->end->next; 222 | } 223 | 224 | if (a->end->count + size > a->end->capacity) { 225 | ARENA_ASSERT(a->end->next == NULL); 226 | size_t capacity = REGION_DEFAULT_CAPACITY; 227 | if (capacity < size) capacity = size; 228 | a->end->next = new_region(capacity); 229 | a->end = a->end->next; 230 | } 231 | 232 | void *result = &a->end->data[a->end->count]; 233 | a->end->count += size; 234 | return result; 235 | } 236 | 237 | void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz) 238 | { 239 | if (newsz <= oldsz) return oldptr; 240 | void *newptr = arena_alloc(a, newsz); 241 | char *newptr_char = (char*)newptr; 242 | char *oldptr_char = (char*)oldptr; 243 | for (size_t i = 0; i < oldsz; ++i) { 244 | newptr_char[i] = oldptr_char[i]; 245 | } 246 | return newptr; 247 | } 248 | 249 | char *arena_strdup(Arena *a, const char *cstr) 250 | { 251 | size_t n = strlen(cstr); 252 | char *dup = (char*)arena_alloc(a, n + 1); 253 | memcpy(dup, cstr, n); 254 | dup[n] = '\0'; 255 | return dup; 256 | } 257 | 258 | void *arena_memdup(Arena *a, void *data, size_t size) 259 | { 260 | return memcpy(arena_alloc(a, size), data, size); 261 | } 262 | 263 | #ifndef ARENA_NOSTDIO 264 | char *arena_sprintf(Arena *a, const char *format, ...) 265 | { 266 | va_list args; 267 | va_start(args, format); 268 | int n = vsnprintf(NULL, 0, format, args); 269 | va_end(args); 270 | 271 | ARENA_ASSERT(n >= 0); 272 | char *result = (char*)arena_alloc(a, n + 1); 273 | va_start(args, format); 274 | vsnprintf(result, n + 1, format, args); 275 | va_end(args); 276 | 277 | return result; 278 | } 279 | #endif // ARENA_NOSTDIO 280 | 281 | void arena_reset(Arena *a) 282 | { 283 | for (Region *r = a->begin; r != NULL; r = r->next) { 284 | r->count = 0; 285 | } 286 | 287 | a->end = a->begin; 288 | } 289 | 290 | void arena_free(Arena *a) 291 | { 292 | Region *r = a->begin; 293 | while (r) { 294 | Region *r0 = r; 295 | r = r->next; 296 | free_region(r0); 297 | } 298 | a->begin = NULL; 299 | a->end = NULL; 300 | } 301 | 302 | #endif // ARENA_IMPLEMENTATION 303 | -------------------------------------------------------------------------------- /plugs/bezier/der/bezier.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage{amsmath} 3 | \begin{document} 4 | \section{Newton's Method for Bezier Curve} 5 | \begin{align} 6 | x(t) &= x_0*(1 - t)^3 \\ 7 | &+ 3x_1(1 - t)^2t \\ 8 | &+ 3x_2(1 - t)t^2 \\ 9 | &+ x_3t^3; \\ 10 | x'(t) &= x_0(-3)(1 - t)^2 \\ 11 | &+ 3x_1\left(2(1 - t)(-1)t + (1 - t)^2\right) \\ 12 | &+ 3x_2\left((-1)t^2 + (1 - t)2t\right) \\ 13 | &+ 3x_3t^2 \\ 14 | &= -3x_0(1 - t)^2 \\ 15 | &+ 3x_1(1 - t)^2 \\ 16 | &- 6x_1(1 - t)t \\ 17 | &+ 6x_2(1 - t)t \\ 18 | &- 3x_2t^2 \\ 19 | &+ 3x_3t^2; 20 | \end{align} 21 | \end{document} -------------------------------------------------------------------------------- /plugs/bezier/interpolators.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERPOLATORS_H_ 2 | #define INTERPOLATORS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef enum { 9 | FUNC_ID, 10 | FUNC_SINSTEP, 11 | FUNC_SMOOTHSTEP, 12 | FUNC_SQR, 13 | FUNC_SQRT, 14 | FUNC_SINPULSE, 15 | } Interp_Func; 16 | 17 | static inline float smoothstep(float x) 18 | { 19 | if (x < 0.0) return 0.0; 20 | if (x >= 1.0) return 1.0; 21 | return 3*x*x - 2*x*x*x; 22 | } 23 | 24 | static inline float sinstep(float t) 25 | { 26 | if (t < 0.0) return 0.0; 27 | if (t >= 1.0) return 1.0; 28 | return (sinf(PI*t - PI*0.5) + 1)*0.5; 29 | } 30 | 31 | static inline float sinpulse(float t) 32 | { 33 | if (t < 0.0) return 0.0; 34 | if (t >= 1.0) return 0.0; 35 | return sinf(PI*t); 36 | } 37 | 38 | static inline Vector2 cubic_bezier(float t, Vector2 nodes[4]) 39 | { 40 | float it = 1 - t; 41 | Vector2 b = Vector2Scale(nodes[0], it*it*it); 42 | b = Vector2Add(b, Vector2Scale(nodes[1], 3*it*it*t)); 43 | b = Vector2Add(b, Vector2Scale(nodes[2], 3*it*t*t)); 44 | b = Vector2Add(b, Vector2Scale(nodes[3], t*t*t)); 45 | return b; 46 | } 47 | 48 | static inline Vector2 cubic_bezier_der(float t, Vector2 nodes[4]) 49 | { 50 | float it = 1 - t; 51 | Vector2 b = Vector2Scale(nodes[0], -3*it*it); 52 | b = Vector2Add(b, Vector2Scale(nodes[1], 3*it*it)); 53 | b = Vector2Add(b, Vector2Scale(nodes[1], -6*it*t)); 54 | b = Vector2Add(b, Vector2Scale(nodes[2], 6*it*t)); 55 | b = Vector2Add(b, Vector2Scale(nodes[2], -3*t*t)); 56 | b = Vector2Add(b, Vector2Scale(nodes[3], 3*t*t)); 57 | return b; 58 | } 59 | 60 | static inline float cuber_bezier_newton(float x, Vector2 nodes[4], size_t n) 61 | { 62 | float t = 0; 63 | for (size_t i = 0; i < n; ++i) { 64 | t = t - (cubic_bezier(t, nodes).x - x)/cubic_bezier_der(t, nodes).x; 65 | } 66 | return t; 67 | } 68 | 69 | static inline float interp_func(Interp_Func func, float t) 70 | { 71 | switch (func) { 72 | case FUNC_ID: return t; 73 | case FUNC_SQR: return t*t; 74 | case FUNC_SQRT: return sqrtf(t); 75 | case FUNC_SINSTEP: return sinstep(t); 76 | case FUNC_SMOOTHSTEP: return smoothstep(t); 77 | case FUNC_SINPULSE: return sinpulse(t); 78 | } 79 | assert(0 && "UNREACHABLE"); 80 | return 0.0f; 81 | } 82 | 83 | #endif // INTERPOLATORS_H_ 84 | -------------------------------------------------------------------------------- /plugs/bezier/plug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include "env.h" 8 | #define NOB_IMPLEMENTATION 9 | #include "nob.h" 10 | #include "interpolators.h" 11 | #include "plug.h" 12 | 13 | #define PLUG(name, ret, ...) ret name(__VA_ARGS__); 14 | LIST_OF_PLUGS 15 | #undef PLUG 16 | 17 | #define FONT_SIZE 32 18 | #define AXIS_THICCNESS 5.0 19 | #define AXIS_COLOR BLUE 20 | #define AXIS_LENGTH 500.0f 21 | #define NODE_RADIUS 15.0f 22 | #define NODE_COLOR RED 23 | #define NODE_HOVER_COLOR YELLOW 24 | #define HANDLE_THICCNESS (AXIS_THICCNESS/2) 25 | #define HANDLE_COLOR YELLOW 26 | #define BEZIER_SAMPLE_RADIUS 5 27 | #define BEZIER_SAMPLE_COLOR YELLOW 28 | #define LABEL_PADDING 100.0 29 | #define CURVE_FILE_PATH "assets/curves/sigmoid.txt" 30 | 31 | #define COUNT_NODES 4 32 | 33 | typedef struct { 34 | size_t size; 35 | Font font; 36 | Vector2 nodes[COUNT_NODES]; 37 | int dragged_node; 38 | Nob_String_Builder sb; 39 | } Plug; 40 | 41 | static Plug *p; 42 | 43 | static void load_assets(void) 44 | { 45 | p->font = LoadFontEx("./assets/fonts/iosevka-regular.ttf", FONT_SIZE, NULL, 0); 46 | GenTextureMipmaps(&p->font.texture); 47 | SetTextureFilter(p->font.texture, TEXTURE_FILTER_BILINEAR); 48 | } 49 | 50 | static void unload_assets(void) 51 | { 52 | UnloadFont(p->font); 53 | } 54 | 55 | static bool save_curve_to_file(const char *file_path, Nob_String_Builder *sb, Vector2 curve[COUNT_NODES]) 56 | { 57 | sb->count = 0; 58 | for (size_t i = 0; i < COUNT_NODES; ++i) { 59 | nob_sb_append_cstr(sb, TextFormat("%f %f\n", curve[i].x/AXIS_LENGTH, -p->nodes[i].y/AXIS_LENGTH)); 60 | } 61 | bool ok = nob_write_entire_file(file_path, sb->items, sb->count); 62 | if (ok) TraceLog(LOG_INFO, "Saved curve to %s", file_path); 63 | return ok; 64 | } 65 | 66 | static bool load_curve_from_file(const char *file_path, Nob_String_Builder *sb, Vector2 curve[COUNT_NODES]) 67 | { 68 | sb->count = 0; 69 | if (!nob_read_entire_file(file_path, sb)) return false; 70 | nob_sb_append_null(sb); // NULL-terminator is needed for strtof below 71 | Nob_String_View content = { 72 | .data = sb->items, 73 | .count = sb->count - 1, // Minus the NULL-terminator 74 | }; 75 | size_t curve_count = 0; 76 | size_t row = 1; 77 | for (; content.count > 0 && curve_count < COUNT_NODES; ++row) { 78 | Nob_String_View line = nob_sv_chop_by_delim(&content, '\n'); 79 | const char *line_start = line.data; 80 | 81 | line = nob_sv_trim_left(line); 82 | if (line.count == 0) continue; // Silently skipping empty lines 83 | 84 | char *endptr = NULL; 85 | Nob_String_View arg = nob_sv_chop_by_delim(&line, ' '); 86 | curve[curve_count].x = strtof(arg.data, &endptr); 87 | if (endptr == arg.data) { 88 | TraceLog(LOG_WARNING, "%s:%zu:%zu: x value of node %zu is not a value float "SV_Fmt, file_path, row, arg.data - line_start + 1, curve_count, SV_Arg(arg)); 89 | continue; 90 | } 91 | curve[curve_count].x *= AXIS_LENGTH; 92 | 93 | line = nob_sv_trim_left(line); 94 | if (line.count == 0) { 95 | TraceLog(LOG_WARNING, "%s:%zu:%zu: y value of node %zu is missing", file_path, row, line.data - line_start + 1, curve_count); 96 | continue; 97 | } 98 | 99 | arg = nob_sv_chop_by_delim(&line, ' '); 100 | curve[curve_count].y = strtof(arg.data, &endptr); 101 | if (endptr == arg.data) { 102 | TraceLog(LOG_WARNING, "%s:%zu:%zu: y value of node %zu is not a value float "SV_Fmt, file_path, row, arg.data - line_start + 1, curve_count, SV_Arg(arg)); 103 | continue; 104 | } 105 | curve[curve_count].y *= -AXIS_LENGTH; 106 | 107 | TraceLog(LOG_INFO, "Parse field %f %f", curve[curve_count].x, curve[curve_count].y); 108 | curve_count += 1; 109 | 110 | line = nob_sv_trim_left(line); 111 | if (line.count > 0) { 112 | TraceLog(LOG_WARNING, "%s:%zu:%zu: garbage at the end of the line", file_path, row, line.data - line_start + 1); 113 | } 114 | } 115 | 116 | content = nob_sv_trim_left(content); 117 | if (content.count > 0) { 118 | TraceLog(LOG_WARNING, "%s:%zu:1: garbage at the end of the file "SV_Fmt" %zu", file_path, row, SV_Arg(content), content.data[0]); 119 | } 120 | 121 | return true; 122 | } 123 | 124 | void plug_reset(void) 125 | { 126 | p->dragged_node = -1; 127 | if (load_curve_from_file(CURVE_FILE_PATH, &p->sb, p->nodes)) { 128 | TraceLog(LOG_INFO, "Loaded curve from %s", CURVE_FILE_PATH); 129 | } 130 | } 131 | 132 | void plug_init(void) 133 | { 134 | p = malloc(sizeof(*p)); 135 | assert(p != NULL); 136 | memset(p, 0, sizeof(*p)); 137 | p->size = sizeof(*p); 138 | 139 | load_assets(); 140 | plug_reset(); 141 | } 142 | 143 | void *plug_pre_reload(void) 144 | { 145 | unload_assets(); 146 | return p; 147 | } 148 | 149 | void plug_post_reload(void *state) 150 | { 151 | p = state; 152 | if (p->size < sizeof(*p)) { 153 | TraceLog(LOG_INFO, "Migrating plug state schema %zu bytes -> %zu bytes", p->size, sizeof(*p)); 154 | p = realloc(p, sizeof(*p)); 155 | p->size = sizeof(*p); 156 | } 157 | 158 | load_assets(); 159 | } 160 | 161 | void plug_update(Env env) 162 | { 163 | Color background_color = ColorFromHSV(0, 0, 0.05); 164 | Color foreground_color = ColorFromHSV(0, 0, 0.95); 165 | 166 | ClearBackground(background_color); 167 | 168 | Camera2D camera = { 169 | .zoom = 0.8, 170 | .target = { 171 | AXIS_LENGTH/2, 172 | -AXIS_LENGTH/2, 173 | }, 174 | .offset = { 175 | .x = env.screen_width/2, 176 | .y = env.screen_height/2, 177 | }, 178 | }; 179 | 180 | BeginMode2D(camera); 181 | { 182 | Vector2 mouse = GetScreenToWorld2D(GetMousePosition(), camera); 183 | 184 | DrawLineEx((Vector2) {0.0, 0.0}, (Vector2) {0.0, -AXIS_LENGTH}, AXIS_THICCNESS, AXIS_COLOR); 185 | DrawLineEx((Vector2) {0.0, 0.0}, (Vector2) {AXIS_LENGTH, 0.0}, AXIS_THICCNESS, AXIS_COLOR); 186 | DrawLineEx(p->nodes[0], p->nodes[1], HANDLE_THICCNESS, HANDLE_COLOR); 187 | DrawLineEx(p->nodes[2], p->nodes[3], HANDLE_THICCNESS, HANDLE_COLOR); 188 | 189 | bool dragging = 0 <= p->dragged_node && (size_t)p->dragged_node < COUNT_NODES; 190 | if (dragging) { 191 | Vector2 *node = &p->nodes[p->dragged_node]; 192 | *node = mouse; 193 | } 194 | 195 | size_t res = 30; 196 | for (size_t i = 0; i <= res; ++i) { 197 | float t = (float)i/res; 198 | DrawCircleV( 199 | cubic_bezier(t, p->nodes), 200 | BEZIER_SAMPLE_RADIUS, 201 | BEZIER_SAMPLE_COLOR); 202 | } 203 | for (size_t i = 0; i < COUNT_NODES; ++i) { 204 | bool hover = CheckCollisionPointCircle(mouse, p->nodes[i], NODE_RADIUS); 205 | DrawCircleV(p->nodes[i], NODE_RADIUS, hover ? NODE_HOVER_COLOR : NODE_COLOR); 206 | const char *label = TextFormat("{%.2f, %.2f}", p->nodes[i].x/AXIS_LENGTH, p->nodes[i].y/AXIS_LENGTH); 207 | Vector2 label_position = Vector2Add(p->nodes[i], (Vector2){NODE_RADIUS, NODE_RADIUS}); 208 | DrawTextEx(p->font, label, label_position, FONT_SIZE, 0, foreground_color); 209 | if (dragging) { 210 | if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { 211 | p->dragged_node = -1; 212 | } 213 | } else { 214 | if (hover && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { 215 | p->dragged_node = i; 216 | } 217 | } 218 | } 219 | 220 | { 221 | float x = Clamp(mouse.x, 0, AXIS_LENGTH); 222 | Vector2 start_pos = { 223 | .x = x, 224 | .y = 0, 225 | }; 226 | Vector2 end_pos = { 227 | .x = x, 228 | .y = -AXIS_LENGTH, 229 | }; 230 | DrawLineEx(start_pos, end_pos, HANDLE_THICCNESS, RED); 231 | float t = cuber_bezier_newton(x, p->nodes, 5); 232 | DrawCircleV(cubic_bezier(t, p->nodes), NODE_RADIUS, PURPLE); 233 | } 234 | 235 | if (IsKeyPressed(KEY_S)) { 236 | if (save_curve_to_file(CURVE_FILE_PATH, &p->sb, p->nodes)) { 237 | TraceLog(LOG_INFO, "Saved curve to %s", CURVE_FILE_PATH); 238 | } 239 | } 240 | } 241 | EndMode2D(); 242 | } 243 | 244 | bool plug_finished(void) 245 | { 246 | return true; 247 | } 248 | 249 | #define ARENA_IMPLEMENTATION 250 | #include "arena.h" 251 | -------------------------------------------------------------------------------- /plugs/c3/future.c3: -------------------------------------------------------------------------------- 1 | module future; 2 | 3 | interface Future { 4 | fn any! poll(any data); 5 | } 6 | 7 | struct FutureDone(Future) { 8 | any result; 9 | } 10 | 11 | fn any! FutureDone.poll(&self, any data) @dynamic { 12 | return self.result; 13 | } 14 | 15 | macro Future done(value) { 16 | return @tclone(FutureDone{@tclone(value)}); 17 | } 18 | 19 | struct FutureReject(Future) { 20 | anyfault excuse; 21 | } 22 | 23 | fn any! FutureReject.poll(&self, any data) @dynamic { 24 | return self.excuse?; 25 | } 26 | 27 | fn Future reject(anyfault excuse) { 28 | FutureReject r = {}; 29 | r.excuse = excuse; 30 | return @tclone(r); 31 | } 32 | 33 | def FutureThenFunction = fn Future(any result); 34 | 35 | struct FutureThen(Future) { 36 | Future left; 37 | Future right; 38 | FutureThenFunction f; 39 | } 40 | 41 | fn any! FutureThen.poll(&self, any data) @dynamic { 42 | if (self.left != null) { 43 | any result = self.left.poll(data)!; 44 | if (result) { 45 | self.right = self.f(result); 46 | self.left = null; 47 | } 48 | return null; 49 | } else { 50 | assert(self.right != null); 51 | return self.right.poll(data); 52 | } 53 | } 54 | 55 | macro Future Future.then(Future left, FutureThenFunction f) { 56 | return @tclone(FutureThen { 57 | .left = left, 58 | .f = f 59 | }); 60 | } 61 | 62 | def FutureCatchFunction = fn Future(anyfault excuse); 63 | 64 | struct FutureCatch(Future) { 65 | Future left; 66 | Future right; 67 | FutureCatchFunction f; 68 | } 69 | 70 | fn any! FutureCatch.poll(&self, any data) @dynamic { 71 | if (self.left != null) { 72 | any! result = self.left.poll(data); 73 | if (catch excuse = result) { 74 | self.right = self.f(excuse); 75 | self.left = null; 76 | return null; 77 | } 78 | return result; 79 | } else { 80 | assert(self.right != null); 81 | return self.right.poll(data); 82 | } 83 | } 84 | 85 | // NOTE: We don't really need to call it @catch (I don't remember what @ does, I think it's an 86 | // inline macro or something?), but since catch is a C3 keyword we decided to call 87 | // @catch. If it causes any problems in the future we should consider a different naming. 88 | macro Future Future.@catch(Future left, FutureCatchFunction f) { 89 | return @tclone(FutureCatch { 90 | .left = left, 91 | .f = f 92 | }); 93 | } 94 | 95 | // TODO: future::start() should be a method of a single future and we should have a 96 | // future combinator that accepts slice of futures. We should call it something 97 | // like future::parallel(). Or maybe even future::collect() which returns you the 98 | // list of future results. 99 | fn void! start(Future[] futures) { 100 | bool quit = false; 101 | while (!quit) { 102 | quit = true; 103 | foreach (future: &futures) { 104 | if (future.poll(null)! == null) { 105 | quit = false; 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /plugs/c3/plug.c3: -------------------------------------------------------------------------------- 1 | import std::io; 2 | import std::math; 3 | import raylib5::rl; 4 | import future; 5 | 6 | // TODO: signature of PlaySoundFunc is incorrect to save time. 7 | def PlaySoundFunc = fn void(); 8 | 9 | const float CYCLE_DURATION = 3.0f; 10 | 11 | struct Env { 12 | float delta_time; 13 | float screen_width; 14 | float screen_height; 15 | bool rendering; 16 | PlaySoundFunc play_sound; 17 | } 18 | 19 | struct Lerp(Future) { 20 | float *place; 21 | float a, b, t; 22 | float duration; 23 | } 24 | 25 | fn any! Lerp.poll(&self, any env) @dynamic { 26 | switch (env) { 27 | case Env: { 28 | if (self.t >= 1) return self; 29 | self.t = (self.duration*self.t + env.delta_time)/self.duration; 30 | *self.place = (self.b - self.a)*self.t; 31 | if (self.t >= 1) return self; 32 | return null; 33 | } 34 | default: unreachable(); 35 | } 36 | } 37 | 38 | fn Future lerp(float *place, float a, float b, float duration) { 39 | return @tclone(Lerp { 40 | .place = place, 41 | .a = a, 42 | .b = b, 43 | .duration = duration, 44 | }); 45 | } 46 | 47 | struct Parallel(Future) { 48 | Future[] futures; 49 | } 50 | 51 | fn any! Parallel.poll(&urmom, any env) @dynamic { 52 | bool finished = true; 53 | foreach (future: &urmom.futures) { 54 | if (future.poll(env)! == null) { 55 | finished = false; 56 | } 57 | } 58 | if (finished) return urmom; 59 | return null; 60 | } 61 | 62 | fn Future parallel(Future[] futures) { 63 | return @tclone(Parallel { 64 | .futures = futures, 65 | }); 66 | } 67 | 68 | struct Seq(Future) { 69 | Future[] futures; 70 | usz index; 71 | } 72 | 73 | fn bool Seq.finished(&urmom) { 74 | return urmom.index >= urmom.futures.len; 75 | } 76 | 77 | fn any! Seq.poll(&urmom, any env) @dynamic { 78 | if (urmom.finished()) return urmom; 79 | if (urmom.futures[urmom.index].poll(env)!) urmom.index += 1; 80 | if (urmom.finished()) return urmom; 81 | return null; 82 | } 83 | 84 | fn Future seq(Future[] futures) { 85 | return @tclone(Seq { 86 | .futures = futures, 87 | }); 88 | } 89 | 90 | struct State { 91 | float t1, t2; 92 | bool finished; 93 | Future anim; 94 | } 95 | 96 | State *state = null; 97 | 98 | fn void reset_anim() 99 | { 100 | // TODO: clean up allocator::temp() here 101 | state.anim = parallel(@tclone(Future[*] { 102 | // TODO: Tuck the whole @tclone(Future[*]{ ... }) under the future constructors 103 | // See if variadic args can be applied here 104 | seq(@tclone(Future[*] { 105 | lerp(&state.t1, 0, 1, CYCLE_DURATION), 106 | lerp(&state.t1, 1, 0, CYCLE_DURATION/4), 107 | })), 108 | seq(@tclone(Future[*] { 109 | lerp(&state.t2, 0, 1, CYCLE_DURATION + CYCLE_DURATION/4), 110 | })) 111 | })); 112 | } 113 | 114 | fn void plug_init() @export("plug_init") 115 | { 116 | state = mem::new(State); 117 | reset_anim(); 118 | } 119 | 120 | fn void* plug_pre_reload() @export("plug_pre_reload") 121 | { 122 | return state; 123 | } 124 | 125 | fn void plug_post_reload(void *old_state) @export("plug_post_reload") 126 | { 127 | state = old_state; 128 | } 129 | 130 | fn void orbit_circle(Env env, float t, float radius, float orbit, Color color) 131 | { 132 | float angle = 2*math::PI*t; 133 | float cx = env.screen_width*0.5; 134 | float cy = env.screen_height*0.5; 135 | float px = cx + math::cos(angle)*orbit; 136 | float py = cy + math::sin(angle)*orbit; 137 | rl::drawCircleV({px, py}, radius, color); 138 | } 139 | 140 | fn void plug_update(Env env) @export("plug_update") 141 | { 142 | state.finished = state.anim.poll(&env)!! != null; 143 | rl::clearBackground({0x18, 0x18, 0x18, 0xFF}); 144 | 145 | float radius = env.screen_width*0.04; 146 | float orbit = env.screen_width*0.25; 147 | Color color = rl::BLUE; 148 | orbit_circle(env, state.t1, radius, orbit, color); 149 | 150 | radius = env.screen_width*0.02; 151 | orbit = env.screen_width*0.10; 152 | color = rl::RED; 153 | orbit_circle(env, state.t2, radius, orbit, color); 154 | } 155 | 156 | fn void plug_reset() @export("plug_reset") 157 | { 158 | state.finished = false; 159 | reset_anim(); 160 | } 161 | 162 | fn bool plug_finished() @export("plug_finished") 163 | { 164 | return state.finished; 165 | } 166 | -------------------------------------------------------------------------------- /plugs/cpp/interpolators.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERPOLATORS_H_ 2 | #define INTERPOLATORS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef enum { 9 | FUNC_ID, 10 | FUNC_SINSTEP, 11 | FUNC_SMOOTHSTEP, 12 | FUNC_SQR, 13 | FUNC_SQRT, 14 | FUNC_SINPULSE, 15 | } Interp_Func; 16 | 17 | static inline float smoothstep(float x) 18 | { 19 | if (x < 0.0) return 0.0; 20 | if (x >= 1.0) return 1.0; 21 | return 3*x*x - 2*x*x*x; 22 | } 23 | 24 | static inline float sinstep(float t) 25 | { 26 | if (t < 0.0) return 0.0; 27 | if (t >= 1.0) return 1.0; 28 | return (sinf(PI*t - PI*0.5) + 1)*0.5; 29 | } 30 | 31 | static inline float sinpulse(float t) 32 | { 33 | if (t < 0.0) return 0.0; 34 | if (t >= 1.0) return 0.0; 35 | return sinf(PI*t); 36 | } 37 | 38 | static inline Vector2 cubic_bezier(float t, Vector2 nodes[4]) 39 | { 40 | float it = 1 - t; 41 | Vector2 b = Vector2Scale(nodes[0], it*it*it); 42 | b = Vector2Add(b, Vector2Scale(nodes[1], 3*it*it*t)); 43 | b = Vector2Add(b, Vector2Scale(nodes[2], 3*it*t*t)); 44 | b = Vector2Add(b, Vector2Scale(nodes[3], t*t*t)); 45 | return b; 46 | } 47 | 48 | static inline Vector2 cubic_bezier_der(float t, Vector2 nodes[4]) 49 | { 50 | float it = 1 - t; 51 | Vector2 b = Vector2Scale(nodes[0], -3*it*it); 52 | b = Vector2Add(b, Vector2Scale(nodes[1], 3*it*it)); 53 | b = Vector2Add(b, Vector2Scale(nodes[1], -6*it*t)); 54 | b = Vector2Add(b, Vector2Scale(nodes[2], 6*it*t)); 55 | b = Vector2Add(b, Vector2Scale(nodes[2], -3*t*t)); 56 | b = Vector2Add(b, Vector2Scale(nodes[3], 3*t*t)); 57 | return b; 58 | } 59 | 60 | static inline float cuber_bezier_newton(float x, Vector2 nodes[4], size_t n) 61 | { 62 | float t = 0; 63 | for (size_t i = 0; i < n; ++i) { 64 | t = t - (cubic_bezier(t, nodes).x - x)/cubic_bezier_der(t, nodes).x; 65 | } 66 | return t; 67 | } 68 | 69 | static inline float interp_func(Interp_Func func, float t) 70 | { 71 | switch (func) { 72 | case FUNC_ID: return t; 73 | case FUNC_SQR: return t*t; 74 | case FUNC_SQRT: return sqrtf(t); 75 | case FUNC_SINSTEP: return sinstep(t); 76 | case FUNC_SMOOTHSTEP: return smoothstep(t); 77 | case FUNC_SINPULSE: return sinpulse(t); 78 | } 79 | assert(0 && "UNREACHABLE"); 80 | return 0.0f; 81 | } 82 | 83 | #endif // INTERPOLATORS_H_ 84 | -------------------------------------------------------------------------------- /plugs/cpp/plug.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include "env.h" 11 | #include "interpolators.h" 12 | #include "plug.h" 13 | 14 | #define FONT_SIZE 68 15 | 16 | class Task { 17 | public: 18 | virtual ~Task() = default; 19 | virtual bool update(Env env) = 0; 20 | }; 21 | 22 | class Seq: public Task { 23 | public: 24 | Seq(std::initializer_list tasks): 25 | it(0), 26 | tasks(tasks) 27 | {} 28 | 29 | virtual ~Seq() override { 30 | for (auto task: tasks) { 31 | delete task; 32 | } 33 | } 34 | 35 | bool done() const 36 | { 37 | return it >= tasks.size(); 38 | } 39 | 40 | virtual bool update(Env env) override 41 | { 42 | if (done()) return true; 43 | if (tasks[it]->update(env)) it += 1; 44 | return done(); 45 | } 46 | 47 | protected: 48 | size_t it; 49 | std::vector tasks; 50 | }; 51 | 52 | class Wait: public Task { 53 | public: 54 | Wait(float duration): 55 | started(false), 56 | cursor(0.0f), 57 | duration(duration) 58 | {} 59 | 60 | bool done() const 61 | { 62 | return cursor > duration; 63 | } 64 | 65 | float interp() const 66 | { 67 | if (duration <= 0.0f) { 68 | return 0.0f; 69 | } else { 70 | return cursor/duration; 71 | } 72 | } 73 | 74 | virtual bool update(Env env) override 75 | { 76 | if (done()) return true; 77 | if (!started) started = true; 78 | cursor += env.delta_time; 79 | return done(); 80 | } 81 | 82 | protected: 83 | bool started; 84 | float cursor; 85 | float duration; 86 | }; 87 | 88 | class Move_Vec2: public Wait { 89 | public: 90 | Move_Vec2(Vector2 *place, Vector2 target, float duration): 91 | Wait(duration), 92 | place(place), 93 | start(Vector2()), 94 | target(target) 95 | {} 96 | 97 | virtual bool update(Env env) override 98 | { 99 | if (!started) start = *place; 100 | bool finished = Wait::update(env); 101 | *place = Vector2Lerp(start, target, smoothstep(interp())); 102 | return finished; 103 | } 104 | 105 | private: 106 | Vector2 *place; 107 | Vector2 start, target; 108 | }; 109 | 110 | typedef struct { 111 | size_t size; 112 | Font font; 113 | Task *task; 114 | bool finished; 115 | Vector2 position; 116 | } Plug; 117 | 118 | static Plug *p; 119 | 120 | static void load_assets(void) 121 | { 122 | p->font = LoadFontEx("./assets/fonts/Vollkorn-Regular.ttf", FONT_SIZE, NULL, 0); 123 | } 124 | 125 | static void unload_assets(void) 126 | { 127 | UnloadFont(p->font); 128 | } 129 | 130 | extern "C" { 131 | 132 | #define PLUG(name, ret, ...) ret name(__VA_ARGS__); 133 | LIST_OF_PLUGS 134 | #undef PLUG 135 | 136 | void plug_reset(void) 137 | { 138 | p->finished = false; 139 | if (p->task) delete p->task; 140 | p->task = new Seq { 141 | new Move_Vec2(&p->position, {200.0, 200.0}, 0.5f), 142 | new Move_Vec2(&p->position, {200.0, 0.0}, 0.5f), 143 | new Move_Vec2(&p->position, {0.0, 200.0}, 0.5f), 144 | new Move_Vec2(&p->position, {0.0, 0.0}, 0.5f) 145 | }; 146 | p->position = {0, 0}; 147 | } 148 | 149 | void plug_init(void) 150 | { 151 | p = (Plug*)malloc(sizeof(*p)); 152 | assert(p != NULL); 153 | memset(p, 0, sizeof(*p)); 154 | p->size = sizeof(*p); 155 | 156 | load_assets(); 157 | plug_reset(); 158 | } 159 | 160 | void *plug_pre_reload(void) 161 | { 162 | unload_assets(); 163 | return p; 164 | } 165 | 166 | void plug_post_reload(void *state) 167 | { 168 | p = (Plug*)state; 169 | if (p->size < sizeof(*p)) { 170 | TraceLog(LOG_INFO, "Migrating plug state schema %zu bytes -> %zu bytes", p->size, sizeof(*p)); 171 | p = (Plug*)realloc(p, sizeof(*p)); 172 | p->size = sizeof(*p); 173 | } 174 | 175 | load_assets(); 176 | } 177 | 178 | void plug_update(Env env) 179 | { 180 | p->finished = p->task->update(env); 181 | 182 | Color background_color = ColorFromHSV(0, 0, 0.05); 183 | Color foreground_color = ColorFromHSV(0, 0, 0.95); 184 | 185 | ClearBackground(background_color); 186 | 187 | const char *text = "Hello from C++"; 188 | DrawTextEx(p->font, text, p->position, FONT_SIZE, 0, foreground_color); 189 | } 190 | 191 | bool plug_finished(void) 192 | { 193 | return p->finished; 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /plugs/squares/arena.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Alexey Kutepov 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | #ifndef ARENA_H_ 23 | #define ARENA_H_ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #ifndef ARENA_NOSTDIO 30 | #include 31 | #include 32 | #endif // ARENA_NOSTDIO 33 | 34 | #ifndef ARENA_ASSERT 35 | #include 36 | #define ARENA_ASSERT assert 37 | #endif 38 | 39 | #define ARENA_BACKEND_LIBC_MALLOC 0 40 | #define ARENA_BACKEND_LINUX_MMAP 1 41 | #define ARENA_BACKEND_WIN32_VIRTUALALLOC 2 42 | #define ARENA_BACKEND_WASM_HEAPBASE 3 43 | 44 | #ifndef ARENA_BACKEND 45 | #define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC 46 | #endif // ARENA_BACKEND 47 | 48 | typedef struct Region Region; 49 | 50 | struct Region { 51 | Region *next; 52 | size_t count; 53 | size_t capacity; 54 | uintptr_t data[]; 55 | }; 56 | 57 | typedef struct { 58 | Region *begin, *end; 59 | } Arena; 60 | 61 | #define REGION_DEFAULT_CAPACITY (8*1024) 62 | 63 | Region *new_region(size_t capacity); 64 | void free_region(Region *r); 65 | 66 | // TODO: snapshot/rewind capability for the arena 67 | // - Snapshot should be combination of a->end and a->end->count. 68 | // - Rewinding should be restoring a->end and a->end->count from the snapshot and 69 | // setting count-s of all the Region-s after the remembered a->end to 0. 70 | void *arena_alloc(Arena *a, size_t size_bytes); 71 | void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz); 72 | char *arena_strdup(Arena *a, const char *cstr); 73 | void *arena_memdup(Arena *a, void *data, size_t size); 74 | #ifndef ARENA_NOSTDIO 75 | char *arena_sprintf(Arena *a, const char *format, ...); 76 | #endif // ARENA_NOSTDIO 77 | 78 | void arena_reset(Arena *a); 79 | void arena_free(Arena *a); 80 | 81 | #define ARENA_DA_INIT_CAP 256 82 | 83 | #ifdef __cplusplus 84 | #define cast_ptr(ptr) (decltype(ptr)) 85 | #else 86 | #define cast_ptr(...) 87 | #endif 88 | 89 | #define arena_da_append(a, da, item) \ 90 | do { \ 91 | if ((da)->count >= (da)->capacity) { \ 92 | size_t new_capacity = (da)->capacity == 0 ? ARENA_DA_INIT_CAP : (da)->capacity*2; \ 93 | (da)->items = cast_ptr((da)->items)arena_realloc( \ 94 | (a), (da)->items, \ 95 | (da)->capacity*sizeof(*(da)->items), \ 96 | new_capacity*sizeof(*(da)->items)); \ 97 | (da)->capacity = new_capacity; \ 98 | } \ 99 | \ 100 | (da)->items[(da)->count++] = (item); \ 101 | } while (0) 102 | 103 | #endif // ARENA_H_ 104 | 105 | #ifdef ARENA_IMPLEMENTATION 106 | 107 | #if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC 108 | #include 109 | 110 | // TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region 111 | // It should be up to new_region() to decide the actual capacity to allocate 112 | Region *new_region(size_t capacity) 113 | { 114 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity; 115 | // TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned 116 | Region *r = (Region*)malloc(size_bytes); 117 | ARENA_ASSERT(r); 118 | r->next = NULL; 119 | r->count = 0; 120 | r->capacity = capacity; 121 | return r; 122 | } 123 | 124 | void free_region(Region *r) 125 | { 126 | free(r); 127 | } 128 | #elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP 129 | #include 130 | #include 131 | 132 | Region *new_region(size_t capacity) 133 | { 134 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; 135 | Region *r = mmap(NULL, size_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 136 | ARENA_ASSERT(r != MAP_FAILED); 137 | r->next = NULL; 138 | r->count = 0; 139 | r->capacity = capacity; 140 | return r; 141 | } 142 | 143 | void free_region(Region *r) 144 | { 145 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * r->capacity; 146 | int ret = munmap(r, size_bytes); 147 | ARENA_ASSERT(ret == 0); 148 | } 149 | 150 | #elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC 151 | 152 | #if !defined(_WIN32) 153 | # error "Current platform is not Windows" 154 | #endif 155 | 156 | #define WIN32_LEAN_AND_MEAN 157 | #include 158 | 159 | #define INV_HANDLE(x) (((x) == NULL) || ((x) == INVALID_HANDLE_VALUE)) 160 | 161 | Region *new_region(size_t capacity) 162 | { 163 | SIZE_T size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; 164 | Region *r = VirtualAllocEx( 165 | GetCurrentProcess(), /* Allocate in current process address space */ 166 | NULL, /* Unknown position */ 167 | size_bytes, /* Bytes to allocate */ 168 | MEM_COMMIT | MEM_RESERVE, /* Reserve and commit allocated page */ 169 | PAGE_READWRITE /* Permissions ( Read/Write )*/ 170 | ); 171 | if (INV_HANDLE(r)) 172 | ARENA_ASSERT(0 && "VirtualAllocEx() failed."); 173 | 174 | r->next = NULL; 175 | r->count = 0; 176 | r->capacity = capacity; 177 | return r; 178 | } 179 | 180 | void free_region(Region *r) 181 | { 182 | if (INV_HANDLE(r)) 183 | return; 184 | 185 | BOOL free_result = VirtualFreeEx( 186 | GetCurrentProcess(), /* Deallocate from current process address space */ 187 | (LPVOID)r, /* Address to deallocate */ 188 | 0, /* Bytes to deallocate ( Unknown, deallocate entire page ) */ 189 | MEM_RELEASE /* Release the page ( And implicitly decommit it ) */ 190 | ); 191 | 192 | if (FALSE == free_result) 193 | ARENA_ASSERT(0 && "VirtualFreeEx() failed."); 194 | } 195 | 196 | #elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE 197 | # error "TODO: WASM __heap_base backend is not implemented yet" 198 | #else 199 | # error "Unknown Arena backend" 200 | #endif 201 | 202 | // TODO: add debug statistic collection mode for arena 203 | // Should collect things like: 204 | // - How many times new_region was called 205 | // - How many times existing region was skipped 206 | // - How many times allocation exceeded REGION_DEFAULT_CAPACITY 207 | 208 | void *arena_alloc(Arena *a, size_t size_bytes) 209 | { 210 | size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t); 211 | 212 | if (a->end == NULL) { 213 | ARENA_ASSERT(a->begin == NULL); 214 | size_t capacity = REGION_DEFAULT_CAPACITY; 215 | if (capacity < size) capacity = size; 216 | a->end = new_region(capacity); 217 | a->begin = a->end; 218 | } 219 | 220 | while (a->end->count + size > a->end->capacity && a->end->next != NULL) { 221 | a->end = a->end->next; 222 | } 223 | 224 | if (a->end->count + size > a->end->capacity) { 225 | ARENA_ASSERT(a->end->next == NULL); 226 | size_t capacity = REGION_DEFAULT_CAPACITY; 227 | if (capacity < size) capacity = size; 228 | a->end->next = new_region(capacity); 229 | a->end = a->end->next; 230 | } 231 | 232 | void *result = &a->end->data[a->end->count]; 233 | a->end->count += size; 234 | return result; 235 | } 236 | 237 | void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz) 238 | { 239 | if (newsz <= oldsz) return oldptr; 240 | void *newptr = arena_alloc(a, newsz); 241 | char *newptr_char = (char*)newptr; 242 | char *oldptr_char = (char*)oldptr; 243 | for (size_t i = 0; i < oldsz; ++i) { 244 | newptr_char[i] = oldptr_char[i]; 245 | } 246 | return newptr; 247 | } 248 | 249 | char *arena_strdup(Arena *a, const char *cstr) 250 | { 251 | size_t n = strlen(cstr); 252 | char *dup = (char*)arena_alloc(a, n + 1); 253 | memcpy(dup, cstr, n); 254 | dup[n] = '\0'; 255 | return dup; 256 | } 257 | 258 | void *arena_memdup(Arena *a, void *data, size_t size) 259 | { 260 | return memcpy(arena_alloc(a, size), data, size); 261 | } 262 | 263 | #ifndef ARENA_NOSTDIO 264 | char *arena_sprintf(Arena *a, const char *format, ...) 265 | { 266 | va_list args; 267 | va_start(args, format); 268 | int n = vsnprintf(NULL, 0, format, args); 269 | va_end(args); 270 | 271 | ARENA_ASSERT(n >= 0); 272 | char *result = (char*)arena_alloc(a, n + 1); 273 | va_start(args, format); 274 | vsnprintf(result, n + 1, format, args); 275 | va_end(args); 276 | 277 | return result; 278 | } 279 | #endif // ARENA_NOSTDIO 280 | 281 | void arena_reset(Arena *a) 282 | { 283 | for (Region *r = a->begin; r != NULL; r = r->next) { 284 | r->count = 0; 285 | } 286 | 287 | a->end = a->begin; 288 | } 289 | 290 | void arena_free(Arena *a) 291 | { 292 | Region *r = a->begin; 293 | while (r) { 294 | Region *r0 = r; 295 | r = r->next; 296 | free_region(r0); 297 | } 298 | a->begin = NULL; 299 | a->end = NULL; 300 | } 301 | 302 | #endif // ARENA_IMPLEMENTATION 303 | -------------------------------------------------------------------------------- /plugs/squares/interpolators.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERPOLATORS_H_ 2 | #define INTERPOLATORS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef enum { 9 | FUNC_ID, 10 | FUNC_SINSTEP, 11 | FUNC_SMOOTHSTEP, 12 | FUNC_SQR, 13 | FUNC_SQRT, 14 | FUNC_SINPULSE, 15 | } Interp_Func; 16 | 17 | static inline float smoothstep(float x) 18 | { 19 | if (x < 0.0) return 0.0; 20 | if (x >= 1.0) return 1.0; 21 | return 3*x*x - 2*x*x*x; 22 | } 23 | 24 | static inline float sinstep(float t) 25 | { 26 | if (t < 0.0) return 0.0; 27 | if (t >= 1.0) return 1.0; 28 | return (sinf(PI*t - PI*0.5) + 1)*0.5; 29 | } 30 | 31 | static inline float sinpulse(float t) 32 | { 33 | if (t < 0.0) return 0.0; 34 | if (t >= 1.0) return 0.0; 35 | return sinf(PI*t); 36 | } 37 | 38 | static inline Vector2 cubic_bezier(float t, Vector2 nodes[4]) 39 | { 40 | float it = 1 - t; 41 | Vector2 b = Vector2Scale(nodes[0], it*it*it); 42 | b = Vector2Add(b, Vector2Scale(nodes[1], 3*it*it*t)); 43 | b = Vector2Add(b, Vector2Scale(nodes[2], 3*it*t*t)); 44 | b = Vector2Add(b, Vector2Scale(nodes[3], t*t*t)); 45 | return b; 46 | } 47 | 48 | static inline Vector2 cubic_bezier_der(float t, Vector2 nodes[4]) 49 | { 50 | float it = 1 - t; 51 | Vector2 b = Vector2Scale(nodes[0], -3*it*it); 52 | b = Vector2Add(b, Vector2Scale(nodes[1], 3*it*it)); 53 | b = Vector2Add(b, Vector2Scale(nodes[1], -6*it*t)); 54 | b = Vector2Add(b, Vector2Scale(nodes[2], 6*it*t)); 55 | b = Vector2Add(b, Vector2Scale(nodes[2], -3*t*t)); 56 | b = Vector2Add(b, Vector2Scale(nodes[3], 3*t*t)); 57 | return b; 58 | } 59 | 60 | static inline float cuber_bezier_newton(float x, Vector2 nodes[4], size_t n) 61 | { 62 | float t = 0; 63 | for (size_t i = 0; i < n; ++i) { 64 | t = t - (cubic_bezier(t, nodes).x - x)/cubic_bezier_der(t, nodes).x; 65 | } 66 | return t; 67 | } 68 | 69 | static inline float interp_func(Interp_Func func, float t) 70 | { 71 | switch (func) { 72 | case FUNC_ID: return t; 73 | case FUNC_SQR: return t*t; 74 | case FUNC_SQRT: return sqrtf(t); 75 | case FUNC_SINSTEP: return sinstep(t); 76 | case FUNC_SMOOTHSTEP: return smoothstep(t); 77 | case FUNC_SINPULSE: return sinpulse(t); 78 | } 79 | assert(0 && "UNREACHABLE"); 80 | return 0.0f; 81 | } 82 | 83 | #endif // INTERPOLATORS_H_ 84 | -------------------------------------------------------------------------------- /plugs/squares/plug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include "env.h" 9 | #include "nob.h" 10 | #include "tasks.h" 11 | #include "plug.h" 12 | 13 | #define PLUG(name, ret, ...) ret name(__VA_ARGS__); 14 | LIST_OF_PLUGS 15 | #undef PLUG 16 | 17 | #define FONT_SIZE 68 18 | 19 | #define SQUARES_COUNT 3 20 | #define SQUARE_SIZE 100.0f 21 | #define SQUARE_PAD (SQUARE_SIZE*0.2f) 22 | #define SQUARE_MOVE_DURATION 0.25 23 | #define SQUARE_COLOR_DURATION 0.25 24 | #define BACKGROUND_COLOR ColorFromHSV(0, 0, 0.05) 25 | #define FOREGROUND_COLOR ColorFromHSV(0, 0, 0.95) 26 | 27 | typedef struct { 28 | Vector2 position; 29 | Vector4 color; 30 | } Square; 31 | 32 | typedef struct { 33 | size_t size; 34 | Font font; 35 | Arena state_arena; 36 | Arena asset_arena; 37 | Square squares[SQUARES_COUNT]; 38 | Task task; 39 | bool finished; 40 | } Plug; 41 | 42 | static Plug *p = NULL; 43 | 44 | Vector2 grid(size_t row, size_t col) 45 | { 46 | Vector2 world; 47 | world.x = col*(SQUARE_SIZE + SQUARE_PAD); 48 | world.y = row*(SQUARE_SIZE + SQUARE_PAD); 49 | return world; 50 | } 51 | 52 | static void load_assets(void) 53 | { 54 | p->font = LoadFontEx("./assets/fonts/Vollkorn-Regular.ttf", FONT_SIZE, NULL, 0); 55 | Arena *a = &p->asset_arena; 56 | arena_reset(a); 57 | task_vtable_rebuild(a); 58 | } 59 | 60 | static void unload_assets(void) 61 | { 62 | UnloadFont(p->font); 63 | } 64 | 65 | Task shuffle_squares(Arena *a, Square *s1, Square *s2, Square *s3) 66 | { 67 | Interp_Func func = FUNC_SMOOTHSTEP; 68 | return task_seq(a, 69 | task_group(a, 70 | task_move_vec2(a, &s1->position, grid(1, 1), 0.25, func), 71 | task_move_vec2(a, &s2->position, grid(0, 0), 0.25, func), 72 | task_move_vec2(a, &s3->position, grid(0, 1), 0.25, func)), 73 | task_group(a, 74 | task_move_vec4(a, &s1->color, ColorNormalize(RED), 0.25, func), 75 | task_move_vec4(a, &s2->color, ColorNormalize(GREEN), 0.25, func), 76 | task_move_vec4(a, &s3->color, ColorNormalize(BLUE), 0.25, func)), 77 | task_move_vec2(a, &s1->position, grid(1, 0), 0.25, func), 78 | task_group(a, 79 | task_move_vec4(a, &s1->color, ColorNormalize(FOREGROUND_COLOR), 0.25, func), 80 | task_move_vec4(a, &s2->color, ColorNormalize(FOREGROUND_COLOR), 0.25, func), 81 | task_move_vec4(a, &s3->color, ColorNormalize(FOREGROUND_COLOR), 0.25, func))); 82 | } 83 | 84 | Task loading(Arena *a) 85 | { 86 | Square *s1 = &p->squares[0]; 87 | Square *s2 = &p->squares[1]; 88 | Square *s3 = &p->squares[2]; 89 | return task_seq(a, 90 | shuffle_squares(a, s1, s2, s3), 91 | shuffle_squares(a, s2, s3, s1), 92 | shuffle_squares(a, s3, s1, s2), 93 | task_wait(a, 1.0f) 94 | ); 95 | } 96 | 97 | void plug_reset(void) 98 | { 99 | for (size_t i = 0; i < SQUARES_COUNT; ++i) { 100 | p->squares[i].position = grid(i/2, i%2); 101 | p->squares[i].color = ColorNormalize(FOREGROUND_COLOR); 102 | } 103 | p->finished = false; 104 | arena_reset(&p->state_arena); 105 | 106 | Arena *a = &p->state_arena; 107 | p->task = loading(a); 108 | } 109 | 110 | void plug_init(void) 111 | { 112 | p = malloc(sizeof(*p)); 113 | assert(p != NULL); 114 | memset(p, 0, sizeof(*p)); 115 | p->size = sizeof(*p); 116 | 117 | load_assets(); 118 | plug_reset(); 119 | } 120 | 121 | void *plug_pre_reload(void) 122 | { 123 | unload_assets(); 124 | return p; 125 | } 126 | 127 | void plug_post_reload(void *state) 128 | { 129 | p = state; 130 | if (p->size < sizeof(*p)) { 131 | TraceLog(LOG_INFO, "Migrating plug state schema %zu bytes -> %zu bytes", p->size, sizeof(*p)); 132 | p = realloc(p, sizeof(*p)); 133 | p->size = sizeof(*p); 134 | } 135 | 136 | load_assets(); 137 | } 138 | 139 | void plug_update(Env env) 140 | { 141 | p->finished = task_update(p->task, env); 142 | 143 | ClearBackground(BACKGROUND_COLOR); 144 | 145 | Camera2D camera = {0}; 146 | camera.zoom = 1.0f; 147 | camera.target = CLITERAL(Vector2) { 148 | -env.screen_width/2 + SQUARE_SIZE + SQUARE_PAD*0.5, 149 | -env.screen_height/2 + SQUARE_SIZE + SQUARE_PAD*0.5, 150 | }; 151 | BeginMode2D(camera); 152 | for (size_t i = 0; i < SQUARES_COUNT; ++i) { 153 | Rectangle boundary = { 154 | .x = p->squares[i].position.x, 155 | .y = p->squares[i].position.y, 156 | .width = SQUARE_SIZE, 157 | .height = SQUARE_SIZE, 158 | }; 159 | DrawRectangleRec(boundary, ColorFromNormalized(p->squares[i].color)); 160 | } 161 | EndMode2D(); 162 | } 163 | 164 | bool plug_finished(void) 165 | { 166 | return p->finished; 167 | } 168 | 169 | #define ARENA_IMPLEMENTATION 170 | #include "arena.h" 171 | #include "tasks.c" 172 | -------------------------------------------------------------------------------- /plugs/squares/tasks.c: -------------------------------------------------------------------------------- 1 | #include "tasks.h" 2 | #include "interpolators.h" 3 | 4 | #include "raymath.h" 5 | 6 | Task_VTable task_vtable = {0}; 7 | Tag TASK_MOVE_SCALAR_TAG = 0; 8 | Tag TASK_MOVE_VEC2_TAG = 0; 9 | Tag TASK_MOVE_VEC4_TAG = 0; 10 | Tag TASK_SEQ_TAG = 0; 11 | Tag TASK_GROUP_TAG = 0; 12 | Tag TASK_WAIT_TAG = 0; 13 | 14 | bool task_update(Task task, Env env) 15 | { 16 | return task_vtable.items[task.tag].update(task.data, env); 17 | } 18 | 19 | Tag task_vtable_register(Arena *a, Task_Funcs funcs) 20 | { 21 | Tag tag = task_vtable.count; 22 | arena_da_append(a, &task_vtable, funcs); 23 | return tag; 24 | } 25 | 26 | void task_vtable_rebuild(Arena *a) 27 | { 28 | memset(&task_vtable, 0, sizeof(task_vtable)); 29 | 30 | TASK_WAIT_TAG = task_vtable_register(a, (Task_Funcs) { 31 | .update = (task_update_data_t)wait_update, 32 | }); 33 | TASK_MOVE_SCALAR_TAG = task_vtable_register(a, (Task_Funcs) { 34 | .update = (task_update_data_t)move_scalar_update, 35 | }); 36 | TASK_MOVE_VEC2_TAG = task_vtable_register(a, (Task_Funcs) { 37 | .update = (task_update_data_t)move_vec2_update, 38 | }); 39 | TASK_MOVE_VEC4_TAG = task_vtable_register(a, (Task_Funcs) { 40 | .update = (task_update_data_t)move_vec4_update, 41 | }); 42 | TASK_SEQ_TAG = task_vtable_register(a, (Task_Funcs) { 43 | .update = (task_update_data_t)seq_update, 44 | }); 45 | TASK_GROUP_TAG = task_vtable_register(a, (Task_Funcs) { 46 | .update = (task_update_data_t)group_update, 47 | }); 48 | } 49 | 50 | bool wait_done(Wait_Data *data) 51 | { 52 | return data->cursor >= data->duration; 53 | } 54 | 55 | float wait_interp(Wait_Data *data) 56 | { 57 | float t = 0.0f; 58 | if (data->duration > 0) { 59 | t = data->cursor/data->duration; 60 | } 61 | return t; 62 | } 63 | 64 | bool wait_update(Wait_Data *data, Env env) 65 | { 66 | if (wait_done(data)) return true; 67 | if (!data->started) data->started = true; 68 | data->cursor += env.delta_time; 69 | return wait_done(data); 70 | } 71 | 72 | Wait_Data wait_data(float duration) 73 | { 74 | return (Wait_Data) { .duration = duration }; 75 | } 76 | 77 | Task task_wait(Arena *a, float duration) 78 | { 79 | Wait_Data data = wait_data(duration); 80 | return (Task) { 81 | .tag = TASK_WAIT_TAG, 82 | .data = arena_memdup(a, &data, sizeof(data)), 83 | }; 84 | } 85 | 86 | bool move_scalar_update(Move_Scalar_Data *data, Env env) 87 | { 88 | if (wait_done(&data->wait)) return true; 89 | 90 | if (!data->wait.started && data->value) { 91 | data->start = *data->value; 92 | } 93 | 94 | bool finished = wait_update(&data->wait, env); 95 | 96 | if (data->value) { 97 | *data->value = Lerp( 98 | data->start, 99 | data->target, 100 | interp_func(data->func, wait_interp(&data->wait))); 101 | } 102 | 103 | return finished; 104 | } 105 | 106 | Move_Scalar_Data move_scalar_data(float *value, float target, float duration, Interp_Func func) 107 | { 108 | return (Move_Scalar_Data) { 109 | .wait = wait_data(duration), 110 | .value = value, 111 | .target = target, 112 | .func = func, 113 | }; 114 | } 115 | 116 | Task task_move_scalar(Arena *a, float *value, float target, float duration, Interp_Func func) 117 | { 118 | Move_Scalar_Data data = move_scalar_data(value, target, duration, func); 119 | return (Task) { 120 | .tag = TASK_MOVE_SCALAR_TAG, 121 | .data = arena_memdup(a, &data, sizeof(data)), 122 | }; 123 | } 124 | 125 | bool move_vec2_update(Move_Vec2_Data *data, Env env) 126 | { 127 | if (wait_done(&data->wait)) return true; 128 | 129 | if (!data->wait.started && data->value) { 130 | data->start = *data->value; 131 | } 132 | 133 | bool finished = wait_update(&data->wait, env); 134 | 135 | if (data->value) { 136 | *data->value = Vector2Lerp( 137 | data->start, 138 | data->target, 139 | interp_func(data->func, wait_interp(&data->wait))); 140 | } 141 | return finished; 142 | } 143 | 144 | Move_Vec2_Data move_vec2_data(Vector2 *value, Vector2 target, float duration, Interp_Func func) 145 | { 146 | return (Move_Vec2_Data) { 147 | .wait = wait_data(duration), 148 | .value = value, 149 | .target = target, 150 | .func = func, 151 | }; 152 | } 153 | 154 | Task task_move_vec2(Arena *a, Vector2 *value, Vector2 target, float duration, Interp_Func func) 155 | { 156 | Move_Vec2_Data data = move_vec2_data(value, target, duration, func); 157 | return (Task) { 158 | .tag = TASK_MOVE_VEC2_TAG, 159 | .data = arena_memdup(a, &data, sizeof(data)), 160 | }; 161 | } 162 | 163 | bool move_vec4_update(Move_Vec4_Data *data, Env env) 164 | { 165 | if (wait_done(&data->wait)) return true; 166 | 167 | if (!data->wait.started && data->value) { 168 | data->start = *data->value; 169 | } 170 | 171 | bool finished = wait_update(&data->wait, env); 172 | 173 | if (data->value) { 174 | *data->value = QuaternionLerp( 175 | data->start, 176 | data->target, 177 | interp_func(data->func, wait_interp(&data->wait))); 178 | } 179 | 180 | return finished; 181 | } 182 | 183 | Move_Vec4_Data move_vec4_data(Vector4 *value, Vector4 target, float duration, Interp_Func func) 184 | { 185 | return (Move_Vec4_Data) { 186 | .wait = wait_data(duration), 187 | .value = value, 188 | .target = target, 189 | .func = func, 190 | }; 191 | } 192 | 193 | Task task_move_vec4(Arena *a, Vector4 *value, Vector4 target, float duration, Interp_Func func) 194 | { 195 | Move_Vec4_Data data = move_vec4_data(value, target, duration, func); 196 | return (Task) { 197 | .tag = TASK_MOVE_VEC4_TAG, 198 | .data = arena_memdup(a, &data, sizeof(data)), 199 | }; 200 | } 201 | 202 | bool group_update(Group_Data *data, Env env) 203 | { 204 | bool finished = true; 205 | for (size_t i = 0; i < data->tasks.count; ++i) { 206 | Task it = data->tasks.items[i]; 207 | if (!task_update(it, env)) { 208 | finished = false; 209 | } 210 | } 211 | return finished; 212 | } 213 | 214 | Task task_group_(Arena *a, ...) 215 | { 216 | Group_Data *data = (Group_Data*)arena_alloc(a, sizeof(*data)); 217 | memset(data, 0, sizeof(*data)); 218 | 219 | va_list args; 220 | va_start(args, a); 221 | for (;;) { 222 | Task task = va_arg(args, Task); 223 | if (task.data == NULL) break; 224 | arena_da_append(a, &data->tasks, task); 225 | } 226 | va_end(args); 227 | 228 | return (Task) { 229 | .tag = TASK_GROUP_TAG, 230 | .data = data, 231 | }; 232 | } 233 | 234 | bool seq_update(Seq_Data *data, Env env) 235 | { 236 | if (data->it >= data->tasks.count) return true; 237 | 238 | Task it = data->tasks.items[data->it]; 239 | if (task_update(it, env)) { 240 | data->it += 1; 241 | } 242 | 243 | return data->it >= data->tasks.count; 244 | } 245 | 246 | Task task_seq_(Arena *a, ...) 247 | { 248 | Seq_Data *data = (Seq_Data*)arena_alloc(a, sizeof(*data)); 249 | memset(data, 0, sizeof(*data)); 250 | 251 | va_list args; 252 | va_start(args, a); 253 | for (;;) { 254 | Task task = va_arg(args, Task); 255 | if (task.data == NULL) break; 256 | arena_da_append(a, &data->tasks, task); 257 | } 258 | va_end(args); 259 | 260 | return (Task) { 261 | .tag = TASK_SEQ_TAG, 262 | .data = data, 263 | }; 264 | } 265 | -------------------------------------------------------------------------------- /plugs/squares/tasks.h: -------------------------------------------------------------------------------- 1 | #ifndef TASKS_H_ 2 | #define TASKS_H_ 3 | 4 | #include "env.h" 5 | #include "arena.h" 6 | #include "interpolators.h" 7 | 8 | typedef size_t Tag; 9 | 10 | typedef struct { 11 | Tag tag; 12 | void *data; 13 | } Task; 14 | 15 | typedef bool (*task_update_data_t)(void*, Env); 16 | 17 | typedef struct { 18 | task_update_data_t update; 19 | } Task_Funcs; 20 | 21 | bool task_update(Task task, Env env); 22 | 23 | typedef struct { 24 | Task_Funcs *items; 25 | size_t count; 26 | size_t capacity; 27 | } Task_VTable; 28 | 29 | extern Task_VTable task_vtable; 30 | extern Tag TASK_WAIT_TAG; 31 | extern Tag TASK_MOVE_SCALAR_TAG; 32 | extern Tag TASK_MOVE_VEC2_TAG; 33 | extern Tag TASK_MOVE_VEC4_TAG; 34 | extern Tag TASK_SEQ_TAG; 35 | extern Tag TASK_GROUP_TAG; 36 | 37 | Tag task_vtable_register(Arena *a, Task_Funcs funcs); 38 | void task_vtable_rebuild(Arena *a); 39 | 40 | typedef struct { 41 | Task *items; 42 | size_t count; 43 | size_t capacity; 44 | } Tasks; 45 | 46 | typedef struct { 47 | bool started; 48 | float cursor; 49 | float duration; 50 | } Wait_Data; 51 | 52 | float wait_interp(Wait_Data *data); 53 | bool wait_done(Wait_Data *data); 54 | bool wait_update(Wait_Data *data, Env env); 55 | Wait_Data wait_data(float duration); 56 | Task task_wait(Arena *a, float duration); 57 | 58 | typedef struct { 59 | Wait_Data wait; 60 | float *value; 61 | float start, target; 62 | Interp_Func func; 63 | } Move_Scalar_Data; 64 | 65 | bool move_scalar_update(Move_Scalar_Data *data, Env env); 66 | Move_Scalar_Data move_scalar_data(float *value, float target, float duration, Interp_Func func); 67 | Task task_move_scalar(Arena *a, float *value, float target, float duration, Interp_Func); 68 | 69 | typedef struct { 70 | Wait_Data wait; 71 | Vector2 *value; 72 | Vector2 start, target; 73 | Interp_Func func; 74 | } Move_Vec2_Data; 75 | 76 | bool move_vec2_update(Move_Vec2_Data *data, Env env); 77 | Move_Vec2_Data move_vec2_data(Vector2 *value, Vector2 target, float duration, Interp_Func func); 78 | Task task_move_vec2(Arena *a, Vector2 *value, Vector2 target, float duration, Interp_Func func); 79 | 80 | typedef struct { 81 | Wait_Data wait; 82 | Vector4 *value; 83 | Vector4 start, target; 84 | Interp_Func func; 85 | } Move_Vec4_Data; 86 | 87 | bool move_vec4_update(Move_Vec4_Data *data, Env env); 88 | Move_Vec4_Data move_vec4_data(Vector4 *value, Vector4 target, float duration, Interp_Func func); 89 | Task task_move_vec4(Arena *a, Vector4 *value, Vector4 target, float duration, Interp_Func func); 90 | 91 | typedef struct { 92 | Tasks tasks; 93 | } Group_Data; 94 | 95 | bool group_update(Group_Data *data, Env env); 96 | Task task_group_(Arena *a, ...); 97 | #define task_group(...) task_group_(__VA_ARGS__, (Task){0}) 98 | 99 | typedef struct { 100 | Tasks tasks; 101 | size_t it; 102 | } Seq_Data; 103 | 104 | bool seq_update(Seq_Data *data, Env env); 105 | Task task_seq_(Arena *a, ...); 106 | #define task_seq(...) task_seq_(__VA_ARGS__, (Task){0}) 107 | 108 | #endif // TASKS_H_ 109 | -------------------------------------------------------------------------------- /plugs/template/plug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include "env.h" 8 | #include "plug.h" 9 | 10 | #define PLUG(name, ret, ...) ret name(__VA_ARGS__); 11 | LIST_OF_PLUGS 12 | #undef PLUG 13 | 14 | #define FONT_SIZE 68 15 | 16 | typedef struct { 17 | size_t size; 18 | Font font; 19 | } Plug; 20 | 21 | static Plug *p; 22 | 23 | static void load_assets(void) 24 | { 25 | p->font = LoadFontEx("./assets/fonts/Vollkorn-Regular.ttf", FONT_SIZE, NULL, 0); 26 | } 27 | 28 | static void unload_assets(void) 29 | { 30 | UnloadFont(p->font); 31 | } 32 | 33 | void plug_reset(void) 34 | { 35 | } 36 | 37 | void plug_init(void) 38 | { 39 | p = malloc(sizeof(*p)); 40 | assert(p != NULL); 41 | memset(p, 0, sizeof(*p)); 42 | p->size = sizeof(*p); 43 | 44 | load_assets(); 45 | plug_reset(); 46 | } 47 | 48 | void *plug_pre_reload(void) 49 | { 50 | unload_assets(); 51 | return p; 52 | } 53 | 54 | void plug_post_reload(void *state) 55 | { 56 | p = state; 57 | if (p->size < sizeof(*p)) { 58 | TraceLog(LOG_INFO, "Migrating plug state schema %zu bytes -> %zu bytes", p->size, sizeof(*p)); 59 | p = realloc(p, sizeof(*p)); 60 | p->size = sizeof(*p); 61 | } 62 | 63 | load_assets(); 64 | } 65 | 66 | void plug_update(Env env) 67 | { 68 | Color background_color = ColorFromHSV(0, 0, 0.05); 69 | Color foreground_color = ColorFromHSV(0, 0, 0.95); 70 | 71 | ClearBackground(background_color); 72 | 73 | Camera2D camera = { 74 | .zoom = 1.0, 75 | .offset = {env.screen_width/2, env.screen_height/2}, 76 | }; 77 | BeginMode2D(camera); 78 | const char *text = "Panim Template"; 79 | Vector2 text_size = MeasureTextEx(p->font, text, FONT_SIZE, 0); 80 | Vector2 position = Vector2Scale(text_size, -0.5); 81 | DrawTextEx(p->font, text, position, FONT_SIZE, 0, foreground_color); 82 | EndMode2D(); 83 | } 84 | 85 | bool plug_finished(void) 86 | { 87 | return true; 88 | } 89 | -------------------------------------------------------------------------------- /plugs/tm/arena.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Alexey Kutepov 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | #ifndef ARENA_H_ 23 | #define ARENA_H_ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #ifndef ARENA_NOSTDIO 30 | #include 31 | #include 32 | #endif // ARENA_NOSTDIO 33 | 34 | #ifndef ARENA_ASSERT 35 | #include 36 | #define ARENA_ASSERT assert 37 | #endif 38 | 39 | #define ARENA_BACKEND_LIBC_MALLOC 0 40 | #define ARENA_BACKEND_LINUX_MMAP 1 41 | #define ARENA_BACKEND_WIN32_VIRTUALALLOC 2 42 | #define ARENA_BACKEND_WASM_HEAPBASE 3 43 | 44 | #ifndef ARENA_BACKEND 45 | #define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC 46 | #endif // ARENA_BACKEND 47 | 48 | typedef struct Region Region; 49 | 50 | struct Region { 51 | Region *next; 52 | size_t count; 53 | size_t capacity; 54 | uintptr_t data[]; 55 | }; 56 | 57 | typedef struct { 58 | Region *begin, *end; 59 | } Arena; 60 | 61 | #define REGION_DEFAULT_CAPACITY (8*1024) 62 | 63 | Region *new_region(size_t capacity); 64 | void free_region(Region *r); 65 | 66 | // TODO: snapshot/rewind capability for the arena 67 | // - Snapshot should be combination of a->end and a->end->count. 68 | // - Rewinding should be restoring a->end and a->end->count from the snapshot and 69 | // setting count-s of all the Region-s after the remembered a->end to 0. 70 | void *arena_alloc(Arena *a, size_t size_bytes); 71 | void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz); 72 | char *arena_strdup(Arena *a, const char *cstr); 73 | void *arena_memdup(Arena *a, void *data, size_t size); 74 | #ifndef ARENA_NOSTDIO 75 | char *arena_sprintf(Arena *a, const char *format, ...); 76 | #endif // ARENA_NOSTDIO 77 | 78 | void arena_reset(Arena *a); 79 | void arena_free(Arena *a); 80 | 81 | #define ARENA_DA_INIT_CAP 256 82 | 83 | #ifdef __cplusplus 84 | #define cast_ptr(ptr) (decltype(ptr)) 85 | #else 86 | #define cast_ptr(...) 87 | #endif 88 | 89 | #define arena_da_append(a, da, item) \ 90 | do { \ 91 | if ((da)->count >= (da)->capacity) { \ 92 | size_t new_capacity = (da)->capacity == 0 ? ARENA_DA_INIT_CAP : (da)->capacity*2; \ 93 | (da)->items = cast_ptr((da)->items)arena_realloc( \ 94 | (a), (da)->items, \ 95 | (da)->capacity*sizeof(*(da)->items), \ 96 | new_capacity*sizeof(*(da)->items)); \ 97 | (da)->capacity = new_capacity; \ 98 | } \ 99 | \ 100 | (da)->items[(da)->count++] = (item); \ 101 | } while (0) 102 | 103 | #endif // ARENA_H_ 104 | 105 | #ifdef ARENA_IMPLEMENTATION 106 | 107 | #if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC 108 | #include 109 | 110 | // TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region 111 | // It should be up to new_region() to decide the actual capacity to allocate 112 | Region *new_region(size_t capacity) 113 | { 114 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity; 115 | // TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned 116 | Region *r = (Region*)malloc(size_bytes); 117 | ARENA_ASSERT(r); 118 | r->next = NULL; 119 | r->count = 0; 120 | r->capacity = capacity; 121 | return r; 122 | } 123 | 124 | void free_region(Region *r) 125 | { 126 | free(r); 127 | } 128 | #elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP 129 | #include 130 | #include 131 | 132 | Region *new_region(size_t capacity) 133 | { 134 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; 135 | Region *r = mmap(NULL, size_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 136 | ARENA_ASSERT(r != MAP_FAILED); 137 | r->next = NULL; 138 | r->count = 0; 139 | r->capacity = capacity; 140 | return r; 141 | } 142 | 143 | void free_region(Region *r) 144 | { 145 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * r->capacity; 146 | int ret = munmap(r, size_bytes); 147 | ARENA_ASSERT(ret == 0); 148 | } 149 | 150 | #elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC 151 | 152 | #if !defined(_WIN32) 153 | # error "Current platform is not Windows" 154 | #endif 155 | 156 | #define WIN32_LEAN_AND_MEAN 157 | #include 158 | 159 | #define INV_HANDLE(x) (((x) == NULL) || ((x) == INVALID_HANDLE_VALUE)) 160 | 161 | Region *new_region(size_t capacity) 162 | { 163 | SIZE_T size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; 164 | Region *r = VirtualAllocEx( 165 | GetCurrentProcess(), /* Allocate in current process address space */ 166 | NULL, /* Unknown position */ 167 | size_bytes, /* Bytes to allocate */ 168 | MEM_COMMIT | MEM_RESERVE, /* Reserve and commit allocated page */ 169 | PAGE_READWRITE /* Permissions ( Read/Write )*/ 170 | ); 171 | if (INV_HANDLE(r)) 172 | ARENA_ASSERT(0 && "VirtualAllocEx() failed."); 173 | 174 | r->next = NULL; 175 | r->count = 0; 176 | r->capacity = capacity; 177 | return r; 178 | } 179 | 180 | void free_region(Region *r) 181 | { 182 | if (INV_HANDLE(r)) 183 | return; 184 | 185 | BOOL free_result = VirtualFreeEx( 186 | GetCurrentProcess(), /* Deallocate from current process address space */ 187 | (LPVOID)r, /* Address to deallocate */ 188 | 0, /* Bytes to deallocate ( Unknown, deallocate entire page ) */ 189 | MEM_RELEASE /* Release the page ( And implicitly decommit it ) */ 190 | ); 191 | 192 | if (FALSE == free_result) 193 | ARENA_ASSERT(0 && "VirtualFreeEx() failed."); 194 | } 195 | 196 | #elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE 197 | # error "TODO: WASM __heap_base backend is not implemented yet" 198 | #else 199 | # error "Unknown Arena backend" 200 | #endif 201 | 202 | // TODO: add debug statistic collection mode for arena 203 | // Should collect things like: 204 | // - How many times new_region was called 205 | // - How many times existing region was skipped 206 | // - How many times allocation exceeded REGION_DEFAULT_CAPACITY 207 | 208 | void *arena_alloc(Arena *a, size_t size_bytes) 209 | { 210 | size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t); 211 | 212 | if (a->end == NULL) { 213 | ARENA_ASSERT(a->begin == NULL); 214 | size_t capacity = REGION_DEFAULT_CAPACITY; 215 | if (capacity < size) capacity = size; 216 | a->end = new_region(capacity); 217 | a->begin = a->end; 218 | } 219 | 220 | while (a->end->count + size > a->end->capacity && a->end->next != NULL) { 221 | a->end = a->end->next; 222 | } 223 | 224 | if (a->end->count + size > a->end->capacity) { 225 | ARENA_ASSERT(a->end->next == NULL); 226 | size_t capacity = REGION_DEFAULT_CAPACITY; 227 | if (capacity < size) capacity = size; 228 | a->end->next = new_region(capacity); 229 | a->end = a->end->next; 230 | } 231 | 232 | void *result = &a->end->data[a->end->count]; 233 | a->end->count += size; 234 | return result; 235 | } 236 | 237 | void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz) 238 | { 239 | if (newsz <= oldsz) return oldptr; 240 | void *newptr = arena_alloc(a, newsz); 241 | char *newptr_char = (char*)newptr; 242 | char *oldptr_char = (char*)oldptr; 243 | for (size_t i = 0; i < oldsz; ++i) { 244 | newptr_char[i] = oldptr_char[i]; 245 | } 246 | return newptr; 247 | } 248 | 249 | char *arena_strdup(Arena *a, const char *cstr) 250 | { 251 | size_t n = strlen(cstr); 252 | char *dup = (char*)arena_alloc(a, n + 1); 253 | memcpy(dup, cstr, n); 254 | dup[n] = '\0'; 255 | return dup; 256 | } 257 | 258 | void *arena_memdup(Arena *a, void *data, size_t size) 259 | { 260 | return memcpy(arena_alloc(a, size), data, size); 261 | } 262 | 263 | #ifndef ARENA_NOSTDIO 264 | char *arena_sprintf(Arena *a, const char *format, ...) 265 | { 266 | va_list args; 267 | va_start(args, format); 268 | int n = vsnprintf(NULL, 0, format, args); 269 | va_end(args); 270 | 271 | ARENA_ASSERT(n >= 0); 272 | char *result = (char*)arena_alloc(a, n + 1); 273 | va_start(args, format); 274 | vsnprintf(result, n + 1, format, args); 275 | va_end(args); 276 | 277 | return result; 278 | } 279 | #endif // ARENA_NOSTDIO 280 | 281 | void arena_reset(Arena *a) 282 | { 283 | for (Region *r = a->begin; r != NULL; r = r->next) { 284 | r->count = 0; 285 | } 286 | 287 | a->end = a->begin; 288 | } 289 | 290 | void arena_free(Arena *a) 291 | { 292 | Region *r = a->begin; 293 | while (r) { 294 | Region *r0 = r; 295 | r = r->next; 296 | free_region(r0); 297 | } 298 | a->begin = NULL; 299 | a->end = NULL; 300 | } 301 | 302 | #endif // ARENA_IMPLEMENTATION 303 | -------------------------------------------------------------------------------- /plugs/tm/interpolators.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERPOLATORS_H_ 2 | #define INTERPOLATORS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef enum { 9 | FUNC_ID, 10 | FUNC_SINSTEP, 11 | FUNC_SMOOTHSTEP, 12 | FUNC_SQR, 13 | FUNC_SQRT, 14 | FUNC_SINPULSE, 15 | } Interp_Func; 16 | 17 | static inline float smoothstep(float x) 18 | { 19 | if (x < 0.0) return 0.0; 20 | if (x >= 1.0) return 1.0; 21 | return 3*x*x - 2*x*x*x; 22 | } 23 | 24 | static inline float sinstep(float t) 25 | { 26 | if (t < 0.0) return 0.0; 27 | if (t >= 1.0) return 1.0; 28 | return (sinf(PI*t - PI*0.5) + 1)*0.5; 29 | } 30 | 31 | static inline float sinpulse(float t) 32 | { 33 | if (t < 0.0) return 0.0; 34 | if (t >= 1.0) return 0.0; 35 | return sinf(PI*t); 36 | } 37 | 38 | static inline Vector2 cubic_bezier(float t, Vector2 nodes[4]) 39 | { 40 | float it = 1 - t; 41 | Vector2 b = Vector2Scale(nodes[0], it*it*it); 42 | b = Vector2Add(b, Vector2Scale(nodes[1], 3*it*it*t)); 43 | b = Vector2Add(b, Vector2Scale(nodes[2], 3*it*t*t)); 44 | b = Vector2Add(b, Vector2Scale(nodes[3], t*t*t)); 45 | return b; 46 | } 47 | 48 | static inline Vector2 cubic_bezier_der(float t, Vector2 nodes[4]) 49 | { 50 | float it = 1 - t; 51 | Vector2 b = Vector2Scale(nodes[0], -3*it*it); 52 | b = Vector2Add(b, Vector2Scale(nodes[1], 3*it*it)); 53 | b = Vector2Add(b, Vector2Scale(nodes[1], -6*it*t)); 54 | b = Vector2Add(b, Vector2Scale(nodes[2], 6*it*t)); 55 | b = Vector2Add(b, Vector2Scale(nodes[2], -3*t*t)); 56 | b = Vector2Add(b, Vector2Scale(nodes[3], 3*t*t)); 57 | return b; 58 | } 59 | 60 | static inline float cuber_bezier_newton(float x, Vector2 nodes[4], size_t n) 61 | { 62 | float t = 0; 63 | for (size_t i = 0; i < n; ++i) { 64 | t = t - (cubic_bezier(t, nodes).x - x)/cubic_bezier_der(t, nodes).x; 65 | } 66 | return t; 67 | } 68 | 69 | static inline float interp_func(Interp_Func func, float t) 70 | { 71 | switch (func) { 72 | case FUNC_ID: return t; 73 | case FUNC_SQR: return t*t; 74 | case FUNC_SQRT: return sqrtf(t); 75 | case FUNC_SINSTEP: return sinstep(t); 76 | case FUNC_SMOOTHSTEP: return smoothstep(t); 77 | case FUNC_SINPULSE: return sinpulse(t); 78 | } 79 | assert(0 && "UNREACHABLE"); 80 | return 0.0f; 81 | } 82 | 83 | #endif // INTERPOLATORS_H_ 84 | -------------------------------------------------------------------------------- /plugs/tm/plug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "nob.h" 10 | #include "env.h" 11 | #include "interpolators.h" 12 | #include "tasks.h" 13 | #include "plug.h" 14 | 15 | #define PLUG(name, ret, ...) ret name(__VA_ARGS__); 16 | LIST_OF_PLUGS 17 | #undef PLUG 18 | 19 | #if 0 20 | #define CELL_COLOR ColorFromHSV(0, 0.0, 0.15) 21 | #define HEAD_COLOR ColorFromHSV(200, 0.8, 0.8) 22 | #define BACKGROUND_COLOR ColorFromHSV(120, 0.0, 0.88) 23 | #else 24 | #define CELL_COLOR ColorFromHSV(0, 0.0, 1 - 0.15) 25 | #define HEAD_COLOR ColorFromHSV(200, 0.8, 0.8) 26 | #define BACKGROUND_COLOR ColorFromHSV(120, 0.0, 1 - 0.88) 27 | #endif 28 | 29 | #define CELL_WIDTH 200.0f 30 | #define CELL_HEIGHT 200.0f 31 | #define FONT_SIZE (CELL_WIDTH*0.52f) 32 | #define CELL_PAD (CELL_WIDTH*0.15f) 33 | #define START_AT_CELL_INDEX 5 34 | #define HEAD_MOVING_DURATION 0.25f 35 | #define HEAD_WRITING_DURATION 0.25f 36 | #define INTRO_DURATION 1.0f 37 | #define TAPE_SIZE 50 38 | #define BUMP_DECIPATE 0.8f 39 | 40 | typedef enum { 41 | DIR_LEFT = -1, 42 | DIR_RIGHT = 1, 43 | } Direction; 44 | 45 | typedef enum { 46 | IMAGE_EGGPLANT, 47 | IMAGE_100, 48 | IMAGE_FIRE, 49 | IMAGE_JOY, 50 | IMAGE_OK, 51 | COUNT_IMAGES, 52 | } Image_Index; 53 | 54 | static_assert(COUNT_IMAGES == 5, "Amount of images is updated"); 55 | static const char *image_file_paths[COUNT_IMAGES] = { 56 | [IMAGE_EGGPLANT] = "./assets/images/eggplant.png", 57 | [IMAGE_100] = "./assets/images/100.png", 58 | [IMAGE_FIRE] = "./assets/images/fire.png", 59 | [IMAGE_JOY] = "./assets/images/joy.png", 60 | [IMAGE_OK] = "./assets/images/ok.png", 61 | }; 62 | 63 | typedef enum { 64 | SYMBOL_TEXT, 65 | SYMBOL_IMAGE, 66 | } Symbol_Kind; 67 | 68 | typedef struct { 69 | Symbol_Kind kind; 70 | const char *text; 71 | Image_Index image_index; 72 | } Symbol; 73 | 74 | static Symbol symbol_text(Arena *a, const char *text) 75 | { 76 | return (Symbol) { 77 | .kind = SYMBOL_TEXT, 78 | .text = arena_strdup(a, text), 79 | }; 80 | } 81 | 82 | static Symbol symbol_image(Image_Index image_index) 83 | { 84 | return (Symbol) { 85 | .kind = SYMBOL_IMAGE, 86 | .image_index = image_index, 87 | }; 88 | } 89 | 90 | typedef enum { 91 | RULE_STATE = 0, 92 | RULE_READ, 93 | RULE_WRITE, 94 | RULE_STEP, 95 | RULE_NEXT, 96 | COUNT_RULE_SYMBOLS, 97 | } Rule_Symbol; 98 | 99 | typedef struct { 100 | Symbol symbols[COUNT_RULE_SYMBOLS]; 101 | float bump[COUNT_RULE_SYMBOLS]; 102 | } Rule; 103 | 104 | typedef struct { 105 | Rule *items; 106 | size_t count; 107 | size_t capacity; 108 | 109 | float lines_t; 110 | float symbols_t; 111 | float head_t; 112 | float head_offset_t; 113 | } Table; 114 | 115 | typedef struct { 116 | Symbol symbol_a; 117 | Symbol symbol_b; 118 | float t; 119 | float bump; 120 | } Cell; 121 | 122 | typedef struct { 123 | Cell *items; 124 | size_t count; 125 | size_t capacity; 126 | } Tape; 127 | 128 | typedef struct { 129 | int index; 130 | float offset; 131 | 132 | Cell state; 133 | float state_t; 134 | } Head; 135 | 136 | typedef enum { 137 | FONT_REGULAR = 0, 138 | FONT_BOLD, 139 | COUNT_FONT_STYLE, 140 | } Font_Style; 141 | 142 | typedef struct { 143 | size_t size; 144 | 145 | // State (survives the plugin reload, resets on plug_reset) 146 | Arena arena_state; 147 | struct { 148 | float t; 149 | Head head; 150 | Tape tape; 151 | Table table; 152 | float tape_y_offset; 153 | Task task; 154 | bool finished; 155 | } scene; 156 | 157 | // Assets (reloads along with the plugin, does not change throughout the animation) 158 | Arena arena_assets; 159 | Font iosevka[COUNT_FONT_STYLE]; 160 | Sound write_sound; 161 | Wave write_wave; 162 | Texture2D images[COUNT_IMAGES]; 163 | Tag TASK_INTRO_TAG; 164 | Tag TASK_MOVE_HEAD_TAG; 165 | Tag TASK_WRITE_HEAD_TAG; 166 | Tag TASK_WRITE_ALL_TAG; 167 | Tag TASK_WRITE_CELL_TAG; 168 | Tag TASK_BUMP_TAG; 169 | } Plug; 170 | 171 | static Plug *p = NULL; 172 | 173 | typedef struct { 174 | Wait_Data wait; 175 | size_t head; 176 | } Intro_Data; 177 | 178 | static bool task_intro_update(Intro_Data *data, Env env) 179 | { 180 | if (wait_done(&data->wait)) return true; 181 | if (!data->wait.started) p->scene.head.index = data->head; 182 | bool finished = wait_update(&data->wait, env); 183 | p->scene.t = smoothstep(wait_interp(&data->wait)); 184 | return finished; 185 | } 186 | 187 | static Intro_Data intro_data(size_t head) 188 | { 189 | return (Intro_Data) { 190 | .wait = wait_data(INTRO_DURATION), 191 | .head = head, 192 | }; 193 | } 194 | 195 | static Task task_intro(Arena *a, size_t head) 196 | { 197 | Intro_Data data = intro_data(head); 198 | return (Task) { 199 | .tag = p->TASK_INTRO_TAG, 200 | .data = arena_memdup(a, &data, sizeof(data)), 201 | }; 202 | } 203 | 204 | typedef struct { 205 | Wait_Data wait; 206 | Direction dir; 207 | } Move_Head_Data; 208 | 209 | static bool move_head_update(Move_Head_Data *data, Env env) 210 | { 211 | if (wait_done(&data->wait)) return true; 212 | 213 | if (wait_update(&data->wait, env)) { 214 | p->scene.head.offset = 0.0f; 215 | p->scene.head.index += data->dir; 216 | return true; 217 | } 218 | 219 | p->scene.head.offset = Lerp(0, data->dir, smoothstep(wait_interp(&data->wait))); 220 | return false; 221 | } 222 | 223 | static Move_Head_Data move_head(Direction dir, float duration) 224 | { 225 | return (Move_Head_Data) { 226 | .wait = wait_data(duration), 227 | .dir = dir, 228 | }; 229 | } 230 | 231 | static Task task_move_head(Arena *a, Direction dir, float duration) 232 | { 233 | Move_Head_Data data = move_head(dir, duration); 234 | return (Task) { 235 | .tag = p->TASK_MOVE_HEAD_TAG, 236 | .data = arena_memdup(a, &data, sizeof(data)), 237 | }; 238 | } 239 | 240 | typedef struct { 241 | Wait_Data wait; 242 | Symbol write; 243 | Cell *cell; 244 | } Write_Cell_Data; 245 | 246 | static bool write_cell_update(Write_Cell_Data *data, Env env) 247 | { 248 | if (wait_done(&data->wait)) return true; 249 | 250 | if (!data->wait.started && data->cell) { 251 | data->cell->symbol_b = data->write; 252 | data->cell->t = 0.0; 253 | } 254 | 255 | float t1 = wait_interp(&data->wait); 256 | bool finished = wait_update(&data->wait, env); 257 | float t2 = wait_interp(&data->wait); 258 | 259 | if (t1 < 0.5 && t2 >= 0.5) { 260 | env.play_sound(p->write_sound, p->write_wave); 261 | } 262 | 263 | if (data->cell) data->cell->t = smoothstep(t2); 264 | 265 | if (finished && data->cell) { 266 | data->cell->symbol_a = data->cell->symbol_b; 267 | data->cell->t = 0.0; 268 | } 269 | 270 | return finished; 271 | } 272 | 273 | static Write_Cell_Data write_cell_data(Cell *cell, Symbol write) 274 | { 275 | return (Write_Cell_Data) { 276 | .wait = wait_data(HEAD_WRITING_DURATION), 277 | .cell = cell, 278 | .write = write, 279 | }; 280 | } 281 | 282 | static Task task_write_cell(Arena *a, Cell *cell, Symbol write) 283 | { 284 | Write_Cell_Data data = write_cell_data(cell, write); 285 | return (Task) { 286 | .tag = p->TASK_WRITE_CELL_TAG, 287 | .data = arena_memdup(a, &data, sizeof(data)), 288 | }; 289 | } 290 | 291 | typedef struct { 292 | Wait_Data wait; 293 | Symbol write; 294 | } Write_Head_Data; 295 | 296 | static bool write_head_update(Write_Head_Data *data, Env env) 297 | { 298 | if (wait_done(&data->wait)) return true; 299 | 300 | Cell *cell = NULL; 301 | if ((size_t)p->scene.head.index < p->scene.tape.count) { 302 | cell = &p->scene.tape.items[(size_t)p->scene.head.index]; 303 | } 304 | 305 | if (!data->wait.started && cell) { 306 | cell->symbol_b = data->write; 307 | cell->t = 0.0; 308 | } 309 | 310 | float t1 = wait_interp(&data->wait); 311 | bool finished = wait_update(&data->wait, env); 312 | float t2 = wait_interp(&data->wait); 313 | 314 | if (t1 < 0.5 && t2 >= 0.5) { 315 | env.play_sound(p->write_sound, p->write_wave); 316 | } 317 | 318 | if (cell) cell->t = smoothstep(t2); 319 | 320 | if (finished && cell) { 321 | cell->symbol_a = cell->symbol_b; 322 | cell->t = 0.0; 323 | } 324 | 325 | return finished; 326 | } 327 | 328 | static Write_Head_Data write_head_data(Symbol write, float duration) 329 | { 330 | return (Write_Head_Data) { 331 | .wait = wait_data(duration), 332 | .write = write, 333 | }; 334 | } 335 | 336 | static Task task_write_head(Arena *a, Symbol write, float duration) 337 | { 338 | Write_Head_Data data = write_head_data(write, duration); 339 | return (Task) { 340 | .tag = p->TASK_WRITE_HEAD_TAG, 341 | .data = arena_memdup(a, &data, sizeof(data)), 342 | }; 343 | } 344 | 345 | typedef struct { 346 | Wait_Data wait; 347 | Symbol write; 348 | } Write_All_Data; 349 | 350 | static bool write_all_update(Write_All_Data *data, Env env) 351 | { 352 | if (wait_done(&data->wait)) return true; 353 | 354 | if (!data->wait.started) { 355 | for (size_t i = 0; i < p->scene.tape.count; ++i) { 356 | p->scene.tape.items[i].t = 0.0f; 357 | p->scene.tape.items[i].symbol_b = data->write; 358 | } 359 | } 360 | 361 | float t1 = wait_interp(&data->wait); 362 | bool finished = wait_update(&data->wait, env); 363 | float t2 = wait_interp(&data->wait); 364 | 365 | if (t1 < 0.5 && t2 >= 0.5) { 366 | env.play_sound(p->write_sound, p->write_wave); 367 | } 368 | 369 | for (size_t i = 0; i < p->scene.tape.count; ++i) { 370 | p->scene.tape.items[i].t = smoothstep(t2); 371 | } 372 | 373 | if (finished) { 374 | for (size_t i = 0; i < p->scene.tape.count; ++i) { 375 | p->scene.tape.items[i].t = 0.0f; 376 | p->scene.tape.items[i].symbol_a = p->scene.tape.items[i].symbol_b; 377 | } 378 | } 379 | 380 | return finished; 381 | } 382 | 383 | static Write_All_Data write_all_data(Symbol write) 384 | { 385 | return (Write_All_Data) { 386 | .write = write, 387 | .wait = wait_data(HEAD_WRITING_DURATION), 388 | }; 389 | } 390 | 391 | static Task task_write_all(Arena *a, Symbol write) 392 | { 393 | Write_All_Data data = write_all_data(write); 394 | return (Task) { 395 | .tag = p->TASK_WRITE_ALL_TAG, 396 | .data = arena_memdup(a, &data, sizeof(data)), 397 | }; 398 | } 399 | 400 | typedef struct { 401 | size_t row; 402 | size_t column; 403 | bool done; 404 | } Bump_Data; 405 | 406 | static bool bump_update(Bump_Data *data, Env env) 407 | { 408 | (void) env; 409 | if (data->done) return true; 410 | p->scene.table.items[data->row].bump[data->column] = 1.0f; 411 | data->done = true; 412 | return true; 413 | } 414 | 415 | static Bump_Data bump_data(size_t row, size_t column) 416 | { 417 | return (Bump_Data) { 418 | .row = row, 419 | .column = column, 420 | }; 421 | } 422 | 423 | static Task task_bump(Arena *a, size_t row, size_t column) 424 | { 425 | Bump_Data data = bump_data(row, column); 426 | return (Task) { 427 | .tag = p->TASK_BUMP_TAG, 428 | .data = arena_memdup(a, &data, sizeof(data)), 429 | }; 430 | } 431 | 432 | static Rule rule(Symbol state, Symbol read, Symbol write, Symbol step, Symbol next) 433 | { 434 | return (Rule) { 435 | .symbols = { 436 | [RULE_STATE] = state, 437 | [RULE_READ] = read, 438 | [RULE_WRITE] = write, 439 | [RULE_STEP] = step, 440 | [RULE_NEXT] = next, 441 | }, 442 | }; 443 | } 444 | 445 | static void load_assets(void) 446 | { 447 | Arena *a = &p->arena_assets; 448 | arena_reset(a); 449 | 450 | int codepoints_count = 0; 451 | int *codepoints = LoadCodepoints("?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-@./:)→←", &codepoints_count); 452 | p->iosevka[FONT_REGULAR] = LoadFontEx("./assets/fonts/iosevka-regular.ttf", FONT_SIZE*3, codepoints, codepoints_count); 453 | p->iosevka[FONT_BOLD] = LoadFontEx("./assets/fonts/iosevka-bold.ttf", FONT_SIZE*3, codepoints, codepoints_count); 454 | UnloadCodepoints(codepoints); 455 | for (size_t i = 0; i < COUNT_FONT_STYLE; ++i) { 456 | GenTextureMipmaps(&p->iosevka[i].texture); 457 | SetTextureFilter(p->iosevka[i].texture, TEXTURE_FILTER_BILINEAR); 458 | } 459 | 460 | for (size_t i = 0; i < COUNT_IMAGES; ++i) { 461 | p->images[i] = LoadTexture(image_file_paths[i]); 462 | GenTextureMipmaps(&p->images[i]); 463 | SetTextureFilter(p->images[i], TEXTURE_FILTER_BILINEAR); 464 | } 465 | 466 | p->write_wave = LoadWave("./assets/sounds/plant-bomb.wav"); 467 | p->write_sound = LoadSoundFromWave(p->write_wave); 468 | 469 | task_vtable_rebuild(a); 470 | p->TASK_INTRO_TAG = task_vtable_register(a, (Task_Funcs) { 471 | .update = (task_update_data_t)task_intro_update, 472 | }); 473 | p->TASK_MOVE_HEAD_TAG = task_vtable_register(a, (Task_Funcs) { 474 | .update = (task_update_data_t)move_head_update, 475 | }); 476 | p->TASK_WRITE_HEAD_TAG = task_vtable_register(a, (Task_Funcs) { 477 | .update = (task_update_data_t)write_head_update, 478 | }); 479 | p->TASK_WRITE_ALL_TAG = task_vtable_register(a, (Task_Funcs) { 480 | .update = (task_update_data_t)write_all_update, 481 | }); 482 | p->TASK_WRITE_CELL_TAG = task_vtable_register(a, (Task_Funcs) { 483 | .update = (task_update_data_t)write_cell_update, 484 | }); 485 | p->TASK_BUMP_TAG = task_vtable_register(a, (Task_Funcs) { 486 | .update = (task_update_data_t)bump_update, 487 | }); 488 | } 489 | 490 | static void unload_assets(void) 491 | { 492 | for (size_t i = 0; i < COUNT_FONT_STYLE; ++i) { 493 | UnloadFont(p->iosevka[i]); 494 | } 495 | UnloadSound(p->write_sound); 496 | UnloadWave(p->write_wave); 497 | for (size_t i = 0; i < COUNT_IMAGES; ++i) { 498 | UnloadTexture(p->images[i]); 499 | } 500 | } 501 | 502 | static Task task_outro(Arena *a, float duration) 503 | { 504 | Interp_Func func = FUNC_SMOOTHSTEP; 505 | return task_group(a, 506 | task_move_scalar(a, &p->scene.t, 0.0, duration, func), 507 | task_move_scalar(a, &p->scene.tape_y_offset, 0.0, duration, func), 508 | task_move_scalar(a, &p->scene.table.lines_t, 0.0, duration, func), 509 | task_move_scalar(a, &p->scene.table.symbols_t, 0.0, duration, func), 510 | task_move_scalar(a, &p->scene.table.head_t, 0.0, duration, func), 511 | task_move_scalar(a, &p->scene.head.state_t, 0.0, duration, func)); 512 | } 513 | 514 | static Task task_fun(Arena *a) 515 | { 516 | return task_seq(a, 517 | task_write_head(a, symbol_text(a, "1"), HEAD_WRITING_DURATION), 518 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 519 | task_write_head(a, symbol_text(a, "2"), HEAD_WRITING_DURATION), 520 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 521 | task_write_head(a, symbol_text(a, "69"), HEAD_WRITING_DURATION), 522 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 523 | task_write_head(a, symbol_text(a, "420"), HEAD_WRITING_DURATION), 524 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 525 | task_write_head(a, symbol_text(a, ":)"), HEAD_WRITING_DURATION), 526 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 527 | task_write_head(a, symbol_image(IMAGE_JOY), HEAD_WRITING_DURATION), 528 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 529 | task_write_head(a, symbol_image(IMAGE_FIRE), HEAD_WRITING_DURATION), 530 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 531 | task_write_head(a, symbol_image(IMAGE_OK), HEAD_WRITING_DURATION), 532 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 533 | task_write_head(a, symbol_image(IMAGE_100), HEAD_WRITING_DURATION), 534 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 535 | task_write_head(a, symbol_image(IMAGE_EGGPLANT), HEAD_WRITING_DURATION), 536 | task_write_all(a, symbol_text(a, "0")), 537 | task_write_all(a, symbol_text(a, "69")), 538 | task_write_all(a, symbol_image(IMAGE_EGGPLANT)), 539 | task_write_all(a, symbol_text(a, "0"))); 540 | } 541 | 542 | static Task task_inc(Arena *a, Symbol zero, Symbol one) 543 | { 544 | float delay = 0.8; 545 | return task_seq(a, 546 | task_wait(a, delay), 547 | task_group(a, 548 | task_write_head(a, zero, HEAD_WRITING_DURATION), 549 | task_bump(a, 1, RULE_WRITE)), 550 | task_wait(a, delay), 551 | task_group(a, 552 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 553 | task_bump(a, 1, RULE_STEP)), 554 | task_wait(a, delay), 555 | task_group(a, 556 | task_write_cell(a, &p->scene.head.state, symbol_text(a, "Inc")), 557 | task_bump(a, 1, RULE_NEXT)), 558 | task_wait(a, delay), 559 | task_group(a, 560 | task_write_head(a, zero, HEAD_WRITING_DURATION), 561 | task_bump(a, 1, RULE_WRITE)), 562 | task_wait(a, delay), 563 | task_group(a, 564 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 565 | task_bump(a, 1, RULE_STEP)), 566 | task_wait(a, delay), 567 | task_group(a, 568 | task_write_cell(a, &p->scene.head.state, symbol_text(a, "Inc")), 569 | task_bump(a, 1, RULE_NEXT)), 570 | task_wait(a, delay), 571 | task_group(a, 572 | task_write_head(a, zero, HEAD_WRITING_DURATION), 573 | task_bump(a, 1, RULE_WRITE)), 574 | task_wait(a, delay), 575 | task_group(a, 576 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 577 | task_bump(a, 1, RULE_STEP)), 578 | task_wait(a, delay), 579 | task_group(a, 580 | task_move_scalar(a, &p->scene.table.head_offset_t, 0.0, HEAD_WRITING_DURATION, FUNC_SMOOTHSTEP), 581 | task_write_cell(a, &p->scene.head.state, symbol_text(a, "Inc")), 582 | task_bump(a, 1, RULE_NEXT)), 583 | task_wait(a, delay), 584 | 585 | // task_wait(a, delay), 586 | 587 | task_group(a, 588 | task_write_head(a, one, HEAD_WRITING_DURATION), 589 | task_bump(a, 0, RULE_WRITE)), 590 | task_wait(a, delay), 591 | task_group(a, 592 | task_move_head(a, DIR_RIGHT, HEAD_MOVING_DURATION), 593 | task_bump(a, 0, RULE_STEP)), 594 | task_wait(a, delay), 595 | task_group(a, 596 | task_write_cell(a, &p->scene.head.state, symbol_text(a, "Halt")), 597 | task_bump(a, 0, RULE_NEXT)), 598 | task_wait(a, delay)); 599 | } 600 | 601 | void plug_reset(void) 602 | { 603 | Arena *a = &p->arena_state; 604 | arena_reset(a); 605 | memset(&p->scene, 0, sizeof(p->scene)); 606 | 607 | // Table 608 | { 609 | arena_da_append(a, &p->scene.table, rule( 610 | symbol_text(a, "Inc"), 611 | symbol_text(a, "0"), 612 | symbol_text(a, "1"), 613 | symbol_text(a, "→"), 614 | symbol_text(a, "Halt"))); 615 | arena_da_append(a, &p->scene.table, rule( 616 | symbol_text(a, "Inc"), 617 | symbol_text(a, "1"), 618 | symbol_text(a, "0"), 619 | symbol_text(a, "→"), 620 | symbol_text(a, "Inc"))); 621 | } 622 | 623 | Symbol zero = symbol_text(a, "0"); 624 | Symbol one = symbol_text(a, "1"); 625 | Symbol nothing = symbol_text(a, " "); 626 | for (size_t i = 0; i < START_AT_CELL_INDEX; ++i) { 627 | Cell cell = {.symbol_a = nothing,}; 628 | nob_da_append(&p->scene.tape, cell); 629 | } 630 | for (size_t i = START_AT_CELL_INDEX; i < START_AT_CELL_INDEX + 3; ++i) { 631 | Cell cell = {.symbol_a = one,}; 632 | nob_da_append(&p->scene.tape, cell); 633 | } 634 | for (size_t i = START_AT_CELL_INDEX + 3; i < TAPE_SIZE; ++i) { 635 | Cell cell = {.symbol_a = zero,}; 636 | nob_da_append(&p->scene.tape, cell); 637 | } 638 | 639 | p->scene.head.state.symbol_a = symbol_text(a, "Inc"); 640 | p->scene.table.head_offset_t = 1.0f; 641 | 642 | p->scene.task = task_seq(a, 643 | task_intro(a, START_AT_CELL_INDEX), 644 | task_wait(a, 0.5), 645 | task_move_scalar(a, &p->scene.tape_y_offset, -280.0, 0.5, FUNC_SMOOTHSTEP), 646 | task_wait(a, 0.5), 647 | 648 | task_group(a, 649 | task_move_scalar(a, &p->scene.table.lines_t, 1.0, 0.5, FUNC_SMOOTHSTEP), 650 | task_move_scalar(a, &p->scene.table.symbols_t, 1.0, 0.5, FUNC_SMOOTHSTEP), 651 | task_move_scalar(a, &p->scene.head.state_t, 1.0, 0.5, FUNC_SMOOTHSTEP), 652 | task_move_scalar(a, &p->scene.table.head_t, 1.0, 0.5, FUNC_SMOOTHSTEP)), 653 | 654 | task_inc(a, zero, one), 655 | 656 | // task_fun(a), 657 | 658 | task_wait(a, 1.5), 659 | task_outro(a, INTRO_DURATION), 660 | task_wait(a, 0.5)); 661 | } 662 | 663 | void plug_init(void) 664 | { 665 | p = malloc(sizeof(*p)); 666 | assert(p != NULL); 667 | memset(p, 0, sizeof(*p)); 668 | p->size = sizeof(*p); 669 | 670 | load_assets(); 671 | plug_reset(); 672 | } 673 | 674 | void *plug_pre_reload(void) 675 | { 676 | unload_assets(); 677 | return p; 678 | } 679 | 680 | void plug_post_reload(void *state) 681 | { 682 | p = state; 683 | if (p->size < sizeof(*p)) { 684 | TraceLog(LOG_INFO, "Migrating plug state schema %zu bytes -> %zu bytes", p->size, sizeof(*p)); 685 | p = realloc(p, sizeof(*p)); 686 | p->size = sizeof(*p); 687 | } 688 | 689 | load_assets(); 690 | } 691 | 692 | static void text_in_rec(Rectangle rec, const char *text, Font_Style style, float size, Color color) 693 | { 694 | Vector2 rec_size = {rec.width, rec.height}; 695 | float font_size = size; 696 | Vector2 text_size = MeasureTextEx(p->iosevka[style], text, font_size, 0); 697 | Vector2 position = { .x = rec.x, .y = rec.y }; 698 | 699 | position = Vector2Add(position, Vector2Scale(rec_size, 0.5)); 700 | position = Vector2Subtract(position, Vector2Scale(text_size, 0.5)); 701 | 702 | DrawTextEx(p->iosevka[style], text, position, font_size, 0, color); 703 | } 704 | 705 | static void image_in_rec(Rectangle rec, Texture2D image, float size, Color color) 706 | { 707 | Vector2 rec_size = {rec.width, rec.height}; 708 | Vector2 image_size = {size, size}; 709 | Vector2 position = {rec.x, rec.y}; 710 | 711 | position = Vector2Add(position, Vector2Scale(rec_size, 0.5)); 712 | position = Vector2Subtract(position, Vector2Scale(image_size, 0.5)); 713 | 714 | Rectangle source = { 0, 0, image.width, image.height }; 715 | Rectangle dest = { position.x, position.y, image_size.x, image_size.y }; 716 | DrawTexturePro(image, source, dest, Vector2Zero(), 0.0, color); 717 | } 718 | 719 | static void symbol_in_rec(Rectangle rec, Symbol symbol, float size, Color color) 720 | { 721 | switch (symbol.kind) { 722 | case SYMBOL_TEXT: { 723 | text_in_rec(rec, symbol.text, FONT_REGULAR, size, color); 724 | } break; 725 | case SYMBOL_IMAGE: { 726 | image_in_rec(rec, p->images[symbol.image_index], size, WHITE); 727 | } break; 728 | } 729 | } 730 | 731 | static void interp_symbol_in_rec(Rectangle rec, Symbol from_symbol, Symbol to_symbol, float size, float t, Color color) 732 | { 733 | symbol_in_rec(rec, from_symbol, size*(1 - t), ColorAlpha(color, 1 - t)); 734 | symbol_in_rec(rec, to_symbol, size*t, ColorAlpha(color, t)); 735 | } 736 | 737 | static void cell_in_rec(Rectangle rec, Cell cell, float size, Color color) 738 | { 739 | interp_symbol_in_rec(rec, cell.symbol_a, cell.symbol_b, size + (cell.bump > 0 ? 1 - cell.bump : 0)*size*3, cell.t, color); 740 | } 741 | 742 | static void render_table_lines(float x, float y, float field_width, float field_height, size_t table_columns, size_t table_rows, float t, float thick, Color color) 743 | { 744 | thick *= t; 745 | for (size_t i = 0; i < table_rows + 1; ++i) { 746 | Vector2 start_pos = { 747 | .x = x - thick/2, 748 | .y = y + i*field_height, 749 | }; 750 | Vector2 end_pos = { 751 | .x = x + field_width*table_columns + thick/2, 752 | .y = y + i*field_height, 753 | }; 754 | if (i >= table_rows) { 755 | Vector2 t = start_pos; 756 | start_pos = end_pos; 757 | end_pos = t; 758 | } 759 | end_pos = Vector2Lerp(start_pos, end_pos, t); 760 | DrawLineEx(start_pos, end_pos, thick, color); 761 | } 762 | 763 | for (size_t i = 0; i < table_columns + 1; ++i) { 764 | Vector2 start_pos = { 765 | .x = x + i*field_width, 766 | .y = y, 767 | }; 768 | Vector2 end_pos = { 769 | .x = x + i*field_width, 770 | .y = y + field_height*table_rows, 771 | }; 772 | if (i > 0) { 773 | Vector2 t = start_pos; 774 | start_pos = end_pos; 775 | end_pos = t; 776 | } 777 | end_pos = Vector2Lerp(start_pos, end_pos, t); 778 | DrawLineEx(start_pos, end_pos, thick, color); 779 | } 780 | } 781 | 782 | void plug_update(Env env) 783 | { 784 | ClearBackground(BACKGROUND_COLOR); 785 | 786 | p->scene.finished = task_update(p->scene.task, env); 787 | 788 | for (size_t i = 0; i < p->scene.table.count; ++i) { 789 | for (size_t j = 0; j < COUNT_RULE_SYMBOLS; ++j) { 790 | float *t = &p->scene.table.items[i].bump[j]; 791 | if (*t > 0) { 792 | *t = ((*t)*BUMP_DECIPATE - env.delta_time)/BUMP_DECIPATE; 793 | } 794 | } 795 | } 796 | { 797 | float *t = &p->scene.head.state.bump; 798 | if (*t > 0) { 799 | *t = ((*t)*BUMP_DECIPATE - env.delta_time)/BUMP_DECIPATE; 800 | } 801 | } 802 | 803 | float head_thick = 20.0; 804 | float head_padding = head_thick*2.5; 805 | Rectangle head_rec = { 806 | .width = CELL_WIDTH + head_padding, 807 | .height = CELL_HEIGHT + head_padding, 808 | }; 809 | float t = ((float)p->scene.head.index + p->scene.head.offset); 810 | head_rec.x = CELL_WIDTH/2 - head_rec.width/2 + Lerp(-20.0, t, p->scene.t)*(CELL_WIDTH + CELL_PAD); 811 | head_rec.y = CELL_HEIGHT/2 - head_rec.height/2; 812 | Camera2D camera = { 813 | .target = { 814 | .x = head_rec.x + head_rec.width/2, 815 | .y = head_rec.y + head_rec.height/2 - p->scene.tape_y_offset, 816 | }, 817 | .zoom = Lerp(0.5, 0.93, p->scene.t), 818 | .offset = { 819 | .x = env.screen_width/2, 820 | .y = env.screen_height/2, 821 | }, 822 | }; 823 | 824 | // Scene 825 | BeginMode2D(camera); 826 | { 827 | // Tape 828 | { 829 | for (size_t i = 0; i < p->scene.tape.count; ++i) { 830 | Rectangle rec = { 831 | .x = i*(CELL_WIDTH + CELL_PAD), 832 | .y = 0, 833 | .width = CELL_WIDTH, 834 | .height = CELL_HEIGHT, 835 | }; 836 | DrawRectangleRec(rec, CELL_COLOR); 837 | cell_in_rec(rec, p->scene.tape.items[i], FONT_SIZE, BACKGROUND_COLOR); 838 | } 839 | } 840 | 841 | // Head 842 | { 843 | Rectangle state_rec = { 844 | .width = head_rec.width, 845 | .height = head_rec.height*0.5, 846 | }; 847 | state_rec.x = head_rec.x, 848 | state_rec.y = head_rec.y + head_rec.height - state_rec.height*(1 - p->scene.head.state_t), 849 | cell_in_rec(state_rec, p->scene.head.state, FONT_SIZE*0.75*p->scene.head.state_t, ColorAlpha(CELL_COLOR, p->scene.head.state_t)); 850 | float h = head_rec.height; 851 | if (state_rec.y + state_rec.height > head_rec.y + head_rec.height) { 852 | h += state_rec.y + state_rec.height - (head_rec.y + head_rec.height); 853 | } 854 | render_table_lines(head_rec.x, head_rec.y, head_rec.width, h, 1, 1, p->scene.t, head_thick, HEAD_COLOR); 855 | Rectangle watermark = { 856 | .width = state_rec.width, 857 | .height = FONT_SIZE*0.5, 858 | }; 859 | watermark.x = state_rec.x, 860 | watermark.y = state_rec.y + state_rec.height; 861 | text_in_rec(watermark, "tsoding.bsky.social", FONT_REGULAR, FONT_SIZE*0.25, ColorAlpha(CELL_COLOR, p->scene.t*0.5)); 862 | } 863 | 864 | // Table 865 | { 866 | float top_margin = 300.0; 867 | float right_margin = 70.0; 868 | float symbol_size = FONT_SIZE*0.75; 869 | float field_width = 20.0f*9 + CELL_PAD*0.5; 870 | float field_height = 15.0f*9 + CELL_PAD*0.5; 871 | float x = head_rec.x + head_rec.width/2 - (field_width*COUNT_RULE_SYMBOLS + right_margin)/2; 872 | float y = head_rec.y + head_rec.height + top_margin; 873 | 874 | // Table Header 875 | if (0) { 876 | static const char *header_names[COUNT_RULE_SYMBOLS] = { 877 | [RULE_STATE] = "State", 878 | [RULE_READ] = "Read", 879 | [RULE_WRITE] = "Write", 880 | [RULE_STEP] = "Step", 881 | [RULE_NEXT] = "Next", 882 | }; 883 | 884 | float factor = 0.52; 885 | float margin = head_thick; 886 | for (size_t j = 0; j < COUNT_RULE_SYMBOLS; ++j) { 887 | Rectangle rec = { 888 | .x = x + j*field_width + (j >= 2 ? right_margin : 0.0f), 889 | .y = y + (-1)*field_height*factor - margin, 890 | .width = field_width, 891 | .height = field_height*factor, 892 | }; 893 | 894 | text_in_rec(rec, header_names[j], FONT_BOLD, 895 | symbol_size*factor*p->scene.table.symbols_t, 896 | ColorAlpha(CELL_COLOR, p->scene.table.symbols_t*t)); 897 | } 898 | } 899 | 900 | for (size_t i = 0; i < p->scene.table.count; ++i) { 901 | for (size_t j = 0; j < COUNT_RULE_SYMBOLS; ++j) { 902 | Rectangle rec = { 903 | .x = x + j*field_width + (j >= 2 ? right_margin : 0.0f), 904 | .y = y + i*field_height, 905 | .width = field_width, 906 | .height = field_height, 907 | }; 908 | symbol_in_rec(rec, 909 | p->scene.table.items[i].symbols[j], 910 | symbol_size*p->scene.table.symbols_t, 911 | ColorAlpha(CELL_COLOR, p->scene.table.symbols_t)); 912 | if (p->scene.table.items[i].bump[j] > 0.0) { 913 | float t = (p->scene.table.items[i].bump[j]); 914 | t *= t; 915 | symbol_in_rec(rec, 916 | p->scene.table.items[i].symbols[j], 917 | symbol_size*p->scene.table.symbols_t + (1 - t)*symbol_size*3, 918 | ColorAlpha(CELL_COLOR, p->scene.table.symbols_t*t)); 919 | } 920 | } 921 | } 922 | 923 | render_table_lines(x, y, field_width, field_height, 2, p->scene.table.count, p->scene.table.lines_t, 7.0f, CELL_COLOR); 924 | 925 | render_table_lines(x + 2*field_width + right_margin, y, field_width, field_height, 3, p->scene.table.count, p->scene.table.lines_t, 7.0f, CELL_COLOR); 926 | 927 | render_table_lines( 928 | x - head_padding/2, 929 | y - head_padding/2 + p->scene.table.head_offset_t*field_height, 930 | 2*field_width + head_padding, 931 | field_height + head_padding, 932 | 1, 1, 933 | p->scene.table.head_t, head_thick, HEAD_COLOR); 934 | } 935 | } 936 | EndMode2D(); 937 | } 938 | 939 | bool plug_finished(void) 940 | { 941 | return p->scene.finished; 942 | } 943 | 944 | #define ARENA_IMPLEMENTATION 945 | #include "arena.h" 946 | #include "tasks.c" 947 | -------------------------------------------------------------------------------- /plugs/tm/tasks.c: -------------------------------------------------------------------------------- 1 | #include "tasks.h" 2 | #include "interpolators.h" 3 | 4 | #include "raymath.h" 5 | 6 | Task_VTable task_vtable = {0}; 7 | Tag TASK_MOVE_SCALAR_TAG = 0; 8 | Tag TASK_MOVE_VEC2_TAG = 0; 9 | Tag TASK_MOVE_VEC4_TAG = 0; 10 | Tag TASK_SEQ_TAG = 0; 11 | Tag TASK_GROUP_TAG = 0; 12 | Tag TASK_WAIT_TAG = 0; 13 | 14 | bool task_update(Task task, Env env) 15 | { 16 | return task_vtable.items[task.tag].update(task.data, env); 17 | } 18 | 19 | Tag task_vtable_register(Arena *a, Task_Funcs funcs) 20 | { 21 | Tag tag = task_vtable.count; 22 | arena_da_append(a, &task_vtable, funcs); 23 | return tag; 24 | } 25 | 26 | void task_vtable_rebuild(Arena *a) 27 | { 28 | memset(&task_vtable, 0, sizeof(task_vtable)); 29 | 30 | TASK_WAIT_TAG = task_vtable_register(a, (Task_Funcs) { 31 | .update = (task_update_data_t)wait_update, 32 | }); 33 | TASK_MOVE_SCALAR_TAG = task_vtable_register(a, (Task_Funcs) { 34 | .update = (task_update_data_t)move_scalar_update, 35 | }); 36 | TASK_MOVE_VEC2_TAG = task_vtable_register(a, (Task_Funcs) { 37 | .update = (task_update_data_t)move_vec2_update, 38 | }); 39 | TASK_MOVE_VEC4_TAG = task_vtable_register(a, (Task_Funcs) { 40 | .update = (task_update_data_t)move_vec4_update, 41 | }); 42 | TASK_SEQ_TAG = task_vtable_register(a, (Task_Funcs) { 43 | .update = (task_update_data_t)seq_update, 44 | }); 45 | TASK_GROUP_TAG = task_vtable_register(a, (Task_Funcs) { 46 | .update = (task_update_data_t)group_update, 47 | }); 48 | } 49 | 50 | bool wait_done(Wait_Data *data) 51 | { 52 | return data->cursor >= data->duration; 53 | } 54 | 55 | float wait_interp(Wait_Data *data) 56 | { 57 | float t = 0.0f; 58 | if (data->duration > 0) { 59 | t = data->cursor/data->duration; 60 | } 61 | return t; 62 | } 63 | 64 | bool wait_update(Wait_Data *data, Env env) 65 | { 66 | if (wait_done(data)) return true; 67 | if (!data->started) data->started = true; 68 | data->cursor += env.delta_time; 69 | return wait_done(data); 70 | } 71 | 72 | Wait_Data wait_data(float duration) 73 | { 74 | return (Wait_Data) { .duration = duration }; 75 | } 76 | 77 | Task task_wait(Arena *a, float duration) 78 | { 79 | Wait_Data data = wait_data(duration); 80 | return (Task) { 81 | .tag = TASK_WAIT_TAG, 82 | .data = arena_memdup(a, &data, sizeof(data)), 83 | }; 84 | } 85 | 86 | bool move_scalar_update(Move_Scalar_Data *data, Env env) 87 | { 88 | if (wait_done(&data->wait)) return true; 89 | 90 | if (!data->wait.started && data->value) { 91 | data->start = *data->value; 92 | } 93 | 94 | bool finished = wait_update(&data->wait, env); 95 | 96 | if (data->value) { 97 | *data->value = Lerp( 98 | data->start, 99 | data->target, 100 | interp_func(data->func, wait_interp(&data->wait))); 101 | } 102 | 103 | return finished; 104 | } 105 | 106 | Move_Scalar_Data move_scalar_data(float *value, float target, float duration, Interp_Func func) 107 | { 108 | return (Move_Scalar_Data) { 109 | .wait = wait_data(duration), 110 | .value = value, 111 | .target = target, 112 | .func = func, 113 | }; 114 | } 115 | 116 | Task task_move_scalar(Arena *a, float *value, float target, float duration, Interp_Func func) 117 | { 118 | Move_Scalar_Data data = move_scalar_data(value, target, duration, func); 119 | return (Task) { 120 | .tag = TASK_MOVE_SCALAR_TAG, 121 | .data = arena_memdup(a, &data, sizeof(data)), 122 | }; 123 | } 124 | 125 | bool move_vec2_update(Move_Vec2_Data *data, Env env) 126 | { 127 | if (wait_done(&data->wait)) return true; 128 | 129 | if (!data->wait.started && data->value) { 130 | data->start = *data->value; 131 | } 132 | 133 | bool finished = wait_update(&data->wait, env); 134 | 135 | if (data->value) { 136 | *data->value = Vector2Lerp( 137 | data->start, 138 | data->target, 139 | interp_func(data->func, wait_interp(&data->wait))); 140 | } 141 | return finished; 142 | } 143 | 144 | Move_Vec2_Data move_vec2_data(Vector2 *value, Vector2 target, float duration, Interp_Func func) 145 | { 146 | return (Move_Vec2_Data) { 147 | .wait = wait_data(duration), 148 | .value = value, 149 | .target = target, 150 | .func = func, 151 | }; 152 | } 153 | 154 | Task task_move_vec2(Arena *a, Vector2 *value, Vector2 target, float duration, Interp_Func func) 155 | { 156 | Move_Vec2_Data data = move_vec2_data(value, target, duration, func); 157 | return (Task) { 158 | .tag = TASK_MOVE_VEC2_TAG, 159 | .data = arena_memdup(a, &data, sizeof(data)), 160 | }; 161 | } 162 | 163 | bool move_vec4_update(Move_Vec4_Data *data, Env env) 164 | { 165 | if (wait_done(&data->wait)) return true; 166 | 167 | if (!data->wait.started && data->value) { 168 | data->start = *data->value; 169 | } 170 | 171 | bool finished = wait_update(&data->wait, env); 172 | 173 | if (data->value) { 174 | *data->value = QuaternionLerp( 175 | data->start, 176 | data->target, 177 | interp_func(data->func, wait_interp(&data->wait))); 178 | } 179 | 180 | return finished; 181 | } 182 | 183 | Move_Vec4_Data move_vec4_data(Vector4 *value, Vector4 target, float duration, Interp_Func func) 184 | { 185 | return (Move_Vec4_Data) { 186 | .wait = wait_data(duration), 187 | .value = value, 188 | .target = target, 189 | .func = func, 190 | }; 191 | } 192 | 193 | Task task_move_vec4(Arena *a, Vector4 *value, Vector4 target, float duration, Interp_Func func) 194 | { 195 | Move_Vec4_Data data = move_vec4_data(value, target, duration, func); 196 | return (Task) { 197 | .tag = TASK_MOVE_VEC4_TAG, 198 | .data = arena_memdup(a, &data, sizeof(data)), 199 | }; 200 | } 201 | 202 | bool group_update(Group_Data *data, Env env) 203 | { 204 | bool finished = true; 205 | for (size_t i = 0; i < data->tasks.count; ++i) { 206 | Task it = data->tasks.items[i]; 207 | if (!task_update(it, env)) { 208 | finished = false; 209 | } 210 | } 211 | return finished; 212 | } 213 | 214 | Task task_group_(Arena *a, ...) 215 | { 216 | Group_Data *data = (Group_Data*)arena_alloc(a, sizeof(*data)); 217 | memset(data, 0, sizeof(*data)); 218 | 219 | va_list args; 220 | va_start(args, a); 221 | for (;;) { 222 | Task task = va_arg(args, Task); 223 | if (task.data == NULL) break; 224 | arena_da_append(a, &data->tasks, task); 225 | } 226 | va_end(args); 227 | 228 | return (Task) { 229 | .tag = TASK_GROUP_TAG, 230 | .data = data, 231 | }; 232 | } 233 | 234 | bool seq_update(Seq_Data *data, Env env) 235 | { 236 | if (data->it >= data->tasks.count) return true; 237 | 238 | Task it = data->tasks.items[data->it]; 239 | if (task_update(it, env)) { 240 | data->it += 1; 241 | } 242 | 243 | return data->it >= data->tasks.count; 244 | } 245 | 246 | Task task_seq_(Arena *a, ...) 247 | { 248 | Seq_Data *data = (Seq_Data*)arena_alloc(a, sizeof(*data)); 249 | memset(data, 0, sizeof(*data)); 250 | 251 | va_list args; 252 | va_start(args, a); 253 | for (;;) { 254 | Task task = va_arg(args, Task); 255 | if (task.data == NULL) break; 256 | arena_da_append(a, &data->tasks, task); 257 | } 258 | va_end(args); 259 | 260 | return (Task) { 261 | .tag = TASK_SEQ_TAG, 262 | .data = data, 263 | }; 264 | } 265 | -------------------------------------------------------------------------------- /plugs/tm/tasks.h: -------------------------------------------------------------------------------- 1 | #ifndef TASKS_H_ 2 | #define TASKS_H_ 3 | 4 | #include "env.h" 5 | #include "arena.h" 6 | #include "interpolators.h" 7 | 8 | typedef size_t Tag; 9 | 10 | typedef struct { 11 | Tag tag; 12 | void *data; 13 | } Task; 14 | 15 | typedef bool (*task_update_data_t)(void*, Env); 16 | 17 | typedef struct { 18 | task_update_data_t update; 19 | } Task_Funcs; 20 | 21 | bool task_update(Task task, Env env); 22 | 23 | typedef struct { 24 | Task_Funcs *items; 25 | size_t count; 26 | size_t capacity; 27 | } Task_VTable; 28 | 29 | extern Task_VTable task_vtable; 30 | extern Tag TASK_WAIT_TAG; 31 | extern Tag TASK_MOVE_SCALAR_TAG; 32 | extern Tag TASK_MOVE_VEC2_TAG; 33 | extern Tag TASK_MOVE_VEC4_TAG; 34 | extern Tag TASK_SEQ_TAG; 35 | extern Tag TASK_GROUP_TAG; 36 | 37 | Tag task_vtable_register(Arena *a, Task_Funcs funcs); 38 | void task_vtable_rebuild(Arena *a); 39 | 40 | typedef struct { 41 | Task *items; 42 | size_t count; 43 | size_t capacity; 44 | } Tasks; 45 | 46 | typedef struct { 47 | bool started; 48 | float cursor; 49 | float duration; 50 | } Wait_Data; 51 | 52 | float wait_interp(Wait_Data *data); 53 | bool wait_done(Wait_Data *data); 54 | bool wait_update(Wait_Data *data, Env env); 55 | Wait_Data wait_data(float duration); 56 | Task task_wait(Arena *a, float duration); 57 | 58 | typedef struct { 59 | Wait_Data wait; 60 | float *value; 61 | float start, target; 62 | Interp_Func func; 63 | } Move_Scalar_Data; 64 | 65 | bool move_scalar_update(Move_Scalar_Data *data, Env env); 66 | Move_Scalar_Data move_scalar_data(float *value, float target, float duration, Interp_Func func); 67 | Task task_move_scalar(Arena *a, float *value, float target, float duration, Interp_Func); 68 | 69 | typedef struct { 70 | Wait_Data wait; 71 | Vector2 *value; 72 | Vector2 start, target; 73 | Interp_Func func; 74 | } Move_Vec2_Data; 75 | 76 | bool move_vec2_update(Move_Vec2_Data *data, Env env); 77 | Move_Vec2_Data move_vec2_data(Vector2 *value, Vector2 target, float duration, Interp_Func func); 78 | Task task_move_vec2(Arena *a, Vector2 *value, Vector2 target, float duration, Interp_Func func); 79 | 80 | typedef struct { 81 | Wait_Data wait; 82 | Vector4 *value; 83 | Vector4 start, target; 84 | Interp_Func func; 85 | } Move_Vec4_Data; 86 | 87 | bool move_vec4_update(Move_Vec4_Data *data, Env env); 88 | Move_Vec4_Data move_vec4_data(Vector4 *value, Vector4 target, float duration, Interp_Func func); 89 | Task task_move_vec4(Arena *a, Vector4 *value, Vector4 target, float duration, Interp_Func func); 90 | 91 | typedef struct { 92 | Tasks tasks; 93 | } Group_Data; 94 | 95 | bool group_update(Group_Data *data, Env env); 96 | Task task_group_(Arena *a, ...); 97 | #define task_group(...) task_group_(__VA_ARGS__, (Task){0}) 98 | 99 | typedef struct { 100 | Tasks tasks; 101 | size_t it; 102 | } Seq_Data; 103 | 104 | bool seq_update(Seq_Data *data, Env env); 105 | Task task_seq_(Arena *a, ...); 106 | #define task_seq(...) task_seq_(__VA_ARGS__, (Task){0}) 107 | 108 | #endif // TASKS_H_ 109 | -------------------------------------------------------------------------------- /raylib/raylib-5.0_linux_amd64/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2023 Ramon Santamaria (@raysan5) 2 | 3 | This software is provided "as-is", without any express or implied warranty. In no event 4 | will the authors be held liable for any damages arising from the use of this software. 5 | 6 | Permission is granted to anyone to use this software for any purpose, including commercial 7 | applications, and to alter it and redistribute it freely, subject to the following restrictions: 8 | 9 | 1. The origin of this software must not be misrepresented; you must not claim that you 10 | wrote the original software. If you use this software in a product, an acknowledgment 11 | in the product documentation would be appreciated but is not required. 12 | 13 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented 14 | as being the original software. 15 | 16 | 3. This notice may not be removed or altered from any source distribution. 17 | -------------------------------------------------------------------------------- /raylib/raylib-5.0_linux_amd64/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **raylib is a simple and easy-to-use library to enjoy videogames programming.** 4 | 5 | raylib is highly inspired by Borland BGI graphics lib and by XNA framework and it's especially well suited for prototyping, tooling, graphical applications, embedded systems and education. 6 | 7 | *NOTE for ADVENTURERS: raylib is a programming library to enjoy videogames programming; no fancy interface, no visual helpers, no debug button... just coding in the most pure spartan-programmers way.* 8 | 9 | Ready to learn? Jump to [code examples!](https://www.raylib.com/examples.html) 10 | 11 | --- 12 | 13 |
14 | 15 | [![GitHub Releases Downloads](https://img.shields.io/github/downloads/raysan5/raylib/total)](https://github.com/raysan5/raylib/releases) 16 | [![GitHub Stars](https://img.shields.io/github/stars/raysan5/raylib?style=flat&label=stars)](https://github.com/raysan5/raylib/stargazers) 17 | [![GitHub commits since tagged version](https://img.shields.io/github/commits-since/raysan5/raylib/4.5.0)](https://github.com/raysan5/raylib/commits/master) 18 | [![GitHub Sponsors](https://img.shields.io/github/sponsors/raysan5?label=sponsors)](https://github.com/sponsors/raysan5) 19 | [![Packaging Status](https://repology.org/badge/tiny-repos/raylib.svg)](https://repology.org/project/raylib/versions) 20 | [![License](https://img.shields.io/badge/license-zlib%2Flibpng-blue.svg)](LICENSE) 21 | 22 | [![Discord Members](https://img.shields.io/discord/426912293134270465.svg?label=Discord&logo=discord)](https://discord.gg/raylib) 23 | [![Subreddit Subscribers](https://img.shields.io/reddit/subreddit-subscribers/raylib?label=reddit%20r%2Fraylib&logo=reddit)](https://www.reddit.com/r/raylib/) 24 | [![Youtube Subscribers](https://img.shields.io/youtube/channel/subscribers/UC8WIBkhYb5sBNqXO1mZ7WSQ?style=flat&label=Youtube&logo=youtube)](https://www.youtube.com/c/raylib) 25 | [![Twitch Status](https://img.shields.io/twitch/status/raysan5?style=flat&label=Twitch&logo=twitch)](https://www.twitch.tv/raysan5) 26 | 27 | [![Windows](https://github.com/raysan5/raylib/workflows/Windows/badge.svg)](https://github.com/raysan5/raylib/actions?query=workflow%3AWindows) 28 | [![Linux](https://github.com/raysan5/raylib/workflows/Linux/badge.svg)](https://github.com/raysan5/raylib/actions?query=workflow%3ALinux) 29 | [![macOS](https://github.com/raysan5/raylib/workflows/macOS/badge.svg)](https://github.com/raysan5/raylib/actions?query=workflow%3AmacOS) 30 | [![WebAssembly](https://github.com/raysan5/raylib/workflows/WebAssembly/badge.svg)](https://github.com/raysan5/raylib/actions?query=workflow%3AWebAssembly) 31 | 32 | [![CMakeBuilds](https://github.com/raysan5/raylib/workflows/CMakeBuilds/badge.svg)](https://github.com/raysan5/raylib/actions?query=workflow%3ACMakeBuilds) 33 | [![Windows Examples](https://github.com/raysan5/raylib/actions/workflows/windows_examples.yml/badge.svg)](https://github.com/raysan5/raylib/actions/workflows/windows_examples.yml) 34 | [![Linux Examples](https://github.com/raysan5/raylib/actions/workflows/linux_examples.yml/badge.svg)](https://github.com/raysan5/raylib/actions/workflows/linux_examples.yml) 35 | 36 | features 37 | -------- 38 | - **NO external dependencies**, all required libraries are [bundled into raylib](https://github.com/raysan5/raylib/tree/master/src/external) 39 | - Multiple platforms supported: **Windows, Linux, MacOS, RPI, Android, HTML5... and more!** 40 | - Written in plain C code (C99) using PascalCase/camelCase notation 41 | - Hardware accelerated with OpenGL (**1.1, 2.1, 3.3, 4.3, ES 2.0, ES 3.0**) 42 | - **Unique OpenGL abstraction layer** (usable as standalone module): [rlgl](https://github.com/raysan5/raylib/blob/master/src/rlgl.h) 43 | - Multiple **Fonts** formats supported (TTF, OTF, Image fonts, AngelCode fonts) 44 | - Multiple texture formats supported, including **compressed formats** (DXT, ETC, ASTC) 45 | - **Full 3D support**, including 3D Shapes, Models, Billboards, Heightmaps and more! 46 | - Flexible Materials system, supporting classic maps and **PBR maps** 47 | - **Animated 3D models** supported (skeletal bones animation) (IQM, M3D, glTF) 48 | - Shaders support, including model shaders and **postprocessing** shaders 49 | - **Powerful math module** for Vector, Matrix and Quaternion operations: [raymath](https://github.com/raysan5/raylib/blob/master/src/raymath.h) 50 | - Audio loading and playing with streaming support (WAV, QOA, OGG, MP3, FLAC, XM, MOD) 51 | - **VR stereo rendering** support with configurable HMD device parameters 52 | - Huge examples collection with [+140 code examples](https://github.com/raysan5/raylib/tree/master/examples)! 53 | - Bindings to [+70 programming languages](https://github.com/raysan5/raylib/blob/master/BINDINGS.md)! 54 | - **Free and open source** 55 | 56 | basic example 57 | -------------- 58 | This is a basic raylib example, it creates a window and draws the text `"Congrats! You created your first window!"` in the middle of the screen. Check this example [running live on web here](https://www.raylib.com/examples/core/loader.html?name=core_basic_window). 59 | ```c 60 | #include "raylib.h" 61 | 62 | int main(void) 63 | { 64 | InitWindow(800, 450, "raylib [core] example - basic window"); 65 | 66 | while (!WindowShouldClose()) 67 | { 68 | BeginDrawing(); 69 | ClearBackground(RAYWHITE); 70 | DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY); 71 | EndDrawing(); 72 | } 73 | 74 | CloseWindow(); 75 | 76 | return 0; 77 | } 78 | ``` 79 | 80 | build and installation 81 | ---------------------- 82 | 83 | raylib binary releases for Windows, Linux, macOS, Android and HTML5 are available at the [Github Releases page](https://github.com/raysan5/raylib/releases). 84 | 85 | raylib is also available via multiple [package managers](https://github.com/raysan5/raylib/issues/613) on multiple OS distributions. 86 | 87 | #### Installing and building raylib on multiple platforms 88 | 89 | [raylib Wiki](https://github.com/raysan5/raylib/wiki#development-platforms) contains detailed instructions on building and usage on multiple platforms. 90 | 91 | - [Working on Windows](https://github.com/raysan5/raylib/wiki/Working-on-Windows) 92 | - [Working on macOS](https://github.com/raysan5/raylib/wiki/Working-on-macOS) 93 | - [Working on GNU Linux](https://github.com/raysan5/raylib/wiki/Working-on-GNU-Linux) 94 | - [Working on Chrome OS](https://github.com/raysan5/raylib/wiki/Working-on-Chrome-OS) 95 | - [Working on FreeBSD](https://github.com/raysan5/raylib/wiki/Working-on-FreeBSD) 96 | - [Working on Raspberry Pi](https://github.com/raysan5/raylib/wiki/Working-on-Raspberry-Pi) 97 | - [Working for Android](https://github.com/raysan5/raylib/wiki/Working-for-Android) 98 | - [Working for Web (HTML5)](https://github.com/raysan5/raylib/wiki/Working-for-Web-(HTML5)) 99 | - [Working anywhere with CMake](https://github.com/raysan5/raylib/wiki/Working-with-CMake) 100 | 101 | *Note that the Wiki is open for edit, if you find some issues while building raylib for your target platform, feel free to edit the Wiki or open an issue related to it.* 102 | 103 | #### Setup raylib with multiple IDEs 104 | 105 | raylib has been developed on Windows platform using [Notepad++](https://notepad-plus-plus.org/) and [MinGW GCC](https://www.mingw-w64.org/) compiler but it can be used with other IDEs on multiple platforms. 106 | 107 | [Projects directory](https://github.com/raysan5/raylib/tree/master/projects) contains several ready-to-use **project templates** to build raylib and code examples with multiple IDEs. 108 | 109 | *Note that there are lots of IDEs supported, some of the provided templates could require some review, so please, if you find some issue with a template or you think they could be improved, feel free to send a PR or open a related issue.* 110 | 111 | learning and docs 112 | ------------------ 113 | 114 | raylib is designed to be learned using [the examples](https://github.com/raysan5/raylib/tree/master/examples) as the main reference. There is no standard API documentation but there is a [**cheatsheet**](https://www.raylib.com/cheatsheet/cheatsheet.html) containing all the functions available on the library a short description of each one of them, input parameters and result value names should be intuitive enough to understand how each function works. 115 | 116 | Some additional documentation about raylib design can be found in [raylib GitHub Wiki](https://github.com/raysan5/raylib/wiki). Here are the relevant links: 117 | 118 | - [raylib cheatsheet](https://www.raylib.com/cheatsheet/cheatsheet.html) 119 | - [raylib architecture](https://github.com/raysan5/raylib/wiki/raylib-architecture) 120 | - [raylib library design](https://github.com/raysan5/raylib/wiki) 121 | - [raylib examples collection](https://github.com/raysan5/raylib/tree/master/examples) 122 | - [raylib games collection](https://github.com/raysan5/raylib-games) 123 | 124 | 125 | contact and networks 126 | --------------------- 127 | 128 | raylib is present in several networks and raylib community is growing everyday. If you are using raylib and enjoying it, feel free to join us in any of these networks. The most active network is our [Discord server](https://discord.gg/raylib)! :) 129 | 130 | - Webpage: [https://www.raylib.com](https://www.raylib.com) 131 | - Discord: [https://discord.gg/raylib](https://discord.gg/raylib) 132 | - Twitter: [https://www.twitter.com/raysan5](https://www.twitter.com/raysan5) 133 | - Twitch: [https://www.twitch.tv/raysan5](https://www.twitch.tv/raysan5) 134 | - Reddit: [https://www.reddit.com/r/raylib](https://www.reddit.com/r/raylib) 135 | - Patreon: [https://www.patreon.com/raylib](https://www.patreon.com/raylib) 136 | - YouTube: [https://www.youtube.com/channel/raylib](https://www.youtube.com/c/raylib) 137 | 138 | contributors 139 | ------------ 140 | 141 | 142 | 143 | 144 | 145 | license 146 | ------- 147 | 148 | raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, BSD-like license that allows static linking with closed source software. Check [LICENSE](LICENSE) for further details. 149 | 150 | raylib uses internally some libraries for window/graphics/inputs management and also to support different file formats loading, all those libraries are embedded with and are available in [src/external](https://github.com/raysan5/raylib/tree/master/src/external) directory. Check [raylib dependencies LICENSES](https://github.com/raysan5/raylib/wiki/raylib-dependencies) on [raylib Wiki](https://github.com/raysan5/raylib/wiki) for details. 151 | -------------------------------------------------------------------------------- /raylib/raylib-5.0_linux_amd64/lib/libraylib.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/raylib/raylib-5.0_linux_amd64/lib/libraylib.a -------------------------------------------------------------------------------- /raylib/raylib-5.0_linux_amd64/lib/libraylib.so: -------------------------------------------------------------------------------- 1 | libraylib.so.500 -------------------------------------------------------------------------------- /raylib/raylib-5.0_linux_amd64/lib/libraylib.so.5.0.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/panim/fb77e5e009c55cf677508fb7c6feeae0dfef24b7/raylib/raylib-5.0_linux_amd64/lib/libraylib.so.5.0.0 -------------------------------------------------------------------------------- /raylib/raylib-5.0_linux_amd64/lib/libraylib.so.500: -------------------------------------------------------------------------------- 1 | libraylib.so.5.0.0 --------------------------------------------------------------------------------