├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── build.zig ├── data ├── blocks8-v1.png ├── blocks8-v1.xcf ├── boot.script ├── font.png ├── font.xcf ├── postproc.frag ├── postproc.vert ├── screenshot.jpg └── teapot.obj ├── noise.md └── src ├── blocks.c ├── blocks.h ├── common.h ├── easing.h ├── game.h ├── gen.c ├── gen.h ├── geometry.c ├── geometry.h ├── images.h ├── main.c ├── map.c ├── map.h ├── math3d.c ├── math3d.h ├── noise.c ├── noise.h ├── objfile.c ├── objfile.h ├── player.c ├── player.h ├── rnd.h ├── roam_linux.c ├── roam_windows.c ├── script.c ├── script.h ├── shaders.h ├── sky.c ├── sky.h ├── stb.c ├── sys.c ├── u8.c ├── u8.h ├── ui.c └── ui.h /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | roam 3 | #*.*# 4 | *~ 5 | TAGS 6 | roam-????-??-??-*.png 7 | zig-cache/ 8 | zig-out/ 9 | .cache/ 10 | 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "stb"] 2 | path = stb 3 | url = https://github.com/nothings/stb 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # R O A M 2 | 3 | A first-person exploration game with voxel terrain a la Minecraft. At 4 | least, that's the eventual goal. Right now it's a non-functional 5 | Minecraft-like, but I'm going to go a different direction, I think. 6 | 7 | Released under the most liberal license I could find (see LICENSE). 8 | 9 | ![screenshot](https://raw.githubusercontent.com/krig/roam/78a696e2835fe0f5cd1d7557d1d687cee171a56c/data/screenshot.jpg) 10 | 11 | ## Features 12 | 13 | Right now, not much. 14 | 15 | * Day/night cycle 16 | * Place/remove blocks 17 | * Infinite terrain 18 | 19 | ## Dependencies 20 | 21 | * Zig 0.12.0 (for building) 22 | * OpenGL 3.3 23 | * SDL 2 24 | * GLEW 25 | 26 | ## Building 27 | 28 | With all dependencies installed and zig somewhere in path (get it from ziglang.org), 29 | this command should build the project and launch the executable: 30 | 31 | ``` 32 | zig build run 33 | ``` 34 | 35 | ## map / chunk structure redesign 36 | 37 | So right now I only have one big cube of block data. However, that's 38 | very limited - I'd like to extend the limits on world height / depth 39 | by quite a bit, but every block extension in the Y direction costs XZ 40 | blocks of storage, so the cost is needlessly high (because this is 41 | something that wouldn't actually be used most of the time). 42 | Also, sub-chunks that are all air shouldn't need to be stored at all. 43 | 44 | Instead, I want to change this: 45 | 46 | 1. keep a virtual address table of chunks, not blocks. 47 | 2. Each chunk has an array of pointers to subchunks of blocks. 48 | 49 | To look up a specific block, we calculate the virtual address of the 50 | chunk that contain that block, and then look up the block within the 51 | chunk. In there, we need to figure out if the sub-chunk is allocated 52 | at all, and if so get the block from there. So block lookup becomes 53 | more expensive, but the benefit is that we can dig extremely deep and 54 | have more terrain variation (valleys, mountains, etc). 55 | 56 | Downside is that when meshing, block lookup will be a lot more 57 | awkward. we basically need to locate the block data for all the 58 | surrounding chunks as well. That's mostly just a performance problem, 59 | and not likely to be a big performance problem. 60 | 61 | Block data is always managed in 16x16x16 chunks, so a simple 62 | single-sized allocator with freelist in the blocks themselves would 63 | work. But to keep the chunks statically sized we need to decide on a 64 | maximum size anyway (although less limited). Well, doable. 65 | 66 | I could chain-allocate larger blocks. 67 | 68 | TODO: 69 | 70 | * Proper memory allocator / memory tracking 71 | * Better visualisation 72 | * builtin HTTP/REST server for tweak controls 73 | * entities 74 | * better terrain generation 75 | * biomes 76 | * features - plants, structures 77 | * terrain editor 78 | * some kind of ui handling (imgui?) 79 | * entities - friendly animals, hostile animals 80 | * inventory, tools, weapons, health 81 | * audio 82 | * physics 83 | * fix controls 84 | 85 | 86 | 87 | ## thoughts on terrain generation 88 | 89 | to generate chunk (X, Z) to level N+1, 90 | require that chunks (X+-1,Z+-1) are generated to level N 91 | 92 | Level 0: Biome determination for each block in chunk (smoothed 2d function (temperature+humidity+elevation maps?) 93 | Level 1: Height map generation - blending between biomes is the tricky 94 | part here... want to allow completely different functions for 95 | different biomes, but need to cross-blend somehow. 96 | Level 2: Ore placement 97 | Level 3: Caves generation - not sure how/if I want to do this.. 98 | Level 4: Water generation - lakes, rivers - use humidity map 99 | Level 5: Trees generation - again, humidity, biome.. 100 | Level 6: Structure generation 101 | Level 7: Decoration (plants, mushrooms) 102 | 103 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | const exe = b.addExecutable(.{ 8 | .name = "roam", 9 | .target = target, 10 | .optimize = optimize, 11 | }); 12 | exe.linkLibC(); 13 | exe.linkSystemLibrary("glew"); 14 | exe.linkSystemLibrary("sdl2"); 15 | if (target.result.os.tag == .macos) { 16 | exe.linkFramework("OpenGL"); 17 | } else { 18 | exe.linkSystemLibrary("gl"); 19 | } 20 | exe.addIncludePath(.{ .cwd_relative = "stb/" }); 21 | exe.addIncludePath(.{ .cwd_relative = "src/" }); 22 | const sources: []const []const u8 = if (target.result.os.tag == .windows) 23 | &[_][]const u8{"roam_windows.c"} 24 | else 25 | &[_][]const u8{"roam_linux.c"}; 26 | exe.addCSourceFiles(.{ 27 | .root = b.path("src"), 28 | .files = sources, 29 | .flags = &.{ 30 | "-fno-sanitize=undefined", 31 | "-DNDEBUG", 32 | }, 33 | }); 34 | 35 | b.installArtifact(exe); 36 | 37 | const run_cmd = b.addRunArtifact(exe); 38 | 39 | run_cmd.step.dependOn(b.getInstallStep()); 40 | 41 | if (b.args) |args| { 42 | run_cmd.addArgs(args); 43 | } 44 | 45 | const run_step = b.step("run", "Run the game"); 46 | run_step.dependOn(&run_cmd.step); 47 | } 48 | -------------------------------------------------------------------------------- /data/blocks8-v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krig/roam/9e4db093e13ce637297e7ce1c53c2e2df4ec072d/data/blocks8-v1.png -------------------------------------------------------------------------------- /data/blocks8-v1.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krig/roam/9e4db093e13ce637297e7ce1c53c2e2df4ec072d/data/blocks8-v1.xcf -------------------------------------------------------------------------------- /data/boot.script: -------------------------------------------------------------------------------- 1 | game.day_length = 1200 2 | game.fast_day_length = 5 3 | player.accel = 120 4 | player.friction = 0.2 5 | player.gravity = -10 6 | player.flyfriction = 0.05 7 | player.flyspeed = 30 8 | player.height = 2.0 9 | player.crouchheight = 1.5 10 | player.camoffset = 1.8 11 | player.crouchcamoffset = 0.8 12 | ui.scale = 2 13 | vsync on 14 | -------------------------------------------------------------------------------- /data/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krig/roam/9e4db093e13ce637297e7ce1c53c2e2df4ec072d/data/font.png -------------------------------------------------------------------------------- /data/font.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krig/roam/9e4db093e13ce637297e7ce1c53c2e2df4ec072d/data/font.xcf -------------------------------------------------------------------------------- /data/postproc.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | uniform sampler2D fbo_texture; 3 | in vec2 f_texcoord; 4 | layout(location = 0) out vec4 fragment; 5 | 6 | 7 | void main(void) { 8 | vec4 tex = texture(fbo_texture, f_texcoord); 9 | fragment = tex; 10 | } 11 | -------------------------------------------------------------------------------- /data/postproc.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | layout(location = 0) in vec2 v_coord; 3 | out vec2 f_texcoord; 4 | 5 | void main(void) { 6 | f_texcoord = (v_coord + 1.0) / 2.0; 7 | gl_Position = vec4(v_coord, 0.0, 1.0); 8 | } 9 | -------------------------------------------------------------------------------- /data/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krig/roam/9e4db093e13ce637297e7ce1c53c2e2df4ec072d/data/screenshot.jpg -------------------------------------------------------------------------------- /noise.md: -------------------------------------------------------------------------------- 1 | From https://code.google.com/archive/p/fractalterraingeneration/wikis/Fractional_Brownian_Motion.wiki 2 | 3 | Octaves are how many layers you are putting together. If you start 4 | with big features, the number of octaves determines how detailed the 5 | map will look. 6 | 7 | The frequency of a layer is how many points fit into the space you've 8 | created. So for the mountain scale, you only need a few points, but at 9 | the rock scale you may need hundreds of points. In the code above, I 10 | start with a small frequency (which equates to large features) and 11 | move to higher frequencies which produce smaller details. 12 | 13 | The amplitude is how tall the features should be. Frequency determines 14 | the width of features, amplitude determines the height. Each octave 15 | the amplitude shrinks, meaning small features are also short. This 16 | doesn't have to be the case, but for this case it makes pleasing maps. 17 | 18 | Lacunarity is what makes the frequency grow. Each octave the frequency 19 | is multiplied by the lacunarity. I use a lacunarity of 2.0, however 20 | values of 1.8715 or 2.1042 can help to reduce artifacts in some 21 | algorithms. A lacunarity of 2.0 means that the frequency doubles each 22 | octave, so if the first octave had 3 points the second would have 6, 23 | then 12, then 24, etc. This is used almost exclusively, partly because 24 | octaves in music double in frequency. Other values are perfectly 25 | acceptable, but the results will vary. 26 | 27 | Gain, also called persistence, is what makes the amplitude shrink (or 28 | not shrink). Each octave the amplitude is multiplied by the gain. I 29 | use a gain of 0.65. If it is higher then the amplitude will barely 30 | shrink, and maps get crazy. Too low and the details become miniscule, 31 | and the map looks washed out. However, most use 1/lacunarity. Since 32 | the standard for lacunarity is 2.0, the standard for the gain is 33 | 0.5. Noise that has a gain of 0.5 and a lacunarity of 2.0 is referred 34 | to as 1/f noise, and is the industry standard. 35 | -------------------------------------------------------------------------------- /src/blocks.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "math3d.h" 3 | #include "images.h" 4 | #include "blocks.h" 5 | 6 | struct blockinfo blockinfo[] = { 7 | { .name = "air", 8 | .img = { 0 } 9 | }, 10 | { .name = "grass", 11 | .img = { IMG_GREEN_GRASS }, 12 | .density = SOLID_DENSITY, 13 | .flags = BLOCK_COLLIDER 14 | }, 15 | { .name = "wet grass", 16 | .img = { IMG_WET_GRASS, IMG_WET_DIRT, IMG_WET_GRASS_SIDE }, 17 | .density = SOLID_DENSITY, 18 | .flags = BLOCK_COLLIDER 19 | }, 20 | { .name = "dirt", 21 | .img = { IMG_DIRT }, 22 | .density = SOLID_DENSITY, 23 | .flags = BLOCK_COLLIDER 24 | }, 25 | { .name = "wet dirt", 26 | .img = { IMG_WET_DIRT }, 27 | .density = SOLID_DENSITY, 28 | .flags = BLOCK_COLLIDER 29 | }, 30 | { .name = "solid dirt", 31 | .img = { IMG_SOLID_DIRT }, 32 | .density = SOLID_DENSITY, 33 | .flags = BLOCK_COLLIDER 34 | }, 35 | { .name = "stone", 36 | .img = { IMG_LIGHT_STONE }, 37 | .density = SOLID_DENSITY, 38 | .flags = BLOCK_COLLIDER 39 | }, 40 | { .name = "clay", 41 | .img = { IMG_CLAY }, 42 | .density = SOLID_DENSITY, 43 | .flags = BLOCK_COLLIDER 44 | }, 45 | { .name = "snow", 46 | .img = { IMG_SNOW }, 47 | .density = SOLID_DENSITY, 48 | .flags = BLOCK_COLLIDER 49 | }, 50 | { .name = "mint", 51 | .img = { IMG_MINT }, 52 | .density = WATER_DENSITY, 53 | .flags = BLOCK_COLLIDER | BLOCK_ALPHA 54 | }, 55 | { .name = "pig", 56 | .img = { IMG_PIGBACK, IMG_PIG_SKIN, IMG_PIGBACK, IMG_PIGBACK, IMG_PIG_HEAD, IMG_PIG_SKIN }, 57 | .density = SOLID_DENSITY, 58 | .flags = BLOCK_COLLIDER 59 | }, 60 | { .name = "meat", 61 | .img = { IMG_MEAT_TOP, IMG_MEAT_TOP, IMG_MEAT_SIDE }, 62 | .density = SOLID_DENSITY, 63 | .flags = BLOCK_COLLIDER 64 | }, 65 | { .name = "torch", 66 | .img = { IMG_TORCH_TOP, IMG_SWORD_HILT, IMG_TORCH_SIDE }, 67 | .density = SOLID_DENSITY, 68 | .flags = BLOCK_COLLIDER 69 | }, 70 | { .name = "melon", 71 | .img = { IMG_MELON_CUT, IMG_MELON_SKIN, IMG_MELON_SIDE }, 72 | .density = SOLID_DENSITY, 73 | .flags = BLOCK_COLLIDER 74 | }, 75 | { .name = "tree", 76 | .img = { IMG_TREE_CUT, IMG_TREE_CUT, IMG_TREE }, 77 | .density = SOLID_DENSITY, 78 | .flags = BLOCK_COLLIDER 79 | }, 80 | { .name = "birch", 81 | .img = { IMG_BIRCH_CUT, IMG_BIRCH_CUT, IMG_BIRCH_TREE }, 82 | .density = SOLID_DENSITY, 83 | .flags = BLOCK_COLLIDER 84 | }, 85 | { .name = "oak", 86 | .img = { IMG_OAK_CUT, IMG_OAK_CUT, IMG_OAK_TREE }, 87 | .density = SOLID_DENSITY, 88 | .flags = BLOCK_COLLIDER 89 | }, 90 | { .name = "palm", 91 | .img = { IMG_PALM_CUT, IMG_PALM_CUT, IMG_PALM_TREE }, 92 | .density = SOLID_DENSITY, 93 | .flags = BLOCK_COLLIDER 94 | }, 95 | { .name = "snow grass 1", 96 | .img = { IMG_SNOW, IMG_WET_GRASS, IMG_GRASS_SNOW_1 }, 97 | .density = SOLID_DENSITY, 98 | .flags = BLOCK_COLLIDER 99 | }, 100 | { .name = "snow grass 2", 101 | .img = { IMG_SNOW, IMG_WET_GRASS, IMG_GRASS_SNOW_2 }, 102 | .density = SOLID_DENSITY, 103 | .flags = BLOCK_COLLIDER 104 | }, 105 | { .name = "test cube", 106 | .img = { IMG_TEST_U, IMG_TEST_D, IMG_TEST_L, IMG_TEST_R, IMG_TEST_F, IMG_TEST_B }, 107 | .density = SOLID_DENSITY, 108 | .flags = BLOCK_COLLIDER 109 | }, 110 | { .name = "black rock", 111 | .img = { IMG_BLACKROCK }, 112 | .density = SOLID_DENSITY, 113 | .flags = BLOCK_COLLIDER 114 | 115 | }, 116 | { .name = "ocean1", 117 | .img = { IMG_OCEAN1 }, 118 | .density = WATER_DENSITY, 119 | .flags = BLOCK_ALPHA 120 | }, 121 | { .name = "ocean2", 122 | .img = { IMG_OCEAN2 }, 123 | .density = WATER_DENSITY, 124 | .flags = BLOCK_ALPHA 125 | }, 126 | { .name = "ocean3", 127 | .img = { IMG_OCEAN3 }, 128 | .density = WATER_DENSITY, 129 | .flags = BLOCK_ALPHA 130 | }, 131 | { .name = "mushroom", 132 | .img = { IMG_BB_SHROOM1 }, 133 | .flags = BLOCK_SPRITE 134 | }, 135 | { .name = "test alpha", 136 | .img = { IMG_TEST_ALPHA }, 137 | .density = GLASS_DENSITY, 138 | .flags = BLOCK_COLLIDER | BLOCK_ALPHA 139 | }, 140 | { .name = "gold sand", 141 | .img = { IMG_GOLD_SAND }, 142 | .density = SOLID_DENSITY, 143 | .flags = BLOCK_COLLIDER 144 | }, 145 | { .name = "lava", 146 | .img = { IMG_LAVA }, 147 | .density = WATER_DENSITY, 148 | .flags = BLOCK_ALPHA 149 | }, 150 | { .name = "rich purple", 151 | .img = { IMG_RICH_PURPLE }, 152 | .density = SOLID_DENSITY, 153 | .flags = BLOCK_COLLIDER 154 | }, 155 | { .name = "pale pink", 156 | .img = { IMG_PALE_PINK }, 157 | .density = SOLID_DENSITY, 158 | .flags = BLOCK_COLLIDER 159 | }, 160 | { .name = "green leaves", 161 | .img = { IMG_GREEN_LEAVES }, 162 | .density = SOLID_DENSITY, 163 | .flags = BLOCK_COLLIDER 164 | }, 165 | { .name = "yellow leaves", 166 | .img = { IMG_YELLOW_LEAVES }, 167 | .density = SOLID_DENSITY, 168 | .flags = BLOCK_COLLIDER 169 | }, 170 | { .name = "ORANGE leaves", 171 | .img = { IMG_ORANGE_LEAVES }, 172 | .density = SOLID_DENSITY, 173 | .flags = BLOCK_COLLIDER 174 | }, 175 | { .name = "dark orange leaves", 176 | .img = { IMG_DARKORANGE_LEAVES }, 177 | .density = SOLID_DENSITY, 178 | .flags = BLOCK_COLLIDER 179 | }, 180 | { .name = "red leaves", 181 | .img = { IMG_RED_LEAVES }, 182 | .density = SOLID_DENSITY, 183 | .flags = BLOCK_COLLIDER 184 | }, 185 | { .name = "deep rock", 186 | .img = { IMG_DEEP_ROCK }, 187 | .density = SOLID_DENSITY, 188 | .flags = BLOCK_COLLIDER 189 | }, 190 | }; 191 | 192 | void blocks_init() 193 | { 194 | for (unsigned int i = 0; i < ASIZE(blockinfo); ++i) { 195 | int prev = IMG_BLACKROCK; 196 | for (int m = 0; m < 6; ++m) { 197 | if (blockinfo[i].img[m] == IMG_INVALID) 198 | blockinfo[i].img[m] = prev; 199 | else 200 | prev = blockinfo[i].img[m]; 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/blocks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define AIR_DENSITY 0 4 | #define WATER_DENSITY 100 5 | #define GLASS_DENSITY 500 6 | #define SOLID_DENSITY 1000 7 | 8 | enum BlockTex { 9 | BLOCK_TEX_TOP, 10 | BLOCK_TEX_BOTTOM, 11 | BLOCK_TEX_LEFT, 12 | BLOCK_TEX_RIGHT, 13 | BLOCK_TEX_FRONT, 14 | BLOCK_TEX_BACK 15 | }; 16 | 17 | enum BlockFlags { 18 | BLOCK_COLLIDER = 1, // collide with player 19 | BLOCK_ALPHA = 2, // draw in alpha pass 20 | BLOCK_MESH = 4, // custom mesh shape 21 | BLOCK_SPRITE = 8, // render without backface culling 22 | BLOCK_SLOPE = 0x10, // can walk up/down this block 23 | }; 24 | 25 | enum BlockTypes { 26 | BLOCK_AIR, 27 | BLOCK_GRASS, 28 | BLOCK_WET_GRASS, 29 | BLOCK_DIRT, 30 | BLOCK_WET_DIRT, 31 | BLOCK_SOLID_DIRT, 32 | BLOCK_STONE, 33 | BLOCK_CLAY, 34 | BLOCK_SNOW, 35 | BLOCK_MINT, 36 | BLOCK_PIG, 37 | BLOCK_MEAT, 38 | BLOCK_TORCH, 39 | BLOCK_MELON, 40 | BLOCK_TREE, 41 | BLOCK_BIRCH, 42 | BLOCK_OAK, 43 | BLOCK_PALM, 44 | BLOCK_SNOWY_GRASS_1, 45 | BLOCK_SNOWY_GRASS_2, 46 | BLOCK_TESTCUBE, 47 | BLOCK_BLACKROCK, 48 | BLOCK_OCEAN1, 49 | BLOCK_OCEAN2, 50 | BLOCK_OCEAN3, 51 | BLOCK_MUSHROOM, 52 | BLOCK_TEST_ALPHA, 53 | BLOCK_GOLD_SAND, 54 | BLOCK_LAVA, 55 | BLOCK_RICH_PURPLE, 56 | BLOCK_PALE_PINK, 57 | BLOCK_GREEN_LEAVES, 58 | BLOCK_YELLOW_LEAVES, 59 | BLOCK_ORANGE_LEAVES, 60 | BLOCK_DARKORANGE_LEAVES, 61 | BLOCK_RED_LEAVES, 62 | BLOCK_DEEP_ROCK, 63 | NUM_BLOCKTYPES 64 | }; 65 | 66 | 67 | struct blockinfo { 68 | const char* name; // short description 69 | // image atlas index 70 | // 0 = fill with previous 71 | int img[6]; 72 | 73 | uint32_t flags; 74 | int density; // relative density is used when meshing 75 | int light; // light emitted by block (rgb) 76 | int attenuation; // light attenuation (used when lighting) 77 | 78 | // todo: custom shape? 79 | // maybe this has to be hardcoded in the tesselator for each blocktype 80 | }; 81 | 82 | extern struct blockinfo blockinfo[]; 83 | 84 | 85 | void blocks_init(void); 86 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | #define M_CHECKGL_ENABLED 1 17 | 18 | 19 | #define ASIZE(a) ((sizeof(a)/sizeof((a)[0]))) 20 | #define STATIC_ASSERT(e) do { enum { static_assert__ = 1/(e) }; } while (0) 21 | 22 | 23 | // System functions 24 | 25 | char* sys_readfile(const char* filename); 26 | char* sys_readfile_realloc(const char* filename, char* buffer, size_t* len); 27 | int sys_isfile(const char* filename); 28 | uint64_t sys_urandom(void); 29 | int64_t sys_timems(void); 30 | 31 | 32 | // Common utility functions 33 | 34 | static inline noreturn 35 | void fatal_error(const char* msg, ...) 36 | { 37 | char buf[512]; 38 | va_list va_args; 39 | va_start(va_args, msg); 40 | vsnprintf(buf, 512, msg, va_args); 41 | va_end(va_args); 42 | fprintf(stderr, "Error: %s\n", buf); 43 | exit(1); 44 | } 45 | 46 | // Replacements for str(n)cpy, str(n)cat 47 | // Rationale: http://byuu.org/articles/programming/strcpycat 48 | // length argument includes null-terminator 49 | // returns: strlen(target) 50 | static inline 51 | unsigned strmcpy(char *target, const char *source, unsigned length) 52 | { 53 | const char *origin = target; 54 | if(length) { while(*source && --length) *target++ = *source++; *target = 0; } 55 | return target - origin; 56 | } 57 | 58 | // Replacements for str(n)cpy, str(n)cat 59 | // Rationale: http://byuu.org/articles/programming/strcpycat 60 | // length argument includes null-terminator 61 | // returns: strlen(target) 62 | static inline 63 | unsigned strmcat(char *target, const char *source, unsigned length) 64 | { 65 | const char *origin = target; 66 | while(*target && length) target++, length--; 67 | return (target - origin) + strmcpy(target, source, length); 68 | } 69 | -------------------------------------------------------------------------------- /src/easing.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifndef M_PI 6 | #define M_PI 3.14159265358979323846 /* pi */ 7 | #endif 8 | 9 | #ifndef M_PI_2 10 | #define M_PI_2 1.57079632679489661923 /* pi/2 */ 11 | #endif 12 | 13 | /* 14 | Easing functions 15 | by Kristoffer Gronlund, 2014 16 | Public domain 17 | 18 | This work is a spiritual descendent (not to say derivative work) of works done by the following individuals: 19 | 20 | Warren Moore (https://github.com/warrenm) 21 | Robert Penner (http://www.robertpenner.com/easing/) 22 | George McGinley Smith (http://gsgd.co.uk/sandbox/jquery/easing/) 23 | James Padolsey (http://james.padolsey.com/demos/jquery/easing/) 24 | Authors of jQuery (http://plugins.jquery.com/project/Easing) 25 | Matt Gallagher (http://cocoawithlove.com/2008/09/parametric-acceleration-curves-in-core.html) 26 | Jesse Crossen (http://stackoverflow.com/questions/5161465/how-to-create-custom-easing-function-with-core-animation) 27 | */ 28 | 29 | 30 | typedef float easing_t; 31 | 32 | 33 | static inline 34 | easing_t enEase(easing_t a, easing_t b, easing_t t) 35 | { 36 | return a + (b - a) * t; 37 | } 38 | 39 | 40 | static inline 41 | easing_t enLinear(easing_t t) 42 | { 43 | return t; 44 | } 45 | 46 | 47 | static inline 48 | easing_t enQuadraticIn(easing_t t) 49 | { 50 | return t * t; 51 | } 52 | 53 | 54 | static inline 55 | easing_t enQuadraticOut(easing_t t) 56 | { 57 | return -(t * (t - 2.)); 58 | } 59 | 60 | 61 | static inline 62 | easing_t enQuadraticInOut(easing_t t) 63 | { 64 | return (t < 0.5) ? 2. * t * t : (-2. * t * t) + (4. * t) - 1.; 65 | } 66 | 67 | 68 | static inline 69 | easing_t enCubicIn(easing_t t) 70 | { 71 | return t * t * t; 72 | } 73 | 74 | 75 | static inline 76 | easing_t enCubicOut(easing_t t) 77 | { 78 | const easing_t f = t - 1.; return f * f * f + 1.; 79 | } 80 | 81 | 82 | static inline 83 | easing_t enCubicInOut(easing_t t) 84 | { 85 | if (t < (easing_t)0.5) { 86 | return 4. * t * t * t; 87 | } else { 88 | const easing_t f = (2. * t) - 2.; 89 | return 0.5 * f * f * f + 1.; 90 | } 91 | } 92 | 93 | 94 | static inline 95 | easing_t enQuarticIn(easing_t t) { 96 | return t * t * t * t; 97 | } 98 | 99 | 100 | static inline 101 | easing_t enQuarticOut(easing_t t) { 102 | const easing_t f = t - 1.; 103 | return f * f * f * (1. - t) + 1.; 104 | } 105 | 106 | 107 | static inline 108 | easing_t enQuarticInOut(easing_t t) 109 | { 110 | if(t < 0.5) { 111 | return 8. * t * t * t * t; 112 | } else { 113 | easing_t f = (t - 1.); 114 | return -8. * f * f * f * f + 1.; 115 | } 116 | } 117 | 118 | 119 | static inline 120 | easing_t enQuinticIn(easing_t t) { 121 | return t * t * t * t * t; 122 | } 123 | 124 | 125 | static inline 126 | easing_t enQuinticOut(easing_t t) { 127 | easing_t f = (t - 1.); 128 | return f * f * f * f * f + 1.; 129 | } 130 | 131 | 132 | static inline 133 | easing_t enQuinticInOut(easing_t t) 134 | { 135 | if (t < 0.5) { 136 | return 16. * t * t * t * t * t; 137 | } else { 138 | easing_t f = ((2. * t) - 2.); 139 | return 0.5 * f * f * f * f * f + 1.; 140 | } 141 | } 142 | 143 | 144 | static inline 145 | easing_t enSineIn(easing_t t) 146 | { 147 | return sin((t - 1.) * M_PI_2) + 1.; 148 | } 149 | 150 | 151 | static inline 152 | easing_t enSineOut(easing_t t) 153 | { 154 | return sin(t * M_PI_2); 155 | } 156 | 157 | 158 | static inline 159 | easing_t enSineInOut(easing_t t) 160 | { 161 | return 0.5 * (1. - cos(t * M_PI)); 162 | } 163 | 164 | 165 | static inline 166 | easing_t enCircularIn(easing_t t) 167 | { 168 | return 1. - sqrt(1. - (t * t)); 169 | } 170 | 171 | 172 | static inline 173 | easing_t enCircularOut(easing_t t) 174 | { 175 | return sqrt((2. - t) * t); 176 | } 177 | 178 | 179 | static inline 180 | easing_t enCircularInOut(easing_t t) 181 | { 182 | if (t < 0.5) { 183 | return 0.5 * (1 - sqrt(1 - 4. * (t * t))); 184 | } else { 185 | return 0.5 * (sqrt(-((2. * t) - 3.) * ((2. * t) - 1.)) + 1.); 186 | } 187 | } 188 | 189 | 190 | static inline 191 | easing_t enExponentialIn(easing_t t) 192 | { 193 | return (t <= 0) ? t : pow(2., 10. * (t - 1.)); 194 | } 195 | 196 | 197 | static inline 198 | easing_t enExponentialOut(easing_t t) 199 | { 200 | return (t >= 1.) ? t : 1. - pow(2., -10. * t); 201 | } 202 | 203 | 204 | static inline 205 | easing_t enExponentialInOut(easing_t t) 206 | { 207 | if (t <= 0. || t >= 1.) 208 | return t; 209 | 210 | if (t < 0.5) 211 | { 212 | return 0.5 * pow(2., (20. * t) - 10.); 213 | } else { 214 | return -0.5 * pow(2., (-20. * t) + 10.) + 1.; 215 | } 216 | } 217 | 218 | 219 | static inline 220 | easing_t enElasticIn(easing_t t) 221 | { 222 | return sin(13. * M_PI_2 * t) * pow(2., 10. * (t - 1.)); 223 | } 224 | 225 | 226 | static inline 227 | easing_t enElasticOut(easing_t t) 228 | { 229 | return sin(-13. * M_PI_2 * (t + 1.)) * pow(2., -10. * t) + 1.; 230 | } 231 | 232 | 233 | static inline 234 | easing_t enElasticInOut(easing_t t) 235 | { 236 | if (t < 0.5) { 237 | return 0.5 * sin(13. * M_PI_2 * (2. * t)) * pow(2., 10. * ((2. * t) - 1.)); 238 | } else { 239 | return 0.5 * (sin(-13. * M_PI_2 * ((2. * t - 1) + 1)) * pow(2., -10. * (2. * t - 1.)) + 2.); 240 | } 241 | } 242 | 243 | 244 | static inline 245 | easing_t enBackIn(easing_t t) 246 | { 247 | return t * t * t - t * sin(t * M_PI); 248 | } 249 | 250 | 251 | static inline 252 | easing_t enBackOut(easing_t t) 253 | { 254 | const easing_t f = 1. - t; 255 | return 1. - (f * f * f - f * sin(f * M_PI)); 256 | } 257 | 258 | 259 | static inline 260 | easing_t enBackInOut(easing_t t) 261 | { 262 | if (t < 0.5) { 263 | const easing_t f = 2. * t; 264 | return 0.5 * (f * f * f - f * sin(f * M_PI)); 265 | } else { 266 | const easing_t f = (1. - (2.*t - 1.)); 267 | return 0.5 * (1. - (f * f * f - f * sin(f * M_PI))) + 0.5; 268 | } 269 | } 270 | 271 | 272 | static inline 273 | easing_t enBounceOut(easing_t t) 274 | { 275 | if (t < 4. / 11.) { 276 | return (121. * t * t) / 16.; 277 | } else if (t < 8. / 11.) { 278 | return (363. / 40. * t * t) - (99 / 10. * t) + 17 / 5.; 279 | } else if (t < 9. / 10.) { 280 | return (4356. / 361. * t * t) - (35442. / 1805. * t) + 16061. / 1805.; 281 | } else { 282 | return (54. / 5. * t * t) - (513. / 25. * t) + 268. / 25.; 283 | } 284 | } 285 | 286 | 287 | static inline 288 | easing_t enBounceIn(easing_t t) 289 | { 290 | return 1. - enBounceOut(1. - t); 291 | } 292 | 293 | 294 | static inline 295 | easing_t enBounceInOut(easing_t t) 296 | { 297 | if (t < 0.5) { 298 | return 0.5 * enBounceIn(t * 2.); 299 | } else { 300 | return 0.5 * enBounceOut(t * 2. - 1.) + 0.5; 301 | } 302 | } 303 | 304 | 305 | static inline 306 | easing_t enPerlinInOut(easing_t t) 307 | { 308 | easing_t t3 = t * t * t; 309 | easing_t t4 = t3 * t; 310 | easing_t t5 = t4 * t; 311 | return 6. * t5 - 15. * t4 + 10. * t3; 312 | } 313 | -------------------------------------------------------------------------------- /src/game.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // shared data for the game 3 | 4 | #include "common.h" 5 | #include "map.h" 6 | #include "player.h" 7 | 8 | 9 | #define DAY_LENGTH 1200.0 /* seconds */ 10 | 11 | 12 | enum Materials { 13 | MAT_BASIC, 14 | MAT_UI, 15 | MAT_DEBUG, 16 | MAT_CHUNK, 17 | MAT_CHUNK_ALPHA, 18 | MAT_SKY, 19 | MAX_MATERIALS 20 | }; 21 | 22 | 23 | enum CameraMode { 24 | CAMERA_FPS, 25 | CAMERA_3RDPERSON, 26 | CAMERA_FLIGHT, 27 | NUM_CAMERA_MODES 28 | }; 29 | 30 | 31 | struct camera { 32 | dvec3_t pos; 33 | float pitch; 34 | float yaw; 35 | int mode; 36 | }; 37 | 38 | 39 | struct controls { 40 | int left; 41 | int right; 42 | int forward; 43 | int backward; 44 | int jump; 45 | int sprint; 46 | int crouch; 47 | int interact; 48 | int primary_action; 49 | int secondary_action; 50 | int wireframe; 51 | int debuginfo; 52 | }; 53 | 54 | struct statistics { 55 | int64_t frametime; // milliseconds 56 | uint64_t frames; 57 | }; 58 | 59 | 60 | struct game { 61 | struct camera camera; 62 | struct player player; 63 | struct controls controls; 64 | struct inputstate input; 65 | struct statistics stats; 66 | material_t materials[MAX_MATERIALS]; 67 | mtxstack_t projection; 68 | mtxstack_t modelview; 69 | struct game_map map; 70 | 71 | int day; // increases after every day/night cycle 72 | double time_of_day; 73 | double light_level; 74 | vec3_t amb_light; 75 | vec4_t fog_color; 76 | vec3_t light_dir; 77 | vec3_t sun_color; 78 | vec3_t sky_dark; 79 | vec3_t sky_light; 80 | 81 | bool fast_day_mode; 82 | bool debug_mode; 83 | bool enable_ground; 84 | bool game_active; 85 | bool collisions_on; 86 | bool wireframe; 87 | }; 88 | 89 | 90 | extern struct game game; 91 | 92 | 93 | 94 | static inline 95 | chunkpos_t camera_chunk() 96 | { 97 | chunkpos_t c = { 98 | floor(round(game.camera.pos.x) / CHUNK_SIZE), 99 | floor(round(game.camera.pos.z) / CHUNK_SIZE) 100 | }; 101 | return c; 102 | } 103 | 104 | static inline 105 | ivec3_t camera_block() 106 | { 107 | ivec3_t b = { 108 | round(game.camera.pos.x), 109 | round(game.camera.pos.y), 110 | round(game.camera.pos.z) 111 | }; 112 | return b; 113 | } 114 | 115 | static inline 116 | chunkpos_t player_chunk() 117 | { 118 | chunkpos_t c = { 119 | floor(round(game.player.pos.x) / CHUNK_SIZE), 120 | floor(round(game.player.pos.z) / CHUNK_SIZE) 121 | }; 122 | return c; 123 | } 124 | 125 | static inline 126 | ivec3_t player_block() 127 | { 128 | ivec3_t b = { 129 | round(game.player.pos.x), 130 | round(game.player.pos.y), 131 | round(game.player.pos.z) 132 | }; 133 | return b; 134 | } 135 | 136 | static inline 137 | bool block_eq(ivec3_t a, ivec3_t b) 138 | { 139 | return (a.x == b.x && a.y == b.y && a.z == b.z); 140 | } 141 | 142 | static inline 143 | vec3_t camera_offset() 144 | { 145 | chunkpos_t c = player_chunk(); 146 | return m_vec3(game.camera.pos.x - (double)(c.x * CHUNK_SIZE), 147 | game.camera.pos.y, 148 | game.camera.pos.z - (double)(c.z * CHUNK_SIZE)); 149 | } 150 | -------------------------------------------------------------------------------- /src/gen.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "math3d.h" 3 | #include "game.h" 4 | #include "gen.h" 5 | #include "rnd.h" 6 | #include "noise.h" 7 | #include "map.h" 8 | 9 | static 10 | void gen_testmap(game_chunk* chunk) 11 | { 12 | int x, z, blockx, blockz, fillx, filly, fillz; 13 | uint32_t* blocks; 14 | x = chunk->x; 15 | z = chunk->z; 16 | blocks = map_blocks; 17 | 18 | if (abs(x) >= 2 || abs(z) >= 2) 19 | return; 20 | 21 | blockx = x * CHUNK_SIZE; 22 | blockz = z * CHUNK_SIZE; 23 | 24 | for (fillz = blockz; fillz < blockz + CHUNK_SIZE; ++fillz) { 25 | for (fillx = blockx; fillx < blockx + CHUNK_SIZE; ++fillx) { 26 | uint32_t sunlight = 0xf; 27 | size_t idx0 = block_index(fillx, 0, fillz); 28 | for (filly = MAP_BLOCK_HEIGHT-1; filly >= 0; --filly) { 29 | uint32_t b = BLOCK_AIR; 30 | if (filly <= 32) 31 | b = BLOCK_STONE; 32 | 33 | if (b != BLOCK_AIR) 34 | sunlight = 0; 35 | blocks[idx0 + filly] = b | (sunlight << 28); 36 | } 37 | } 38 | } 39 | 40 | for (int i = 0; i < NUM_BLOCKTYPES; ++i) { 41 | int px = (i*2) % CHUNK_SIZE; 42 | int pz = ((i*2) / CHUNK_SIZE) * 2; 43 | blocks[block_index(blockx + px, 33, blockz + pz)] = i; 44 | } 45 | } 46 | 47 | static 48 | void gen_noisemap(game_chunk* chunk) 49 | { 50 | int x, z, blockx, blockz, fillx, filly, fillz; 51 | uint32_t* blocks; 52 | x = chunk->x; 53 | z = chunk->z; 54 | blocks = map_blocks; 55 | 56 | blockx = x * CHUNK_SIZE; 57 | blockz = z * CHUNK_SIZE; 58 | 59 | #define NOISE_SCALE (1.0/((double)CHUNK_SIZE * 16)) 60 | 61 | uint32_t p, b; 62 | for (fillz = blockz; fillz < blockz + CHUNK_SIZE; ++fillz) { 63 | for (fillx = blockx; fillx < blockx + CHUNK_SIZE; ++fillx) { 64 | size_t idx0 = block_index(fillx, 0, fillz); 65 | if (idx0 >= MAP_BUFFER_SIZE) { 66 | printf("bad index: %d, %d, %d\n", fillx, 0, fillz); 67 | abort(); 68 | } 69 | uint32_t sunlight = 0xf; 70 | double noise1 = fbm_simplex_2d(fillx, fillz, 0.3, NOISE_SCALE, 2.1117, 5); 71 | double noise2 = fbm_simplex_2d(fillx, fillz, 0.5, NOISE_SCALE*2.1331, 2.1117, 3); 72 | double noise3 = fbm_simplex_2d(fillx + 90.0, fillz + 90.0, 0.8, NOISE_SCALE*0.3344, 2.1117, 4); 73 | double height = 40.0 + ((noise1 - noise3*0.777) * 24.0 + noise2 * 24.0) * 0.5; 74 | double dirtdepth = 2.0 + noise2 * 8.0; 75 | double sanddepth = (noise1 * 6.0); 76 | b = BLOCK_AIR; 77 | p = BLOCK_AIR; 78 | 79 | uint32_t base = BLOCK_SOLID_DIRT; 80 | uint32_t dirt = BLOCK_WET_DIRT; 81 | uint32_t shore = BLOCK_GOLD_SAND; 82 | uint32_t ground = BLOCK_WET_GRASS; 83 | uint32_t water = BLOCK_OCEAN1; 84 | 85 | double depth = 0.0; 86 | 87 | for (filly = MAP_BLOCK_HEIGHT-1; filly >= 0; --filly) { 88 | if (filly < 2.0) { 89 | b = BLOCK_BLACKROCK; 90 | } else if (filly > height) { 91 | if (filly > OCEAN_LEVEL) { 92 | b = BLOCK_AIR; 93 | } else { 94 | b = water; 95 | } 96 | } else if (filly > (double)OCEAN_LEVEL + sanddepth) { 97 | if (p == BLOCK_AIR) { 98 | b = ground; 99 | } else if (depth > dirtdepth) { 100 | b = base; 101 | } else { 102 | b = dirt; 103 | } 104 | } else if (filly > (double)OCEAN_LEVEL - sanddepth) { 105 | if (p == BLOCK_AIR || p == shore) { 106 | b = shore; 107 | } else { 108 | b = base; 109 | } 110 | } else if (depth > dirtdepth) { 111 | b = base; 112 | } else { 113 | b = dirt; 114 | } 115 | if (b == BLOCK_AIR) { 116 | } else if (sunlight > 0 && (blockinfo[b].density < SOLID_DENSITY)) { 117 | sunlight -= 1; 118 | } else { 119 | sunlight = 0; 120 | } 121 | blocks[idx0 + filly] = b | (sunlight << 28); 122 | p = b; 123 | if (p != BLOCK_AIR && p != water) 124 | depth += 1.0; 125 | } 126 | } 127 | } 128 | } 129 | 130 | static 131 | void gen_floating(struct game_map* map, game_chunk* chunk) { 132 | 133 | int x, z, blockx, blockz, fillx, filly, fillz; 134 | uint32_t* blocks; 135 | 136 | x = chunk->x; 137 | z = chunk->z; 138 | blocks = map_blocks; 139 | blockx = x * CHUNK_SIZE; 140 | blockz = z * CHUNK_SIZE; 141 | 142 | for (fillz = blockz; fillz < blockz + CHUNK_SIZE; ++fillz) { 143 | for (fillx = blockx; fillx < blockx + CHUNK_SIZE; ++fillx) { 144 | size_t idx0 = block_index(fillx, 0, fillz); 145 | double noise2d = fbm_simplex_2d((double)fillx / MAP_BLOCK_HEIGHT, (double)fillz / MAP_BLOCK_HEIGHT, 146 | 0.45, 0.8, 2.0, 5); 147 | noise2d = (noise2d + 1.0) * 0.5; 148 | int groundy = (int)(40.0 * noise2d) + 40; 149 | 150 | int watery = 50; 151 | 152 | uint32_t p = BLOCK_AIR; 153 | uint32_t sunlight = 0xf; 154 | 155 | for (filly = MAP_BLOCK_HEIGHT-1; filly >= 0; --filly) { 156 | uint32_t b = BLOCK_AIR; 157 | if (filly < watery && (sunlight || p == BLOCK_OCEAN3)) { 158 | b = BLOCK_OCEAN3; 159 | } 160 | 161 | if (filly > 16.0 && filly < groundy) { 162 | double gradient = (double)filly / (double)MAP_BLOCK_HEIGHT; 163 | double density = opensimplex_noise_3d( 164 | (double)fillx / (double)MAP_BLOCK_HEIGHT * 15.0, 165 | (double)filly / (double)MAP_BLOCK_HEIGHT * 15.0, 166 | (double)fillz / (double)MAP_BLOCK_HEIGHT * 15.0); 167 | 168 | double density01 = (density * 0.5) + 0.5; 169 | 170 | if (density01 + (1.0 - gradient) < 0.8) { 171 | } else if (filly < groundy) { 172 | int dirt_depth = 2 + (rand64(fillx ^ filly ^ fillz) % 5); 173 | if (sunlight && (fabs(filly - watery - (density * 3.0)) < 1.5)) { 174 | b = BLOCK_GOLD_SAND; 175 | } else if (sunlight == 0xf) { 176 | b = BLOCK_WET_GRASS; 177 | } else if (groundy - filly > dirt_depth) { 178 | b = BLOCK_DEEP_ROCK; 179 | } else { 180 | b = BLOCK_WET_DIRT; 181 | } 182 | } 183 | } else if (filly < groundy) { 184 | b = BLOCK_WET_DIRT; 185 | } 186 | if (b == BLOCK_AIR) { 187 | } else if (sunlight > 0 && (blockinfo[b].density < SOLID_DENSITY)) { 188 | sunlight--; 189 | } else { 190 | sunlight = 0; 191 | } 192 | blocks[idx0 + filly] = b | (sunlight << 28); 193 | p = b; 194 | } 195 | } 196 | } 197 | 198 | int nitems = rand64(blockx + (blockz << 5)) % 10; 199 | if (nitems > 6) { 200 | int x = rand64((blockz << 5) + blockx) % CHUNK_SIZE; 201 | int z = rand64(blockx ^ blockz) % CHUNK_SIZE; 202 | int y = MAP_BLOCK_HEIGHT-1; 203 | size_t idx0 = block_index(blockx + x, 0, blockz + z); 204 | while (y && ((blocks[idx0 + y] >> 28) & 0xf)) { 205 | --y; 206 | } 207 | if (y + 1 < MAP_BLOCK_HEIGHT) { 208 | blocks[idx0 + y + 1] = BLOCK_MELON; 209 | } 210 | } 211 | } 212 | 213 | // light propagation has to happen 214 | // after all block generation is completed 215 | 216 | // propagate light from x,y,z 217 | // so (x,y,z) is the light "source" 218 | // push (x,y,z) to process queue 219 | // check its neighbours 220 | // if neighbour is !solid and... 221 | // has lightlevel < this - 2, 222 | // increase their lightlevel 223 | // add that neighbour to propagation queue 224 | // loop until queue is empty 225 | // neighbour data can be packed into uint8[3] 226 | // TODO: per-thread queue, queue retesselation of 227 | // lit chunks as they are modified 228 | 229 | void propagate_light(int x, int y, int z) 230 | { 231 | // push light propagation to queue 232 | } 233 | 234 | void process_light_propagation() 235 | { 236 | } 237 | 238 | void gen_loadchunk(struct game_map* map, game_chunk* chunk) 239 | { 240 | //gen_testmap(chunk); 241 | //gen_noisemap(chunk); 242 | gen_floating(map, chunk); 243 | } 244 | -------------------------------------------------------------------------------- /src/gen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "game.h" 3 | 4 | void gen_loadchunk(struct game_map* map, game_chunk* chunk); 5 | -------------------------------------------------------------------------------- /src/geometry.c: -------------------------------------------------------------------------------- 1 | /* code that generates geometry (like cube, sphere, etc) */ 2 | #include "common.h" 3 | #include "math3d.h" 4 | #include "geometry.h" 5 | 6 | /* top = 0xff24C6DC; 7 | bottom = 0xff514A9D; 8 | */ 9 | 10 | void make_cube(mesh_t* mesh, vec3_t size, uint32_t top_clr, uint32_t bottom_clr) { 11 | posnormalclrvert_t vtx[] = { 12 | {{-0.5f*size.x,-0.5f*size.y,-0.5f*size.z }, { 0, 0, 0 }, bottom_clr }, 13 | {{ 0.5f*size.x,-0.5f*size.y,-0.5f*size.z }, { 0, 0, 0 }, bottom_clr }, 14 | {{-0.5f*size.x,-0.5f*size.y, 0.5f*size.z }, { 0, 0, 0 }, bottom_clr }, 15 | {{ 0.5f*size.x,-0.5f*size.y, 0.5f*size.z }, { 0, 0, 0 }, bottom_clr }, 16 | {{-0.5f*size.x, 0.5f*size.y,-0.5f*size.z }, { 0, 0, 0 }, top_clr }, 17 | {{-0.5f*size.x, 0.5f*size.y, 0.5f*size.z }, { 0, 0, 0 }, top_clr }, 18 | {{ 0.5f*size.x, 0.5f*size.y,-0.5f*size.z }, { 0, 0, 0 }, top_clr }, 19 | {{ 0.5f*size.x, 0.5f*size.y, 0.5f*size.z }, { 0, 0, 0 }, top_clr } 20 | }; 21 | 22 | posnormalclrvert_t tris[36]; 23 | // bottom 24 | tris[ 0] = vtx[0], tris[ 1] = vtx[1], tris[ 2] = vtx[2], tris[ 3] = vtx[1], tris[ 4] = vtx[3], tris[ 5] = vtx[2], 25 | // top 26 | tris[ 6] = vtx[4], tris[ 7] = vtx[5], tris[ 8] = vtx[6], tris[ 9] = vtx[6], tris[10] = vtx[5], tris[11] = vtx[7], 27 | // front 28 | tris[12] = vtx[2], tris[13] = vtx[3], tris[14] = vtx[5], tris[15] = vtx[3], tris[16] = vtx[7], tris[17] = vtx[5], 29 | // back 30 | tris[18] = vtx[0], tris[19] = vtx[4], tris[20] = vtx[1], tris[21] = vtx[1], tris[22] = vtx[4], tris[23] = vtx[6], 31 | // left 32 | tris[24] = vtx[2], tris[25] = vtx[4], tris[26] = vtx[0], tris[27] = vtx[2], tris[28] = vtx[5], tris[29] = vtx[4], 33 | // right 34 | tris[30] = vtx[3], tris[31] = vtx[1], tris[32] = vtx[6], tris[33] = vtx[3], tris[34] = vtx[6], tris[35] = vtx[7]; 35 | 36 | vec3_t normals[] = {{ 0,-1, 0 }, { 0, 1, 0 }, { 0, 0, 1 }, 37 | { 0, 0,-1 }, {-1, 0, 0 }, { 1, 0, 0 }}; 38 | 39 | for (int i = 0; i < 6; ++i) { 40 | tris[i*6 + 0].n = normals[i]; 41 | tris[i*6 + 1].n = normals[i]; 42 | tris[i*6 + 2].n = normals[i]; 43 | tris[i*6 + 3].n = normals[i]; 44 | tris[i*6 + 4].n = normals[i]; 45 | tris[i*6 + 5].n = normals[i]; 46 | } 47 | 48 | m_create_mesh(mesh, 36, tris, ML_POS_3F | ML_N_3F | ML_CLR_4UB, GL_STATIC_DRAW); 49 | } 50 | 51 | // faceverts must be able to hold 20 * 3 verts 52 | static void initial_icosahedron(vec3_t* faceverts) { 53 | int i; 54 | vec3_t verts[12]; 55 | 56 | double theta = 26.56505117707799 * ML_PI / 180.0; // refer paper for theta value 57 | double stheta = sin(theta); 58 | double ctheta = cos(theta); 59 | 60 | // lower vertex 61 | m_setvec3(verts[0], 0, 0, -1.f); 62 | 63 | // lower pentagon 64 | double phi = ML_PI / 5.0; 65 | for (i = 1; i < 6; ++i) { 66 | m_setvec3(verts[i], ctheta * cos(phi), ctheta * sin(phi), -stheta); 67 | phi += 2.0 * ML_PI / 5.0; 68 | } 69 | 70 | // upper pentagon 71 | phi = 0.0; 72 | for (i = 6; i < 11; ++i) { 73 | m_setvec3(verts[i], ctheta * cos(phi), ctheta * sin(phi), stheta); 74 | phi += 2.0 * ML_PI / 5.0; 75 | } 76 | 77 | // upper vertex 78 | m_setvec3(verts[11], 0, 0, 1.f); 79 | 80 | i = 0; 81 | #undef FACE 82 | #define FACE(v0, v1, v2) { faceverts[i++] = verts[v0]; faceverts[i++] = verts[v1]; faceverts[i++] = verts[v2]; } 83 | FACE(0, 2, 1); 84 | FACE(0, 3, 2); 85 | FACE(0, 4, 3); 86 | FACE(0, 5, 4); 87 | FACE(0, 1, 5); 88 | FACE(1, 2, 7); 89 | FACE(2, 3, 8); 90 | FACE(3, 4, 9); 91 | FACE(4, 5, 10); 92 | FACE(5, 1, 6); 93 | FACE(1, 7, 6); 94 | FACE(2, 8, 7); 95 | FACE(3, 9, 8); 96 | FACE(4, 10, 9); 97 | FACE(5, 6, 10); 98 | FACE(6, 7, 11); 99 | FACE(7, 8, 11); 100 | FACE(8, 9, 11); 101 | FACE(9, 10, 11); 102 | FACE(10, 6, 11); 103 | #undef FACE 104 | } 105 | 106 | static void initial_hemi_icosahedron(vec3_t* faceverts) { 107 | int i; 108 | vec3_t verts[12]; 109 | 110 | double theta = 26.56505117707799 * ML_PI / 180.0; // refer paper for theta value 111 | double stheta = sin(theta); 112 | double ctheta = cos(theta); 113 | double phi = ML_PI / 5.0; 114 | 115 | // lower pentagon 116 | for (i = 1; i < 6; ++i) { 117 | m_setvec3(verts[i], ctheta * cos(phi), -stheta, ctheta * sin(phi)); 118 | phi += 2.0 * ML_PI / 5.0; 119 | } 120 | 121 | // upper pentagon 122 | phi = 0.0; 123 | for (i = 6; i < 11; ++i) { 124 | m_setvec3(verts[i], ctheta * cos(phi), stheta, ctheta * sin(phi)); 125 | phi += 2.0 * ML_PI / 5.0; 126 | } 127 | 128 | // upper vertex 129 | m_setvec3(verts[11], 0, 1.f, 0.f); 130 | 131 | i = 0; 132 | #undef FACE 133 | #define FACE(v0, v1, v2) { faceverts[i++] = verts[v0]; faceverts[i++] = verts[v1]; faceverts[i++] = verts[v2]; } 134 | FACE(2, 1, 7); 135 | FACE(3, 2, 8); 136 | FACE(4, 3, 9); 137 | FACE(5, 4, 10); 138 | FACE(1, 5, 6); 139 | FACE(7, 1, 6); 140 | FACE(8, 2, 7); 141 | FACE(9, 3, 8); 142 | FACE(10, 4, 9); 143 | FACE(6, 5, 10); 144 | FACE(7, 6, 11); 145 | FACE(8, 7, 11); 146 | FACE(9, 8, 11); 147 | FACE(10, 9, 11); 148 | FACE(6, 10, 11); 149 | #undef FACE 150 | } 151 | 152 | 153 | static size_t subdivide_sphere(vec3_t* to, vec3_t* from, size_t nverts) { 154 | // for each subdivision pass, pop N/3 verts 155 | // off, push the subdivided verts back 156 | size_t nout = 0; 157 | size_t nfaces = nverts / 3; 158 | vec3_t v1, v2, v3, v4, v5, v6; 159 | for (size_t i = 0; i < nfaces; ++i) { 160 | v1 = from[(i*3) + 0]; 161 | v2 = from[(i*3) + 1]; 162 | v3 = from[(i*3) + 2]; 163 | v4 = m_vec3normalize(m_vec3add(v1, v2)); 164 | v5 = m_vec3normalize(m_vec3add(v2, v3)); 165 | v6 = m_vec3normalize(m_vec3add(v3, v1)); 166 | to[nout++] = v1; 167 | to[nout++] = v4; 168 | to[nout++] = v6; 169 | to[nout++] = v4; 170 | to[nout++] = v2; 171 | to[nout++] = v5; 172 | to[nout++] = v6; 173 | to[nout++] = v5; 174 | to[nout++] = v3; 175 | to[nout++] = v6; 176 | to[nout++] = v4; 177 | to[nout++] = v5; 178 | } 179 | return nout; 180 | } 181 | 182 | void make_sphere(mesh_t* mesh, float radius, int subdivisions) { 183 | int i, v; 184 | size_t nverts, currverts; 185 | vec3_t* verts[2]; 186 | // 0 = 60 * 1 187 | // 1 = 60 * 4 188 | // 2 = 60 * 4 * 4 189 | nverts = 60 * pow(4, subdivisions); 190 | verts[0] = malloc(nverts * sizeof(vec3_t) * 2); 191 | verts[1] = verts[0] + nverts; 192 | initial_icosahedron(verts[0]); 193 | currverts = 60; 194 | v = 0; 195 | for (i = 0; i < subdivisions; ++i) { 196 | currverts = subdivide_sphere(verts[(v + 1) % 2], verts[v], currverts); 197 | v = (v + 1) % 2; 198 | } 199 | m_create_mesh(mesh, currverts, verts[subdivisions % 2], ML_POS_3F, GL_STATIC_DRAW); 200 | free(verts[0]); 201 | } 202 | 203 | 204 | void make_hemisphere(mesh_t* mesh, float radius, int subdivisions) { 205 | int i, v; 206 | size_t nverts, currverts; 207 | vec3_t* verts[2]; 208 | // 0 = 45 * 1 209 | // 1 = 45 * 4 210 | // 2 = 45 * 4 * 4 211 | nverts = 45 * pow(4, subdivisions); 212 | verts[0] = malloc(nverts * sizeof(vec3_t) * 2); 213 | verts[1] = verts[0] + nverts; 214 | initial_hemi_icosahedron(verts[0]); 215 | currverts = 45; 216 | v = 0; 217 | for (i = 0; i < subdivisions; ++i) { 218 | currverts = subdivide_sphere(verts[(v + 1) % 2], verts[v], currverts); 219 | v = (v + 1) % 2; 220 | } 221 | m_create_mesh(mesh, currverts, verts[subdivisions % 2], ML_POS_3F, GL_STATIC_DRAW); 222 | free(verts[0]); 223 | } 224 | -------------------------------------------------------------------------------- /src/geometry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void make_sphere(mesh_t* mesh, float radius, int subdivisions); 4 | void make_hemisphere(mesh_t* mesh, float radius, int subdivisions); 5 | void make_cube(mesh_t* mesh, vec3_t size, uint32_t top_clr, uint32_t bottom_clr); 6 | -------------------------------------------------------------------------------- /src/images.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define IMG_TEXW 8 4 | #define IMG_ATLAS_TEXW 128 5 | #define IMG_ATLAS_ROW (IMG_ATLAS_TEXW / IMG_TEXW) 6 | #define IMG_TCW ((double)IMG_TEXW / (double)IMG_ATLAS_TEXW) 7 | #define IMG_TC_BIAS (1.0/4000.0) 8 | #define IMG_AT(c, r) (IMG_ATLAS_ROW*(r) + (c + 1)) 9 | 10 | enum ImageTypes { 11 | IMG_INVALID, 12 | IMG_GREEN_GRASS = IMG_AT(0, 0), 13 | IMG_WET_GRASS = IMG_AT(1, 0), 14 | IMG_DIRT = IMG_AT(2, 0), 15 | IMG_WET_DIRT = IMG_AT(3, 0), 16 | IMG_SOLID_DIRT, 17 | IMG_BLACKROCK, 18 | IMG_LIGHT_STONE, 19 | IMG_SNOW, 20 | IMG_BB_GRASS, 21 | IMG_BB_REDFLOWER, 22 | IMG_BB_FRUIT, 23 | IMG_BB_BERRIES, 24 | IMG_BB_SHROOM1, 25 | IMG_BB_SHROOM2, 26 | IMG_BB_SHROOM3, 27 | IMG_BB_SHROOM4, 28 | IMG_GREEN_LEAVES = IMG_AT(0, 1), 29 | IMG_YELLOW_LEAVES, 30 | IMG_ORANGE_LEAVES, 31 | IMG_DARKORANGE_LEAVES, 32 | IMG_RED_LEAVES, 33 | IMG_SNOW2, 34 | IMG_CLAY, 35 | IMG_BLACKROCK2, 36 | IMG_BB_GRASS2, 37 | IMG_BB_YELLOWFLOWER, 38 | IMG_BB_STRAW, 39 | IMG_MINT = IMG_AT(0, 2), 40 | IMG_OCEAN1, 41 | IMG_OCEAN2, 42 | IMG_OCEAN3, 43 | IMG_MOSS, 44 | IMG_DARKMOSS, 45 | IMG_BLACKROCK3, 46 | IMG_FALLENLEAVES, 47 | IMG_ICON_FRAME, 48 | IMG_TREE, 49 | IMG_BIRCH_TREE, 50 | IMG_PALM_TREE, 51 | IMG_OAK_TREE, 52 | IMG_OAK_CUT, 53 | IMG_BIRCH_CUT, 54 | IMG_SICK_GRASS = IMG_AT(0, 3), 55 | IMG_POISON_GRASS, 56 | IMG_PIG_SKIN = IMG_AT(2, 3), 57 | IMG_WHITE_SAND, 58 | IMG_BLOOD_ROCK, 59 | IMG_PINK_BLOCK, 60 | IMG_STRONG_MINT, 61 | IMG_BEIGE_ROCK, 62 | IMG_BRICK = IMG_AT(10, 3), 63 | IMG_MELON_SIDE = IMG_AT(11, 3), 64 | IMG_MELON_CUT = IMG_AT(12, 3), 65 | IMG_PALM_CUT = IMG_AT(13, 3), 66 | IMG_TREE_CUT = IMG_AT(14, 3), 67 | IMG_GOLD_SAND = IMG_AT(0, 4), 68 | IMG_PALE_CLAY, 69 | IMG_SKIN, 70 | IMG_RICH_PURPLE, 71 | IMG_DEEP_BLACK, 72 | IMG_PALE_PINK, 73 | IMG_LAVA = IMG_AT(6, 4), 74 | IMG_PALE_MUD, 75 | IMG_GRASS_SNOW_1 = IMG_AT(8, 4), 76 | IMG_GRASS_SNOW_2 = IMG_AT(9, 4), 77 | IMG_GOLD_ORE = IMG_AT(10, 4), 78 | IMG_MELON_SKIN = IMG_AT(11, 4), 79 | IMG_MEAT_SIDE = IMG_AT(0, 5), 80 | IMG_PIG_HEAD, 81 | IMG_TORCH_SIDE = IMG_AT(5, 5), 82 | IMG_TORCH_TOP, 83 | IMG_SWORD_BLADE, 84 | IMG_MEAT_TOP = IMG_AT(0, 6), 85 | IMG_PIGBACK, 86 | IMG_SWORD_HILT = IMG_AT(6, 6), 87 | IMG_TEST_L = IMG_AT(0, 7), 88 | IMG_TEST_R, 89 | IMG_TEST_F, 90 | IMG_TEST_B, 91 | IMG_TEST_U, 92 | IMG_TEST_D, 93 | IMG_WET_GRASS_SIDE, 94 | IMG_DEEP_ROCK, 95 | IMG_TEST_ALPHA = IMG_AT(0, 8), 96 | NUM_IMG_TYPES 97 | }; 98 | 99 | static inline vec2_t imgTC(int idx) { 100 | vec2_t tc; 101 | tc.x = (float)((idx-1) % IMG_ATLAS_ROW) * IMG_TCW; 102 | tc.y = (float)((idx-1) / IMG_ATLAS_ROW) * IMG_TCW; 103 | return tc; 104 | } 105 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "common.h" 3 | #include "math3d.h" 4 | #include "shaders.h" 5 | #include "ui.h" 6 | #include "objfile.h" 7 | #include "noise.h" 8 | #include "map.h" 9 | #include "game.h" 10 | #include "geometry.h" 11 | #include "u8.h" 12 | #include "sky.h" 13 | #include "stb.h" 14 | #include "easing.h" 15 | #include "script.h" 16 | 17 | 18 | static SDL_Window* window; 19 | static SDL_GLContext context; 20 | static bool mouse_captured = false; 21 | struct game game; 22 | tex2d_t blocks_texture; 23 | extern bool alpha_sort_chunks; 24 | SDL_Point game_viewport; 25 | 26 | 27 | static 28 | void vsync_onoff(int argc, char** argv) 29 | { 30 | if (argc != 1) 31 | return; 32 | const char* onoff = argv[1]; 33 | if (stb_stricmp(onoff, "on") == 0) 34 | SDL_GL_SetSwapInterval(1); 35 | else if (stb_stricmp(onoff, "off") == 0) 36 | SDL_GL_SetSwapInterval(0); 37 | else if (stb_stricmp(onoff, "tear") == 0) { 38 | int sw = SDL_GL_SetSwapInterval(-1); 39 | if (sw == -1) SDL_GL_SetSwapInterval(1); 40 | } 41 | } 42 | 43 | 44 | static 45 | void game_init() 46 | { 47 | script_init(); 48 | script_defun("vsync", vsync_onoff); 49 | game.camera.pitch = 0; 50 | game.camera.yaw = 0; 51 | m_setvec3(game.camera.pos, 0, 0, 0); 52 | game.fast_day_mode = false; 53 | game.debug_mode = true; 54 | game.collisions_on = true; 55 | game.camera.mode = CAMERA_FPS; 56 | game.enable_ground = true; 57 | game.wireframe = false; 58 | script_dofile("data/boot.script"); 59 | 60 | struct controls default_controls = { 61 | .left = SDLK_a, 62 | .right = SDLK_d, 63 | .forward = SDLK_w, 64 | .backward = SDLK_s, 65 | .jump = SDLK_SPACE, 66 | .sprint = SDLK_LCTRL, 67 | .crouch = SDLK_LSHIFT, 68 | .interact = SDLK_e, 69 | .primary_action = SDL_BUTTON_LEFT, 70 | .secondary_action = SDL_BUTTON_RIGHT, 71 | .wireframe = SDLK_o, 72 | .debuginfo = SDLK_p 73 | }; 74 | game.controls = default_controls; 75 | 76 | memset(&game.input, 0, sizeof(struct inputstate)); 77 | 78 | printf("* Load materials + UI\n"); 79 | m_create_material(&game.materials[MAT_BASIC], basic_vshader, basic_fshader); 80 | m_create_material(&game.materials[MAT_UI], ui_vshader, ui_fshader); 81 | m_create_material(&game.materials[MAT_DEBUG], debug_vshader, debug_fshader); 82 | m_create_material(&game.materials[MAT_CHUNK], chunk_vshader, chunk_fshader); 83 | m_create_material(&game.materials[MAT_CHUNK_ALPHA], chunk_vshader, chunkalpha_fshader); 84 | m_create_material(&game.materials[MAT_SKY], sky_vshader, sky_fshader); 85 | ui_init(game.materials + MAT_UI, game.materials + MAT_DEBUG); 86 | m_tex2d_load(&blocks_texture, "data/blocks8-v1.png"); 87 | 88 | game.day = 0; 89 | game.time_of_day = 0; 90 | 91 | sky_init(); 92 | player_init(); 93 | map_init(); 94 | player_move_to_spawn(); 95 | 96 | mouse_captured = false; 97 | } 98 | 99 | 100 | static 101 | void game_exit() 102 | { 103 | map_exit(); 104 | sky_exit(); 105 | ui_exit(); 106 | for (int i = 0; i < MAX_MATERIALS; ++i) 107 | m_destroy_material(game.materials + i); 108 | m_mtxstack_destroy(&game.projection); 109 | m_mtxstack_destroy(&game.modelview); 110 | m_tex2d_destroy(&blocks_texture); 111 | script_exit(); 112 | } 113 | 114 | 115 | static 116 | void capture_mouse(bool capture) 117 | { 118 | int cval = capture ? SDL_TRUE : SDL_FALSE; 119 | SDL_SetRelativeMouseMode(cval); 120 | SDL_SetWindowGrab(window, cval); 121 | mouse_captured = capture; 122 | } 123 | 124 | static 125 | bool handle_event(SDL_Event* event) 126 | { 127 | if (ui_console_handle_event(event)) 128 | return true; 129 | 130 | switch (event->type) { 131 | case SDL_QUIT: 132 | return false; 133 | case SDL_KEYDOWN: { 134 | SDL_Keycode sym = event->key.keysym.sym; 135 | if (sym == SDLK_ESCAPE) { 136 | if (!mouse_captured) 137 | return false; 138 | else 139 | capture_mouse(false); 140 | } 141 | else if (sym == game.controls.wireframe) 142 | game.wireframe = !game.wireframe; 143 | else if (sym == SDLK_F1) 144 | game.debug_mode = !game.debug_mode; 145 | else if (sym == SDLK_F3) 146 | game.enable_ground = !game.enable_ground; 147 | else if (sym == SDLK_F4) 148 | game.camera.mode = (game.camera.mode + 1) % NUM_CAMERA_MODES; 149 | else if (sym == SDLK_F5) 150 | game.collisions_on = !game.collisions_on; 151 | else if (sym == SDLK_F6) { 152 | alpha_sort_chunks = !alpha_sort_chunks; 153 | printf("sort chunks: %d\n", alpha_sort_chunks); 154 | ui_add_console_line(stb_sprintf("sort chunks: %d", alpha_sort_chunks)); 155 | } 156 | else if (sym == SDLK_F10) { 157 | char name[512]; 158 | char date[64]; 159 | static int cnt = 0; 160 | time_t t; 161 | struct tm *tmp; 162 | t = time(NULL); 163 | tmp = localtime(&t); 164 | strftime(date, sizeof(date), "%Y-%m-%d", tmp); 165 | do { 166 | snprintf(name, sizeof(name), "roam-%s-%d.png", date, cnt); 167 | ++cnt; 168 | } while (sys_isfile(name)); 169 | m_save_screenshot(name); 170 | ui_add_console_line(stb_sprintf("Saved %s.", name)); 171 | } 172 | else if (sym == SDLK_BACKQUOTE) 173 | ui_console_toggle(true); 174 | else if (sym == SDLK_F2) 175 | game.fast_day_mode = true; 176 | else if (sym == game.controls.sprint) 177 | game.input.move_sprint = true; 178 | else if (sym == game.controls.left) 179 | game.input.move_left = true; 180 | else if (sym == game.controls.right) 181 | game.input.move_right = true; 182 | else if (sym == game.controls.forward) 183 | game.input.move_forward = true; 184 | else if (sym == game.controls.backward) 185 | game.input.move_backward = true; 186 | else if (sym == game.controls.jump) 187 | game.input.move_jump = true; 188 | else if (sym == game.controls.crouch) 189 | game.input.move_crouch = true; 190 | } break; 191 | case SDL_KEYUP: { 192 | SDL_Keycode sym = event->key.keysym.sym; 193 | if (sym == SDLK_F2) 194 | game.fast_day_mode = false; 195 | else if (sym == game.controls.sprint) 196 | game.input.move_sprint = false; 197 | else if (sym == game.controls.left) 198 | game.input.move_left = false; 199 | else if (sym == game.controls.right) 200 | game.input.move_right = false; 201 | else if (sym == game.controls.forward) 202 | game.input.move_forward = false; 203 | else if (sym == game.controls.backward) 204 | game.input.move_backward = false; 205 | else if (sym == game.controls.jump) 206 | game.input.move_jump = false; 207 | else if (sym == game.controls.crouch) 208 | game.input.move_crouch = false; 209 | } break; 210 | case SDL_MOUSEBUTTONDOWN: 211 | switch (event->button.button) { 212 | case SDL_BUTTON_LEFT: { 213 | if (!mouse_captured) { 214 | printf("focus gained\n"); 215 | capture_mouse(true); 216 | } else { 217 | printf("deleting picked block (%d, %d, %d)\n", 218 | game.input.picked_block.x, 219 | game.input.picked_block.y, 220 | game.input.picked_block.z); 221 | if (blocktype_by_coord(game.input.picked_block) != BLOCK_AIR) { 222 | printf("can delete.\n"); 223 | map_update_block(game.input.picked_block, BLOCK_AIR); 224 | } 225 | } 226 | } break; 227 | case SDL_BUTTON_RIGHT: { 228 | printf("creating block (%d, %d, %d)\n", 229 | game.input.prepicked_block.x, 230 | game.input.prepicked_block.y, 231 | game.input.prepicked_block.z); 232 | 233 | ivec3_t feet = player_block(); 234 | ivec3_t head = feet; 235 | head.y += 1; 236 | if (blocktype_by_coord(game.input.prepicked_block) == BLOCK_AIR && 237 | !block_eq(head, game.input.prepicked_block) && 238 | !block_eq(feet, game.input.prepicked_block)) { 239 | printf("can create.\n"); 240 | map_update_block(game.input.prepicked_block, BLOCK_TEST_ALPHA); 241 | } 242 | } break; 243 | } break; 244 | case SDL_MOUSEMOTION: { 245 | if (mouse_captured) { 246 | game.input.mouse_xrel += event->motion.xrel; 247 | game.input.mouse_yrel += event->motion.yrel; 248 | } 249 | } break; 250 | default: 251 | break; 252 | } 253 | return true; 254 | } 255 | 256 | 257 | static 258 | void camera_tick(float dt) 259 | { 260 | // offset camera from position 261 | struct player *p = &game.player; 262 | struct camera *cam = &game.camera; 263 | struct playervars* pv = player_vars(); 264 | vec3_t offset = {0, 0, 0}; 265 | switch (cam->mode) { 266 | case CAMERA_FLIGHT: 267 | offset.y = pv->camoffset; 268 | break; 269 | case CAMERA_3RDPERSON: { 270 | vec3_t x, y, z; 271 | m_fps_rotation(cam->pitch, cam->yaw, &x, &y, &z); 272 | offset = m_vec3scale(z, 6.f); 273 | } break; 274 | default: { 275 | float d = enCubicInOut(p->crouch_fade); 276 | offset.y = pv->camoffset * (1.f - d) + pv->crouchcamoffset * d; 277 | } break; 278 | } 279 | cam->pos.x = p->pos.x + offset.x; 280 | cam->pos.y = p->pos.y + offset.y; 281 | cam->pos.z = p->pos.z + offset.z; 282 | } 283 | 284 | 285 | static 286 | void game_tick(float dt) 287 | { 288 | player_tick(dt); 289 | script_tick(); 290 | camera_tick(dt); 291 | ui_tick(dt); 292 | map_tick(); 293 | sky_tick(dt); 294 | // update player/input 295 | // update blocks 296 | // update creatures 297 | // update effects 298 | 299 | } 300 | 301 | static 302 | char* read_file(const char* name) 303 | { 304 | FILE* f = fopen(name, "r"); 305 | char* data = NULL; 306 | if (f == NULL) 307 | return NULL; 308 | fseek(f, 0, SEEK_END); 309 | long len = ftell(f); 310 | fseek(f, 0, SEEK_SET); 311 | data = calloc(len+1, 1); 312 | if (data == NULL) 313 | goto exit; 314 | size_t r = fread(data, 1, len, f); 315 | if (r != (size_t)len) { 316 | free(data); 317 | data = NULL; 318 | } 319 | exit: 320 | fclose(f); 321 | return data; 322 | } 323 | 324 | GLuint fbo, fbo_texture, rbo_depth; 325 | GLuint vbo_fbo_vertices, vao_quad; 326 | GLint program_postproc, attribute_v_coord_postproc, uniform_fbo_texture; 327 | 328 | static 329 | void init_fbo_resources(void) 330 | { 331 | GLuint vs; 332 | GLuint fs; 333 | GLint link_ok, validate_ok; 334 | char* vshader = read_file("data/postproc.vert"); 335 | char* fshader = read_file("data/postproc.frag"); 336 | 337 | 338 | M_CHECKGL(glActiveTexture(GL_TEXTURE0)); 339 | M_CHECKGL(glGenTextures(1, &fbo_texture)); 340 | M_CHECKGL(glBindTexture(GL_TEXTURE_2D, fbo_texture)); 341 | M_CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); 342 | M_CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); 343 | M_CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); 344 | M_CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); 345 | M_CHECKGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, game_viewport.x, game_viewport.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL)); 346 | M_CHECKGL(glBindTexture(GL_TEXTURE_2D, 0)); 347 | M_CHECKGL(glGenRenderbuffers(1, &rbo_depth)); 348 | M_CHECKGL(glBindRenderbuffer(GL_RENDERBUFFER, rbo_depth)); 349 | M_CHECKGL(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, game_viewport.x, game_viewport.y)); 350 | M_CHECKGL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); 351 | 352 | M_CHECKGL(glGenFramebuffers(1, &fbo)); 353 | M_CHECKGL(glBindFramebuffer(GL_FRAMEBUFFER, fbo)); 354 | M_CHECKGL(glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, fbo_texture, 0)); 355 | M_CHECKGL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo_depth)); 356 | 357 | GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0}; 358 | M_CHECKGL(glDrawBuffers(1, DrawBuffers)); 359 | 360 | GLenum status; 361 | if ((status = glCheckFramebufferStatus(GL_FRAMEBUFFER)) != GL_FRAMEBUFFER_COMPLETE) { 362 | fprintf(stderr, "glCheckFramebufferStatus: error %x", status); 363 | return; 364 | } 365 | M_CHECKGL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); 366 | 367 | static const GLfloat g_quad_vertex_buffer_data[] = { 368 | -1.0f, -1.0f, 369 | 1.0f, -1.0f, 370 | -1.0f, 1.0f, 371 | -1.0f, 1.0f, 372 | 1.0f, -1.0f, 373 | 1.0f, 1.0f, 374 | }; 375 | 376 | glGenVertexArrays(1, &vao_quad); 377 | M_CHECKGL(glGenBuffers(1, &vbo_fbo_vertices)); 378 | M_CHECKGL(glBindBuffer(GL_ARRAY_BUFFER, vbo_fbo_vertices)); 379 | M_CHECKGL(glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data), g_quad_vertex_buffer_data, GL_STATIC_DRAW)); 380 | 381 | vs = m_compile_shader(GL_VERTEX_SHADER, vshader); 382 | fs = m_compile_shader(GL_FRAGMENT_SHADER, fshader); 383 | free(vshader); 384 | free(fshader); 385 | 386 | program_postproc = glCreateProgram(); 387 | M_CHECKGL(glAttachShader(program_postproc, vs)); 388 | M_CHECKGL(glAttachShader(program_postproc, fs)); 389 | glLinkProgram(program_postproc); 390 | glGetProgramiv(program_postproc, GL_LINK_STATUS, &link_ok); 391 | if (!link_ok) { 392 | fprintf(stderr, "glLinkProgram:"); 393 | return; 394 | } 395 | glValidateProgram(program_postproc); 396 | glGetProgramiv(program_postproc, GL_VALIDATE_STATUS, &validate_ok); 397 | if (!validate_ok) { 398 | fprintf(stderr, "glValidateProgram:"); 399 | } 400 | 401 | const char* attribute_name = "v_coord"; 402 | attribute_v_coord_postproc = glGetAttribLocation(program_postproc, attribute_name); 403 | if (attribute_v_coord_postproc == -1) { 404 | fprintf(stderr, "Could not bind attribute %s\n", attribute_name); 405 | return; 406 | } 407 | 408 | const char* uniform_name = "fbo_texture"; 409 | uniform_fbo_texture = glGetUniformLocation(program_postproc, uniform_name); 410 | if (uniform_fbo_texture == -1) { 411 | fprintf(stderr, "Could not bind uniform %s\n", uniform_name); 412 | return; 413 | } 414 | 415 | M_CHECKGL(glBindBuffer(GL_ARRAY_BUFFER, vbo_fbo_vertices)); 416 | glBindVertexArray(vao_quad); 417 | glEnableVertexAttribArray(attribute_v_coord_postproc); 418 | glVertexAttribPointer( 419 | attribute_v_coord_postproc, // attribute 420 | 2, // number of elements per vertex, here (x,y) 421 | GL_FLOAT, // the type of each element 422 | GL_FALSE, // take our values as-is 423 | 0, // no extra data between each position 424 | 0 // offset of first element 425 | ); 426 | M_CHECKGL(glBindBuffer(GL_ARRAY_BUFFER, 0)); 427 | } 428 | 429 | static 430 | void game_draw(SDL_Point* viewport) 431 | { 432 | 433 | M_CHECKGL(glBindFramebuffer(GL_FRAMEBUFFER, fbo)); 434 | M_CHECKGL(glViewport(0, 0, viewport->x, viewport->y)); 435 | 436 | mat44_t view; 437 | frustum_t frustum; 438 | 439 | M_CHECKGL(glEnable(GL_DEPTH_TEST)); 440 | M_CHECKGL(glLogicOp(GL_INVERT)); 441 | M_CHECKGL(glDepthFunc(GL_LESS)); 442 | M_CHECKGL(glEnable(GL_CULL_FACE)); 443 | M_CHECKGL(glCullFace(GL_BACK)); 444 | M_CHECKGL(glClearColor(game.sky_light.x, game.sky_light.y, game.sky_light.z, 1.f)); 445 | M_CHECKGL(glClearDepth(1.f)); 446 | M_CHECKGL(glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)); 447 | 448 | if (game.wireframe) 449 | M_CHECKGL(glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); 450 | 451 | chunkpos_t camera = player_chunk(); 452 | vec3_t viewcenter = camera_offset(); 453 | 454 | if (game.camera.mode != CAMERA_3RDPERSON) { 455 | m_fpsmatrix(&view, viewcenter, game.camera.pitch, game.camera.yaw); 456 | } 457 | else { 458 | m_lookat(&view, 459 | viewcenter, 460 | camera_offset(), 461 | m_up); 462 | } 463 | 464 | m_copymat(m_getmatrix(&game.modelview), &view); 465 | m_makefrustum(&frustum, m_getmatrix(&game.projection), &view); 466 | 467 | // crosshair 468 | ui_rect((float)viewport->x/2. - 1., (float)viewport->y/2. - 5., 2, 10, 0x4fffffff); 469 | ui_rect((float)viewport->x/2. - 5., (float)viewport->y/2. - 1., 10, 2, 0x4fffffff); 470 | 471 | if (game.enable_ground) 472 | map_draw(&frustum); 473 | 474 | if (game.wireframe) 475 | M_CHECKGL(glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); 476 | 477 | sky_draw(); 478 | 479 | if (game.wireframe) 480 | M_CHECKGL(glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); 481 | 482 | if (game.enable_ground) 483 | map_draw_alphapass(); 484 | 485 | if (game.wireframe) 486 | M_CHECKGL(glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); 487 | 488 | mat44_t invview; 489 | m_invert_orthonormal(&invview, &view); 490 | 491 | 492 | { 493 | dvec3_t pp = game.camera.pos; 494 | vec3_t v = {frustum.planes[5].x, frustum.planes[5].y, frustum.planes[5].z}; 495 | if (map_raycast(pp, v, 16, &game.input.picked_block, &game.input.prepicked_block)) 496 | if (game.camera.mode == CAMERA_FPS) 497 | ui_debug_block(game.input.picked_block, 0xcff1c40f); 498 | } 499 | 500 | if (game.camera.mode == CAMERA_3RDPERSON) { 501 | struct playervars* pv = player_vars(); 502 | float ext = (game.player.crouching ? pv->crouchheight : pv->height) * 0.5f; 503 | float offs = (game.player.crouching ? CROUCHCENTEROFFSET : CENTEROFFSET); 504 | vec3_t center = { game.player.pos.x - camera.x*CHUNK_SIZE, 505 | game.player.pos.y + offs, 506 | game.player.pos.z - camera.z*CHUNK_SIZE }; 507 | vec3_t extent = { 0.4f, ext, 0.4f }; 508 | ui_debug_aabb(center, extent, 0xffffffff); 509 | } 510 | 511 | if (game.debug_mode) { 512 | vec3_t origo = { 0.0f, -0.25f, -0.4f }; 513 | vec3_t xaxis = { 0.025, 0, 0 }; 514 | vec3_t yaxis = { 0, 0.025, 0 }; 515 | vec3_t zaxis = { 0, 0, 0.025 }; 516 | origo = m_matmulvec3(&invview, &origo); 517 | xaxis = m_vec3add(origo, xaxis); 518 | yaxis = m_vec3add(origo, yaxis); 519 | zaxis = m_vec3add(origo, zaxis); 520 | ui_debug_line(origo, xaxis, 0xff00ff00); 521 | ui_debug_line(origo, yaxis, 0xffff0000); 522 | ui_debug_line(origo, zaxis, 0xff0000ff); 523 | 524 | static double ft[4] = {0}; 525 | ft[game.stats.frames % 4] = 1.0 / ((double)(game.stats.frametime) / 1000.0); 526 | double fps = 0.0; 527 | for (int fi = 0; fi < 4; ++fi) 528 | fps += ft[fi] * 0.25; 529 | 530 | ui_text(4, viewport->y - 20, 0xffffffff, 531 | "pos: (%+4.4g, %+4.4g, %+4.4g)\n" 532 | "cam: (%+4.4g, %+4.4g, %+4.4g) p: %+.3g, y: %.3g\n" 533 | "vel: (%+4.4f, %+4.4f, %+4.4f)\n" 534 | "chunk: (%d, %d)\n" 535 | "%s%s%s\n" 536 | "fps: %g, t: %4.4f", 537 | game.player.pos.x, game.player.pos.y, game.player.pos.z, 538 | game.camera.pos.x, game.camera.pos.y, game.camera.pos.z, 539 | ML_RAD2DEG(game.camera.pitch), ML_RAD2DEG(game.camera.yaw), 540 | game.player.vel.x, game.player.vel.y, game.player.vel.z, 541 | camera.x, camera.z, 542 | game.player.walking ? "+walk " : "", 543 | game.player.crouching ? "+crouch " : "", 544 | game.input.move_sprint ? "+sprint " : "", 545 | round(fps), game.time_of_day); 546 | } 547 | ui_draw_debug(&game.projection, &game.modelview); 548 | ui_draw(viewport); 549 | 550 | M_CHECKGL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); 551 | 552 | //M_CHECKGL(glDisable(GL_CULL_FACE)); 553 | 554 | glClearColor(1.0, 0.0, 0.0, 1.0); 555 | glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); 556 | 557 | M_CHECKGL(glDisable(GL_DEPTH_TEST)); 558 | M_CHECKGL(glUseProgram(program_postproc)); 559 | M_CHECKGL(glActiveTexture(GL_TEXTURE0)); 560 | M_CHECKGL(glBindTexture(GL_TEXTURE_2D, fbo_texture)); 561 | M_CHECKGL(glUniform1i(uniform_fbo_texture, 0)); 562 | M_CHECKGL(glBindVertexArray(vao_quad)); 563 | 564 | glDrawArrays(GL_TRIANGLES, 0, 6); 565 | 566 | SDL_GL_SwapWindow(window); 567 | } 568 | 569 | static 570 | void game_window_resize(void) 571 | { 572 | SDL_GetWindowSize(window, &game_viewport.x, &game_viewport.y); 573 | M_CHECKGL(glViewport(0, 0, game_viewport.x, game_viewport.y)); 574 | 575 | printf("resize: %d, %d\n", game_viewport.x, game_viewport.y); 576 | 577 | // Rescale FBO and RBO as well 578 | glBindTexture(GL_TEXTURE_2D, fbo_texture); 579 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, game_viewport.x, game_viewport.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); 580 | glBindTexture(GL_TEXTURE_2D, 0); 581 | 582 | glBindRenderbuffer(GL_RENDERBUFFER, rbo_depth); 583 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, game_viewport.x, game_viewport.y); 584 | glBindRenderbuffer(GL_RENDERBUFFER, 0); 585 | } 586 | 587 | static 588 | void game_loop(int64_t dt) 589 | { 590 | SDL_Event event; 591 | while (SDL_PollEvent(&event)) { 592 | if (event.type == SDL_WINDOWEVENT) { 593 | if (event.window.event == SDL_WINDOWEVENT_RESIZED) { 594 | game_window_resize(); 595 | } else if (event.window.event == SDL_WINDOWEVENT_MAXIMIZED) { 596 | game_window_resize(); 597 | m_perspective(m_getmatrix(&game.projection), ML_DEG2RAD(70.f), 598 | (float)game_viewport.x / (float)game_viewport.y, 599 | 0.1f, 1024.f); 600 | } else if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) { 601 | printf("focus lost\n"); 602 | capture_mouse(false); 603 | } else if (event.window.event == SDL_WINDOWEVENT_CLOSE) { 604 | game.game_active = false; 605 | } 606 | } else if (!handle_event(&event)) { 607 | game.game_active = false; 608 | } 609 | } 610 | 611 | float frametime = (float)dt / 1000.f; 612 | game_tick(frametime); 613 | } 614 | 615 | 616 | int roam_main(int argc, char* argv[]) 617 | { 618 | GLenum rc; 619 | 620 | if (argc == 3 && strcmp(argv[1], "objtest") == 0) { 621 | char* fdata = sys_readfile(argv[2]); 622 | obj_t obj; 623 | obj_load(&obj, fdata, 0.1f); 624 | printf("loaded %s: %zu verts, %zu faces\n", argv[2], obj.nverts / 3, obj.nindices / 3); 625 | free(fdata); 626 | obj_free(&obj); 627 | exit(0); 628 | } 629 | 630 | if (SDL_Init(SDL_INIT_EVERYTHING) < 0) 631 | return 1; 632 | 633 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 634 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 635 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); 636 | SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); 637 | SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); 638 | 639 | window = SDL_CreateWindow("roam", 640 | SDL_WINDOWPOS_UNDEFINED, 641 | SDL_WINDOWPOS_UNDEFINED, 642 | 1280, 720, 643 | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); 644 | if (window == NULL) 645 | fatal_error(SDL_GetError()); 646 | 647 | context = SDL_GL_CreateContext(window); 648 | if (context == NULL) 649 | fatal_error(SDL_GetError()); 650 | 651 | SDL_GL_MakeCurrent(window, context); 652 | { 653 | int sw = SDL_GL_SetSwapInterval(-1); // late swap tearing 654 | if (sw == -1) sw = SDL_GL_SetSwapInterval(0); // vsync off 655 | if (sw == -1) sw = SDL_GL_SetSwapInterval(1); 656 | } 657 | 658 | glewExperimental = GL_TRUE; 659 | if ((rc = glewInit()) != GLEW_OK) 660 | fatal_error((const char*)glewGetErrorString(rc)); 661 | 662 | const GLubyte *version, *vendor, *renderer, *glslversion; 663 | M_CHECKGL(version = glGetString(GL_VERSION)); 664 | #if M_CHECKGL_ENABLED 665 | fprintf(stderr, "(if there was a GL error reported before, it is due to a bug in GLEW)\n"); 666 | #endif 667 | M_CHECKGL(vendor = glGetString(GL_VENDOR)); 668 | M_CHECKGL(renderer = glGetString(GL_RENDERER)); 669 | M_CHECKGL(glslversion = glGetString(GL_SHADING_LANGUAGE_VERSION)); 670 | 671 | printf("Version: %s\n" 672 | "Vendor: %s\n" 673 | "Renderer: %s\n" 674 | "GLSL Version: %s\n", 675 | version, vendor, renderer, glslversion); 676 | 677 | SDL_GetWindowSize(window, &game_viewport.x, &game_viewport.y); 678 | M_CHECKGL(glViewport(0, 0, game_viewport.x, game_viewport.y)); 679 | 680 | m_mtxstack_init(&game.projection, 3); 681 | m_mtxstack_init(&game.modelview, 16); 682 | 683 | m_perspective(m_getmatrix(&game.projection), ML_DEG2RAD(70.f), 684 | (float)game_viewport.x / (float)game_viewport.y, 685 | 0.1f, 1024.f); 686 | 687 | game_init(); 688 | init_fbo_resources(); 689 | 690 | int64_t currenttime, newtime, frametime; 691 | int64_t t, dt, accumulator ; 692 | game.stats.frametime = (int64_t)((1.0 / 60.0) * 1000.0); 693 | 694 | t = 0; 695 | dt = 15; 696 | accumulator = 0; 697 | currenttime = sys_timems(); 698 | game.game_active = true; 699 | while (game.game_active) { 700 | newtime = sys_timems(); 701 | frametime = newtime - currenttime; 702 | if (frametime > 250) 703 | frametime = 250; 704 | currenttime = newtime; 705 | 706 | accumulator += frametime; 707 | while (accumulator >= dt) { 708 | // timestep 709 | game_loop(dt); 710 | t += dt; 711 | accumulator -= dt; 712 | } 713 | game_draw(&game_viewport); 714 | game.stats.frames++; 715 | game.stats.frametime = frametime; 716 | } 717 | return 0; 718 | } 719 | -------------------------------------------------------------------------------- /src/map.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "math3d.h" 3 | #include "game.h" 4 | #include "map.h" 5 | #include "noise.h" 6 | #include "images.h" 7 | #include "blocks.h" 8 | #include "ui.h" 9 | #include "gen.h" 10 | #include "easing.h" 11 | 12 | #define SUNLIGHT_MASK 0xf0000000 13 | #define NOSUNLIGHT_MASK 0x0fffffff 14 | 15 | static inline 16 | tc2us_t make_tc2us(vec2_t tc) 17 | { 18 | tc2us_t to = { 19 | (uint16_t)(tc.x * (double)0xffff), 20 | (uint16_t)(tc.y * (double)0xffff) 21 | }; 22 | return to; 23 | } 24 | 25 | uint32_t block_at(int x, int y, int z) 26 | { 27 | if (y < 0 || y >= MAP_BLOCK_HEIGHT) 28 | return (SUNLIGHT_MASK|BLOCK_AIR); 29 | size_t idx = block_index(x, y, z); 30 | if (idx >= MAP_BUFFER_SIZE) 31 | return (SUNLIGHT_MASK|BLOCK_AIR); 32 | return map_blocks[idx]; 33 | } 34 | 35 | void chunk_mark_dirty_ptr(game_chunk* chunk); 36 | void chunk_destroy_mesh_ptr(game_chunk* chunk); 37 | 38 | /* 39 | Set up a lookup table used for the texcoords of all regular blocks. 40 | Things with different dimensions need a different system.. 41 | */ 42 | static tc2us_t block_texcoords[NUM_BLOCKTYPES * 6 * 4]; 43 | #define BLOCKTC(t, f, i) block_texcoords[(t)*(6*4) + (f)*4 + (i)] 44 | static 45 | void gen_block_tcs() 46 | { 47 | float bw = IMG_TCW - IMG_TC_BIAS*2.0; 48 | vec2_t tcoffs[4] = {{0, bw}, {bw, bw}, {bw, 0}, {0, 0} }; 49 | for (int t = 0; t < NUM_BLOCKTYPES; ++t) { 50 | for (int i = 0; i < 6; ++i) { 51 | vec2_t tl = m_vec2addf(imgTC(blockinfo[t].img[i]), IMG_TC_BIAS); 52 | for (int j = 0; j < 4; ++j) 53 | BLOCKTC(t, i, j) = make_tc2us(m_vec2add(tl, tcoffs[j])); 54 | } 55 | } 56 | } 57 | 58 | extern tex2d_t blocks_texture; 59 | uint32_t* map_blocks = NULL; 60 | static chunkpos_t map_chunk; 61 | 62 | static uint32_t lightlut[256]; 63 | 64 | static 65 | void lightlut_init(void) 66 | { 67 | double base_level = 1.0; 68 | for (int i = 0; i < 256; ++i) { 69 | lightlut[i] = (uint32_t)base_level + (uint32_t)trunc(((double)i / 255.0)*(255.0 - base_level)); 70 | lightlut[i] = ML_MIN(255, lightlut[i]); 71 | printf("%02x ", lightlut[i]); 72 | } 73 | printf("\n"); 74 | } 75 | 76 | void map_init() 77 | { 78 | blocks_init(); 79 | gen_block_tcs(); 80 | lightlut_init(); 81 | 82 | printf("* Allocate and build initial map...\n"); 83 | memset(&game.map, 0, sizeof(struct game_map)); 84 | map_blocks = (uint32_t*)malloc(sizeof(uint32_t)*MAP_BUFFER_SIZE); 85 | memset(map_blocks, 0, sizeof(uint32_t)*MAP_BUFFER_SIZE); 86 | 87 | game.map.seed = sys_urandom(); 88 | printf("* Seed: %lx\n", game.map.seed); 89 | simplex_init(game.map.seed); 90 | opensimplex_init(game.map.seed); 91 | 92 | chunkpos_t camera = player_chunk(); 93 | for (int z = -VIEW_DISTANCE; z < VIEW_DISTANCE; ++z) 94 | for (int x = -VIEW_DISTANCE; x < VIEW_DISTANCE; ++x) 95 | chunk_load(camera.x + x, camera.z + z); 96 | map_chunk = camera; 97 | 98 | map_tick(); 99 | printf("* Map load complete.\n"); 100 | } 101 | 102 | 103 | void map_exit() 104 | { 105 | for (size_t i = 0; i < MAP_CHUNK_WIDTH*MAP_CHUNK_WIDTH; ++i) 106 | chunk_destroy_mesh_ptr(game.map.chunks + i); 107 | 108 | free(map_blocks); 109 | map_blocks = NULL; 110 | } 111 | 112 | static inline game_chunk* cached_chunk_at(int x, int z) 113 | { 114 | int bufx = mod(x, MAP_CHUNK_WIDTH); 115 | int bufz = mod(z, MAP_CHUNK_WIDTH); 116 | game_chunk* chunk = game.map.chunks + (bufz*MAP_CHUNK_WIDTH + bufx); 117 | if (chunk->x == x && chunk->z == z) 118 | return chunk; 119 | return 0; 120 | } 121 | 122 | 123 | void map_tick() 124 | { 125 | // here is the correct chunk update algorithm: 126 | // pass through all cache slots. If a cache slot 127 | // doesn't match its loaded chunk data, that cache 128 | // slot plus all surrounding cache slots must be 129 | // reloaded. 130 | // for each slot: 131 | // if not dirty, 132 | // push to reload queue and mark as dirty 133 | 134 | chunkpos_t nc = player_chunk(); 135 | if (nc.x != map_chunk.x || nc.z != map_chunk.z) { 136 | game_chunk* chunks = game.map.chunks; 137 | int cx = nc.x; 138 | int cz = nc.z; 139 | printf("[%d, %d] -> [%d, %d] (%g, %g)\n", 140 | map_chunk.x, map_chunk.z, cx, cz, 141 | game.camera.pos.x, game.camera.pos.z); 142 | map_chunk = nc; 143 | 144 | for (int dz = -VIEW_DISTANCE; dz < VIEW_DISTANCE; ++dz) { 145 | int bz = mod(cz + dz, MAP_CHUNK_WIDTH); 146 | game_chunk* chunk_row = chunks + (bz*MAP_CHUNK_WIDTH); 147 | for (int dx = -VIEW_DISTANCE; dx < VIEW_DISTANCE; ++dx) { 148 | int bx = mod(cx + dx, MAP_CHUNK_WIDTH); 149 | game_chunk* chunk = chunk_row + bx; 150 | if (chunk->x != cx + dx || 151 | chunk->z != cz + dz) { 152 | chunk_mark_dirty_ptr(chunk); 153 | chunk_load(cx + dx, cz + dz); 154 | chunk = chunks + (bz*MAP_CHUNK_WIDTH + bx); 155 | assert(chunk->x == (cx + dx) && chunk->z == (cz + dz)); 156 | 157 | // mark surrounding chunks dirty unless they are also 158 | // invalidated 159 | { 160 | game_chunk* surround; 161 | if ((surround = cached_chunk_at(chunk->x-1, chunk->z)) != 0) 162 | chunk_mark_dirty_ptr(surround); 163 | if ((surround = cached_chunk_at(chunk->x+1, chunk->z)) != 0) 164 | chunk_mark_dirty_ptr(surround); 165 | if ((surround = cached_chunk_at(chunk->x, chunk->z-1)) != 0) 166 | chunk_mark_dirty_ptr(surround); 167 | if ((surround = cached_chunk_at(chunk->x, chunk->z+1)) != 0) 168 | chunk_mark_dirty_ptr(surround); 169 | } 170 | } 171 | } 172 | } 173 | } 174 | { 175 | // TODO: try different meshing patterns, processing out from 176 | // center should look best 177 | Uint32 max_per_frame = 10; // milliseconds 178 | Uint32 start_ticks = SDL_GetTicks(); 179 | Uint32 curr_ticks = start_ticks; 180 | int cx = nc.x; 181 | int cz = nc.z; 182 | game_chunk* chunks = game.map.chunks; 183 | for (int dz = -VIEW_DISTANCE; dz < VIEW_DISTANCE; ++dz) { 184 | int bz = mod(cz + dz, MAP_CHUNK_WIDTH); 185 | game_chunk* chunk_row = chunks + (bz*MAP_CHUNK_WIDTH); 186 | for (int dx = -VIEW_DISTANCE; dx < VIEW_DISTANCE; ++dx) { 187 | int bx = mod(cx + dx, MAP_CHUNK_WIDTH); 188 | game_chunk* chunk = chunk_row + bx; 189 | if (chunk->dirty) { 190 | chunk_build_mesh_ptr(bx, bz, chunk); 191 | curr_ticks = SDL_GetTicks(); 192 | if (curr_ticks < start_ticks || ((curr_ticks - start_ticks) > max_per_frame)) 193 | goto escape; 194 | } 195 | } 196 | } 197 | } 198 | escape: 199 | return; 200 | } 201 | 202 | #define MAX_ALPHAS ((VIEW_DISTANCE*2)*(VIEW_DISTANCE*2)) 203 | 204 | struct alpha_t { 205 | game_chunk* chunk; 206 | vec3_t offset; 207 | }; 208 | 209 | static struct alpha_t alphas[MAX_ALPHAS]; 210 | static size_t nalphas; 211 | 212 | void map_draw(frustum_t* frustum) 213 | { 214 | // for each visible chunk... 215 | // set up material etc. once. 216 | material_t* material = game.materials + MAT_CHUNK; 217 | 218 | glDisable(GL_BLEND); 219 | glEnable(GL_DEPTH_TEST); 220 | m_tex2d_bind(&blocks_texture, 0); 221 | 222 | m_use(material); 223 | m_uniform_i(material->tex0, 0); 224 | m_uniform_mat44(material->projmat, m_getmatrix(&game.projection)); 225 | m_uniform_mat44(material->modelview, m_getmatrix(&game.modelview)); 226 | m_uniform_vec3(material->amb_light, &game.amb_light); 227 | m_uniform_vec4(material->fog_color, &game.fog_color); 228 | 229 | float chunk_radius = (float)CHUNK_SIZE*0.5f; 230 | vec3_t offset, center, extent; 231 | game_chunk* chunks = game.map.chunks; 232 | chunkpos_t camera = player_chunk(); 233 | 234 | int dx, dz, bx, bz, x, z, j; 235 | game_chunk* chunk; 236 | mesh_t* mesh; 237 | 238 | nalphas = 0; 239 | 240 | for (dz = -VIEW_DISTANCE; dz < VIEW_DISTANCE; ++dz) { 241 | for (dx = -VIEW_DISTANCE; dx < VIEW_DISTANCE; ++dx) { 242 | bx = mod(camera.x + dx, MAP_CHUNK_WIDTH); 243 | bz = mod(camera.z + dz, MAP_CHUNK_WIDTH); 244 | chunk = chunks + (bz*MAP_CHUNK_WIDTH + bx); 245 | x = chunk->x - camera.x; 246 | z = chunk->z - camera.z; 247 | 248 | m_setvec3(offset, (float)(x*CHUNK_SIZE) - 0.5f, -0.5f, (float)(z*CHUNK_SIZE) - 0.5f); 249 | m_setvec3(center, offset.x + chunk_radius, MAP_BLOCK_HEIGHT*0.5f, offset.z + chunk_radius); 250 | m_setvec3(extent, chunk_radius, MAP_BLOCK_HEIGHT*0.5f, chunk_radius); 251 | if (collide_frustum_aabb_xz(frustum, center, extent) == ML_OUTSIDE) 252 | continue; 253 | 254 | if (chunk->dirty) { 255 | //if (game.debug_mode) 256 | // ui_debug_aabb(center, extent, 0x44ff2222); 257 | //continue; 258 | } 259 | 260 | extent.y = chunk_radius; 261 | 262 | m_uniform_vec3(material->chunk_offset, &offset); 263 | 264 | if (chunk->alpha.vbo != 0 && nalphas < MAX_ALPHAS) { 265 | alphas[nalphas].chunk = chunk; 266 | alphas[nalphas].offset = offset; 267 | nalphas++; 268 | } 269 | 270 | for (j = 0; j < MAP_CHUNK_HEIGHT; ++j) { 271 | mesh = chunk->solid + j; 272 | if (mesh->vbo == 0) 273 | continue; 274 | center.y = (float)(CHUNK_SIZE*j) - 0.5f + chunk_radius; 275 | if (collide_frustum_aabb_y(frustum, center, extent) == ML_OUTSIDE) 276 | continue; 277 | m_draw(mesh); 278 | } 279 | } 280 | } 281 | 282 | m_use(NULL); 283 | glDisable(GL_BLEND); 284 | glDepthMask(GL_TRUE); 285 | } 286 | 287 | // sort back to front 288 | 289 | static 290 | int cmp_alpha_chunks(struct alpha_t *a, struct alpha_t *b) 291 | { 292 | int ret = 0; 293 | float da = (a->offset.x*a->offset.x) + (a->offset.z*a->offset.z); 294 | float db = (b->offset.x*b->offset.x) + (b->offset.z*b->offset.z); 295 | if (da > db) 296 | ret = -1; 297 | if (da < db) 298 | ret = 1; 299 | return ret; 300 | } 301 | 302 | bool alpha_sort_chunks = true; 303 | 304 | void map_draw_alphapass() 305 | { 306 | size_t i; 307 | material_t* material; 308 | struct alpha_t* alpha; 309 | if (nalphas == 0) 310 | return; 311 | 312 | if (alpha_sort_chunks) 313 | qsort(alphas, nalphas, sizeof(struct alpha_t), (int(*)(const void*, const void*))cmp_alpha_chunks); 314 | 315 | material = game.materials + MAT_CHUNK_ALPHA; 316 | m_tex2d_bind(&blocks_texture, 0); 317 | glEnable(GL_BLEND); 318 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 319 | //glBlendFunc(GL_ZERO, GL_SRC_COLOR); 320 | //glBlendFunc(GL_ONE, GL_ONE); 321 | glEnable(GL_DEPTH_TEST); 322 | glDepthMask(GL_FALSE); 323 | m_use(material); 324 | m_uniform_i(material->tex0, 0); 325 | m_uniform_mat44(material->projmat, m_getmatrix(&game.projection)); 326 | m_uniform_mat44(material->modelview, m_getmatrix(&game.modelview)); 327 | m_uniform_vec3(material->amb_light, &game.amb_light); 328 | m_uniform_vec4(material->fog_color, &game.fog_color); 329 | 330 | for (i = 0, alpha = alphas; i < nalphas; ++i, ++alpha) { 331 | game_chunk* chunk = alpha->chunk; 332 | mesh_t* mesh = &(chunk->alpha); 333 | m_uniform_vec3(material->chunk_offset, &alpha->offset); 334 | m_draw(mesh); 335 | } 336 | 337 | m_use(NULL); 338 | glDisable(GL_BLEND); 339 | glDepthMask(GL_TRUE); 340 | } 341 | 342 | void map_update_block(ivec3_t block, uint32_t value) 343 | { 344 | size_t idx = block_by_coord(block); 345 | 346 | // relight column down (fixes sunlight) 347 | uint32_t sunlight = SUNLIGHT_MASK; 348 | map_blocks[idx] = value; 349 | idx -= block.y; 350 | for (int dy = MAP_BLOCK_HEIGHT-1; dy >= 0; --dy) { 351 | uint32_t t = map_blocks[idx + dy]; 352 | if ((t & 0xff) != BLOCK_AIR) 353 | sunlight = 0; 354 | map_blocks[idx + dy] = (t & NOSUNLIGHT_MASK) | sunlight; 355 | } 356 | // TODO: need to re-propagate light from lightsources affected by this change 357 | 358 | chunkpos_t chunk = block_chunk(block); 359 | bool tess[4] = { false, false, false, false }; 360 | int mx = block.x % CHUNK_SIZE; 361 | int mz = block.z % CHUNK_SIZE; 362 | if (mx == 0 || mx == -CHUNK_SIZE-1) { 363 | tess[0] = true; 364 | } else if (mx == -1 || mx == CHUNK_SIZE-1) { 365 | tess[1] = true; 366 | } 367 | if (mz == 0 || mz == -CHUNK_SIZE-1) { 368 | tess[2] = true; 369 | } else if (mz == -1 || mz == CHUNK_SIZE-1) { 370 | tess[3] = true; 371 | } 372 | printf("reload chunk [%d, %d] [%d, %d]\n", chunk.x, chunk.z, mx, mz); 373 | chunk_mark_dirty(chunk.x, chunk.z); 374 | if (tess[0]) { 375 | printf("reload chunk [%d, %d]\n", chunk.x-1, chunk.z); 376 | chunk_mark_dirty(chunk.x-1, chunk.z); 377 | } 378 | if (tess[1]) { 379 | printf("reload chunk [%d, %d]\n", chunk.x+1, chunk.z); 380 | chunk_mark_dirty(chunk.x+1, chunk.z); 381 | } 382 | if (tess[2]) { 383 | printf("reload chunk [%d, %d]\n", chunk.x, chunk.z-1); 384 | chunk_mark_dirty(chunk.x, chunk.z-1); 385 | } 386 | if (tess[3]) { 387 | printf("reload chunk [%d, %d]\n", chunk.x, chunk.z+1); 388 | chunk_mark_dirty(chunk.x, chunk.z+1); 389 | } 390 | } 391 | 392 | // TODO: chunk saving/loading 393 | // TODO: asynchronous 394 | // as a test, just fill in the designated chunk 395 | // and tesselate the whole thing 396 | 397 | void chunk_load(int x, int z) { 398 | int bufx = mod(x, MAP_CHUNK_WIDTH); 399 | int bufz = mod(z, MAP_CHUNK_WIDTH); 400 | game_chunk* chunk = game.map.chunks + (bufz*MAP_CHUNK_WIDTH + bufx); 401 | chunk->x = x; 402 | chunk->z = z; 403 | chunk_destroy_mesh_ptr(chunk); 404 | gen_loadchunk(&game.map, chunk); 405 | } 406 | 407 | void chunk_destroy_mesh_ptr(game_chunk* chunk) 408 | { 409 | for (int i = 0; i < MAP_CHUNK_HEIGHT; ++i) 410 | m_destroy_mesh(chunk->solid + i); 411 | m_destroy_mesh(&chunk->alpha); 412 | chunk->dirty = true; 413 | } 414 | 415 | void chunk_mark_dirty_ptr(game_chunk* chunk) 416 | { 417 | chunk->dirty = true; 418 | } 419 | 420 | void chunk_mark_dirty(int x, int z) 421 | { 422 | int bufx = mod(x, MAP_CHUNK_WIDTH); 423 | int bufz = mod(z, MAP_CHUNK_WIDTH); 424 | game_chunk* chunk = game.map.chunks + (bufz*MAP_CHUNK_WIDTH + bufx); 425 | if (chunk->x != x || chunk->z != z) 426 | return; 427 | chunk_mark_dirty_ptr(chunk); 428 | } 429 | 430 | // tesselation buffer: size is maximum number of triangles generated 431 | // 1: fill tesselation buffer 432 | // 2: allocate mesh 433 | // 3: fill vertices 434 | // returns num verts in chunk 435 | 436 | #define ALPHA_BUFFER_SIZE (CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE*36) 437 | static block_vtx_t alpha_buffer[ALPHA_BUFFER_SIZE]; 438 | 439 | #define TESSELATION_BUFFER_SIZE (CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE*36) 440 | static block_vtx_t tesselation_buffer[TESSELATION_BUFFER_SIZE]; 441 | 442 | static 443 | bool mesh_subchunk(mesh_t* mesh, int bufx, int bufz, int cy, size_t* alphai); 444 | 445 | static 446 | vec3_t avg3(vec3_t a, vec3_t b, vec3_t c) { 447 | vec3_t r = { 448 | (a.x + b.x + c.x) * (1.f / 3.f), 449 | (a.y + b.y + c.y) * (1.f / 3.f), 450 | (a.z + b.z + c.z) * (1.f / 3.f) 451 | }; 452 | return r; 453 | } 454 | 455 | static 456 | int cmp_alpha_faces(block_face_t* a, block_face_t* b) 457 | { 458 | vec3_t ca = avg3(a->vtx[0].pos, a->vtx[1].pos, a->vtx[2].pos); 459 | vec3_t cb = avg3(b->vtx[0].pos, b->vtx[1].pos, b->vtx[2].pos); 460 | if (ca.y < cb.y) 461 | return -1; 462 | if (ca.y > cb.y) 463 | return 1; 464 | if (ca.x < cb.x) 465 | return -1; 466 | if (ca.x > cb.x) 467 | return 1; 468 | if (ca.z < cb.z) 469 | return -1; 470 | if (ca.z > cb.z) 471 | return 1; 472 | return 0; 473 | } 474 | 475 | void chunk_build_mesh_ptr(int bufx, int bufz, game_chunk* chunk) 476 | { 477 | if (!chunk->dirty) 478 | return; 479 | chunk_destroy_mesh_ptr(chunk); 480 | chunk->dirty = false; 481 | size_t alphai = 0; 482 | mesh_t* mesh = chunk->solid; 483 | for (int y = 0; y < MAP_CHUNK_HEIGHT; ++y) 484 | mesh_subchunk(mesh + y, bufx, bufz, y, &alphai); 485 | if (alphai > 0) { 486 | mesh_t* alpha = &(chunk->alpha); 487 | size_t nalphafaces = (alphai/3); 488 | qsort(alpha_buffer, nalphafaces, sizeof(block_face_t), (int(*)(const void*, const void*))cmp_alpha_faces); 489 | m_create_mesh(alpha, alphai, alpha_buffer, ML_POS_3F | ML_TC_2US | ML_CLR_4UB, GL_DYNAMIC_DRAW); 490 | m_set_material(alpha, game.materials + MAT_CHUNK_ALPHA); 491 | } 492 | } 493 | 494 | void chunk_build_mesh(int x, int z) 495 | { 496 | int bufx = mod(x, MAP_CHUNK_WIDTH); 497 | int bufz = mod(z, MAP_CHUNK_WIDTH); 498 | game_chunk* chunk = game.map.chunks + bufz*MAP_CHUNK_WIDTH + bufx; 499 | if (chunk->x != x || chunk->z != z) 500 | return; 501 | chunk_build_mesh_ptr(bufx, bufz, chunk); 502 | } 503 | 504 | // Interleave lower 16 bits of x and y in groups of 4 505 | // ie turn 0xabcd into 0xaabbccdd 506 | static 507 | uint32_t bitexpand16(uint32_t x) 508 | { 509 | x = ((x << 12) & 0xF000000) + ((x << 8) & 0xF0000) + ((x << 4) & 0xF00) + (x & 0xF); 510 | return x | (x << 4); 511 | } 512 | 513 | // turn 0xaabbccdd into 0xabcd 514 | static 515 | uint32_t bitcontract16(uint32_t x) 516 | { 517 | return ((x >> 16) & 0xF000) + ((x >> 12) & 0xF00) + ((x >> 8) & 0xF0) + ((x & 0xF0) >> 4); 518 | } 519 | 520 | // take light from the given four blocks and mix it into a single light value 521 | // assert(avglight(0xabcd0000, 0xabcd0000, 0xabcd0000, 0xabcd0000) == 0xafbfcfdf); 522 | // assert(avglight(0, 0, 0, 0) == 0x0f0f0f0f); 523 | // assert(avglight(0xffff0000, 0xffff0000, 0xffff0000, 0xffff0000) == 0xffff0000); 524 | static 525 | uint32_t avglight(uint32_t b0, uint32_t b1, uint32_t b2, uint32_t b3) 526 | { 527 | #define AVGLIGHT_SCALE(f) (lightlut[ML_MIN(255, (int)trunc(((double)(f)/60.0)*255.5))]) 528 | static const uint32_t M[4] = { 0xf0000000, 0xf000000, 0xf00000, 0xf0000 }; 529 | static const uint32_t S[4] = { 28, 24, 20, 16 }; 530 | uint32_t ret = 0; 531 | ret |= AVGLIGHT_SCALE(((b0 & M[0]) >> S[0]) + 532 | ((b1 & M[0]) >> S[0]) + 533 | ((b2 & M[0]) >> S[0]) + 534 | ((b3 & M[0]) >> S[0])) << 24; 535 | ret |= AVGLIGHT_SCALE(((b0 & M[1]) >> S[1]) + 536 | ((b1 & M[1]) >> S[1]) + 537 | ((b2 & M[1]) >> S[1]) + 538 | ((b3 & M[1]) >> S[1])) << 16; 539 | ret |= AVGLIGHT_SCALE(((b0 & M[2]) >> S[2]) + 540 | ((b1 & M[2]) >> S[2]) + 541 | ((b2 & M[2]) >> S[2]) + 542 | ((b3 & M[2]) >> S[2])) << 8; 543 | ret |= AVGLIGHT_SCALE(((b0 & M[3]) >> S[3]) + 544 | ((b1 & M[3]) >> S[3]) + 545 | ((b2 & M[3]) >> S[3]) + 546 | ((b3 & M[3]) >> S[3])); 547 | return ret; 548 | } 549 | 550 | // TODO: calculate index of surrounding blocks directly without going through blocktype 551 | #define POS(x, y, z) m_vec3(x, y, z) 552 | #define BNONSOLID(t) (blockinfo[(n[(t)] & 0xff)].density < density) 553 | //#define BNONSOLID(t) ((n[(t)] & 0xff) == BLOCK_AIR) 554 | #define BLOCKAT(x, y, z) (map_blocks[block_index((x), (y), (z))]) 555 | #define BLOCKLIGHT(a, b, c, d, e, f, g) avglight(n[a], n[b], n[c], n[d]) 556 | #define GETCOL(np, ng, x, y, z) memcpy(n + (np), map_blocks + block_index(bx + (x), by + (y), bz + (z)), sizeof(uint32_t) * (ng)) 557 | #define FLIPCHECK() ((corners[0].clr>>24) + (corners[2].clr>>24) > (corners[1].clr>>24) + (corners[3].clr>>24)) 558 | 559 | // n array layout: 560 | //+y +y +y 561 | // \ 02 05 08 | 11 14 17 | 20 23 26 562 | // \ 01 04 07 | 10 13 16 | 19 22 25 563 | // \ 00 03 06 | 09 12 15 | 18 21 24 564 | // +-----+x +-----+x +-----+x 565 | // (iz-1) (iz) (iz+1) 566 | 567 | 568 | bool mesh_subchunk(mesh_t* mesh, int bufx, int bufz, int cy, size_t* alphai) 569 | { 570 | int ix, iy, iz; 571 | int bx, by, bz; 572 | size_t vi; 573 | block_vtx_t* verts; 574 | 575 | verts = tesselation_buffer; 576 | vi = 0; 577 | bx = bufx*CHUNK_SIZE; 578 | by = cy*CHUNK_SIZE; 579 | bz = bufz*CHUNK_SIZE; 580 | size_t nprocessed = 0; 581 | 582 | size_t idx0; 583 | uint32_t t; 584 | uint32_t n[27]; // blocktypes for a 3x3 cube around this block 585 | int density; 586 | size_t save_vi = 0; 587 | 588 | // fill in verts 589 | for (iz = 0; iz < CHUNK_SIZE; ++iz) { 590 | for (ix = 0; ix < CHUNK_SIZE; ++ix) { 591 | idx0 = block_index(bx+ix, by, bz+iz); 592 | for (iy = 0; iy < CHUNK_SIZE; ++iy) { 593 | t = map_blocks[idx0 + iy] & 0xff; 594 | density = blockinfo[t].density; 595 | if (t == BLOCK_AIR) { 596 | ++nprocessed; 597 | // in sunlight, no more blocks above 598 | if ((map_blocks[idx0 + iy] & SUNLIGHT_MASK) == SUNLIGHT_MASK) 599 | break; 600 | else 601 | continue; 602 | } 603 | 604 | if (blockinfo[t].flags & BLOCK_ALPHA) { 605 | save_vi = vi; 606 | verts = alpha_buffer; 607 | vi = *alphai; 608 | } 609 | 610 | if (by+iy+1 >= MAP_BLOCK_HEIGHT) { 611 | n[2] = n[5] = n[8] = n[11] = n[14] = n[17] = n[20] = n[23] = n[26] = (SUNLIGHT_MASK|BLOCK_AIR); 612 | GETCOL(0, 2, ix - 1, iy - 1, iz - 1); 613 | GETCOL(3, 2, ix, iy - 1, iz - 1); 614 | GETCOL(6, 2, ix + 1, iy - 1, iz - 1); 615 | GETCOL(9, 2, ix - 1, iy - 1, iz); 616 | GETCOL(12, 2, ix, iy - 1, iz); 617 | GETCOL(15, 2, ix + 1, iy - 1, iz); 618 | GETCOL(18, 2, ix - 1, iy - 1, iz + 1); 619 | GETCOL(21, 2, ix, iy - 1, iz + 1); 620 | GETCOL(24, 2, ix + 1, iy - 1, iz + 1); 621 | } else if (by+iy-1 < 0) { 622 | n[0] = n[3] = n[6] = n[9] = n[12] = n[15] = n[18] = n[21] = n[24] = (SUNLIGHT_MASK|BLOCK_AIR); 623 | GETCOL(1, 2, ix - 1, iy, iz - 1); 624 | GETCOL(4, 2, ix, iy, iz - 1); 625 | GETCOL(7, 2, ix + 1, iy, iz - 1); 626 | GETCOL(10, 2, ix - 1, iy, iz); 627 | GETCOL(13, 2, ix, iy, iz); 628 | GETCOL(16, 2, ix + 1, iy, iz); 629 | GETCOL(19, 2, ix - 1, iy, iz + 1); 630 | GETCOL(22, 2, ix, iy, iz + 1); 631 | GETCOL(25, 2, ix + 1, iy, iz + 1); 632 | } else { 633 | GETCOL(0, 3, ix - 1, iy - 1, iz - 1); 634 | GETCOL(3, 3, ix, iy - 1, iz - 1); 635 | GETCOL(6, 3, ix + 1, iy - 1, iz - 1); 636 | GETCOL(9, 3, ix - 1, iy - 1, iz); 637 | GETCOL(12, 3, ix, iy - 1, iz); 638 | GETCOL(15, 3, ix + 1, iy - 1, iz); 639 | GETCOL(18, 3, ix - 1, iy - 1, iz + 1); 640 | GETCOL(21, 3, ix, iy - 1, iz + 1); 641 | GETCOL(24, 3, ix + 1, iy - 1, iz + 1); 642 | } 643 | 644 | if (BNONSOLID(14)) { 645 | assert((n[14]&0xff) != (n[13]&0xff)); 646 | const tc2us_t* tc = &BLOCKTC(t, BLOCK_TEX_TOP, 0); 647 | block_vtx_t corners[4]; 648 | corners[0].pos = POS( ix, by+iy+1, iz+1), corners[0].tc = tc[0], corners[0].clr = BLOCKLIGHT(20,23,11,14,10,19,22); 649 | corners[1].pos = POS(ix+1, by+iy+1, iz+1), corners[1].tc = tc[1], corners[1].clr = BLOCKLIGHT(23,26,14,17,16,22,25); 650 | corners[2].pos = POS(ix+1, by+iy+1, iz), corners[2].tc = tc[2], corners[2].clr = BLOCKLIGHT( 5, 8,14,17,4,7,16); 651 | corners[3].pos = POS( ix, by+iy+1, iz), corners[3].tc = tc[3], corners[3].clr = BLOCKLIGHT( 2, 5,11,14,1,4,10); 652 | if (FLIPCHECK()) { 653 | verts[vi++] = corners[0]; 654 | verts[vi++] = corners[1]; 655 | verts[vi++] = corners[2]; 656 | verts[vi++] = corners[2]; 657 | verts[vi++] = corners[3]; 658 | verts[vi++] = corners[0]; 659 | } else { 660 | verts[vi++] = corners[0]; 661 | verts[vi++] = corners[1]; 662 | verts[vi++] = corners[3]; 663 | verts[vi++] = corners[3]; 664 | verts[vi++] = corners[1]; 665 | verts[vi++] = corners[2]; 666 | } 667 | } 668 | if (BNONSOLID(12)) { 669 | const tc2us_t* tc = &BLOCKTC(t, BLOCK_TEX_BOTTOM, 0); 670 | block_vtx_t corners[4]; 671 | corners[0].pos = POS( ix, by+iy, iz), corners[0].tc = tc[0], corners[0].clr = BLOCKLIGHT( 0, 3, 9,12,1,4,10); 672 | corners[1].pos = POS(ix+1, by+iy, iz), corners[1].tc = tc[1], corners[1].clr = BLOCKLIGHT( 3, 6,12,15,4,7,16); 673 | corners[2].pos = POS(ix+1, by+iy, iz+1), corners[2].tc = tc[2], corners[2].clr = BLOCKLIGHT(12,15,21,24,16,22,25); 674 | corners[3].pos = POS( ix, by+iy, iz+1), corners[3].tc = tc[3], corners[3].clr = BLOCKLIGHT( 9,12,18,21,10,19,22); 675 | if (FLIPCHECK()) { 676 | verts[vi++] = corners[0]; 677 | verts[vi++] = corners[1]; 678 | verts[vi++] = corners[2]; 679 | verts[vi++] = corners[2]; 680 | verts[vi++] = corners[3]; 681 | verts[vi++] = corners[0]; 682 | } else { 683 | verts[vi++] = corners[0]; 684 | verts[vi++] = corners[1]; 685 | verts[vi++] = corners[3]; 686 | verts[vi++] = corners[3]; 687 | verts[vi++] = corners[1]; 688 | verts[vi++] = corners[2]; 689 | } 690 | } 691 | if (BNONSOLID(10)) { 692 | const tc2us_t* tc = &BLOCKTC(t, BLOCK_TEX_LEFT, 0); 693 | block_vtx_t corners[4]; 694 | corners[0].pos = POS(ix, by+iy, iz), corners[0].tc = tc[0], corners[0].clr = BLOCKLIGHT( 0, 1, 9,10,3,4,12); 695 | corners[1].pos = POS(ix, by+iy, iz+1), corners[1].tc = tc[1], corners[1].clr = BLOCKLIGHT( 9,10,18,19,12,21,22); 696 | corners[2].pos = POS(ix, by+iy+1, iz+1), corners[2].tc = tc[2], corners[2].clr = BLOCKLIGHT(10,11,19,20,14,22,23); 697 | corners[3].pos = POS(ix, by+iy+1, iz), corners[3].tc = tc[3], corners[3].clr = BLOCKLIGHT( 1, 2,10,11,4,5,14); 698 | if (FLIPCHECK()) { 699 | verts[vi++] = corners[0]; 700 | verts[vi++] = corners[1]; 701 | verts[vi++] = corners[2]; 702 | verts[vi++] = corners[2]; 703 | verts[vi++] = corners[3]; 704 | verts[vi++] = corners[0]; 705 | } else { 706 | verts[vi++] = corners[0]; 707 | verts[vi++] = corners[1]; 708 | verts[vi++] = corners[3]; 709 | verts[vi++] = corners[3]; 710 | verts[vi++] = corners[1]; 711 | verts[vi++] = corners[2]; 712 | } 713 | } 714 | if (BNONSOLID(16)) { 715 | const tc2us_t* tc = &BLOCKTC(t, BLOCK_TEX_RIGHT, 0); 716 | block_vtx_t corners[4]; 717 | corners[0].pos = POS(ix+1, by+iy, iz+1), corners[0].tc = tc[0], corners[0].clr = BLOCKLIGHT(15,16,24,25,12,21,22); 718 | corners[1].pos = POS(ix+1, by+iy, iz), corners[1].tc = tc[1], corners[1].clr = BLOCKLIGHT( 6, 7,15,16,3,4,12); 719 | corners[2].pos = POS(ix+1, by+iy+1, iz), corners[2].tc = tc[2], corners[2].clr = BLOCKLIGHT( 7, 8,16,17,4,5,14); 720 | corners[3].pos = POS(ix+1, by+iy+1, iz+1), corners[3].tc = tc[3], corners[3].clr = BLOCKLIGHT(16,17,25,26,14,22,23); 721 | if (FLIPCHECK()) { 722 | verts[vi++] = corners[0]; 723 | verts[vi++] = corners[1]; 724 | verts[vi++] = corners[2]; 725 | verts[vi++] = corners[2]; 726 | verts[vi++] = corners[3]; 727 | verts[vi++] = corners[0]; 728 | } else { 729 | verts[vi++] = corners[0]; 730 | verts[vi++] = corners[1]; 731 | verts[vi++] = corners[3]; 732 | verts[vi++] = corners[3]; 733 | verts[vi++] = corners[1]; 734 | verts[vi++] = corners[2]; 735 | } 736 | } 737 | if (BNONSOLID(22)) { 738 | tc2us_t* tc = &BLOCKTC(t, BLOCK_TEX_FRONT, 0); 739 | block_vtx_t corners[4]; 740 | corners[0].pos = POS( ix, by+iy, iz+1), corners[0].tc = tc[0], corners[0].clr = BLOCKLIGHT(18,19,21,22,9,10,12); 741 | corners[1].pos = POS(ix+1, by+iy, iz+1), corners[1].tc = tc[1], corners[1].clr = BLOCKLIGHT(21,22,24,25,12,15,16); 742 | corners[2].pos = POS(ix+1, by+iy+1, iz+1), corners[2].tc = tc[2], corners[2].clr = BLOCKLIGHT(22,23,25,26,14,16,17); 743 | corners[3].pos = POS( ix, by+iy+1, iz+1), corners[3].tc = tc[3], corners[3].clr = BLOCKLIGHT(19,20,22,23,10,11,14); 744 | if (FLIPCHECK()) { 745 | verts[vi++] = corners[0]; 746 | verts[vi++] = corners[1]; 747 | verts[vi++] = corners[2]; 748 | verts[vi++] = corners[2]; 749 | verts[vi++] = corners[3]; 750 | verts[vi++] = corners[0]; 751 | } else { 752 | verts[vi++] = corners[0]; 753 | verts[vi++] = corners[1]; 754 | verts[vi++] = corners[3]; 755 | verts[vi++] = corners[3]; 756 | verts[vi++] = corners[1]; 757 | verts[vi++] = corners[2]; 758 | } 759 | } 760 | if (BNONSOLID(4)) { 761 | const tc2us_t* tc = &BLOCKTC(t, BLOCK_TEX_BACK, 0); 762 | block_vtx_t corners[4]; 763 | corners[0].pos = POS(ix+1, by+iy, iz), corners[0].tc = tc[0], corners[0].clr = BLOCKLIGHT( 3, 4, 6, 7,12,15,16); 764 | corners[1].pos = POS( ix, by+iy, iz), corners[1].tc = tc[1], corners[1].clr = BLOCKLIGHT( 0, 1, 3, 4,9,10,12); 765 | corners[2].pos = POS( ix, by+iy+1, iz), corners[2].tc = tc[2], corners[2].clr = BLOCKLIGHT( 1, 2, 4, 5,10,11,14); 766 | corners[3].pos = POS(ix+1, by+iy+1, iz), corners[3].tc = tc[3], corners[3].clr = BLOCKLIGHT( 4, 5, 7, 8,14,16,17); 767 | if (FLIPCHECK()) { 768 | verts[vi++] = corners[0]; 769 | verts[vi++] = corners[1]; 770 | verts[vi++] = corners[2]; 771 | verts[vi++] = corners[2]; 772 | verts[vi++] = corners[3]; 773 | verts[vi++] = corners[0]; 774 | } else { 775 | verts[vi++] = corners[0]; 776 | verts[vi++] = corners[1]; 777 | verts[vi++] = corners[3]; 778 | verts[vi++] = corners[3]; 779 | verts[vi++] = corners[1]; 780 | verts[vi++] = corners[2]; 781 | } 782 | } 783 | 784 | if (blockinfo[t].flags & BLOCK_ALPHA) { 785 | if (vi > ALPHA_BUFFER_SIZE) 786 | fatal_error("Alpha buffer too small for chunk (%d, %d, %d): %zu verts, %zu blocks processed", bufx, cy, bufz, vi, nprocessed); 787 | *alphai = vi; 788 | vi = save_vi; 789 | verts = tesselation_buffer; 790 | } 791 | 792 | ++nprocessed; 793 | 794 | if (vi > TESSELATION_BUFFER_SIZE) 795 | fatal_error("Tesselation buffer too small for chunk (%d, %d, %d): %zu verts, %zu blocks processed", bufx, cy, bufz, vi, nprocessed); 796 | } 797 | } 798 | } 799 | 800 | if (vi > 0) { 801 | m_create_mesh(mesh, vi, verts, ML_POS_3F | ML_TC_2US | ML_CLR_4UB, GL_STATIC_DRAW); 802 | m_set_material(mesh, game.materials + MAT_CHUNK); 803 | } 804 | return (vi > 0); 805 | } 806 | 807 | bool map_raycast(dvec3_t origin, vec3_t dir, int len, ivec3_t* hit, ivec3_t* prehit) 808 | { 809 | dvec3_t blockf = { origin.x, origin.y, origin.z }; 810 | ivec3_t block = { round(origin.x), round(origin.y), round(origin.z) }; 811 | ivec3_t prev = {0, 0, 0}; 812 | int step = 32; 813 | for (int i = 0; i < len*step; ++i) { 814 | block.x = round(blockf.x); 815 | block.y = round(blockf.y); 816 | block.z = round(blockf.z); 817 | if (block.x != prev.x || block.y != prev.y || block.z != prev.z) { 818 | uint8_t t = blocktype_by_coord(block); 819 | if (t != BLOCK_AIR) { 820 | //if (game.debug_mode) { 821 | // ui_debug_block(prev, 0xff0000ff); 822 | // ui_debug_block(block, 0xff00ff00); 823 | //} 824 | if (hit != NULL) 825 | *hit = block; 826 | if (prehit != NULL) 827 | *prehit = prev; 828 | return true; 829 | } 830 | prev = block; 831 | } 832 | blockf.x += dir.x / step; 833 | blockf.y += dir.y / step; 834 | blockf.z += dir.z / step; 835 | } 836 | return false; 837 | } 838 | -------------------------------------------------------------------------------- /src/map.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | #include "math3d.h" 4 | 5 | /* 6 | 7 | * voxel map = regions + chunks + ocean + trees + caves + mountains 8 | 9 | */ 10 | 11 | #include "blocks.h" 12 | 13 | #define CHUNK_SIZE 16 14 | #define MAX_SUBCHUNKS 64 // allow chunks populated across 1km (!) 15 | #define CHUNK_SIZE_STR(s) CHUNK_SIZE_STR_2(s) 16 | #define CHUNK_SIZE_STR_2(s) #s 17 | #define VIEW_DISTANCE 16 18 | #define OCEAN_LEVEL 32 19 | #define MAP_CHUNK_WIDTH (VIEW_DISTANCE*2) 20 | #define MAP_CHUNK_HEIGHT 8 21 | #define MAP_BLOCK_WIDTH (MAP_CHUNK_WIDTH*CHUNK_SIZE) 22 | #define MAP_BLOCK_HEIGHT (MAP_CHUNK_HEIGHT*CHUNK_SIZE) 23 | #define MAP_BUFFER_SIZE (MAP_BLOCK_WIDTH*MAP_BLOCK_WIDTH*MAP_BLOCK_HEIGHT) 24 | 25 | #pragma pack(push, 1) 26 | 27 | typedef struct tc2us_t { 28 | uint16_t u; 29 | uint16_t v; 30 | } tc2us_t; 31 | 32 | typedef struct block_vtx_t { 33 | vec3_t pos; 34 | // uint32_t n; // normal + extra uint8 value (water depth?) 35 | tc2us_t tc; 36 | uint32_t clr; 37 | } block_vtx_t; 38 | 39 | typedef struct block_face_t { 40 | block_vtx_t vtx[3]; 41 | } block_face_t; 42 | 43 | #pragma pack(pop) 44 | 45 | enum ChunkGenState { 46 | CHUNK_GEN_S0, /* no blocks generated for this chunk yet */ 47 | CHUNK_GEN_S1, /* base terrain blocks generated */ 48 | CHUNK_GEN_S2, /* structures / plants generated */ 49 | CHUNK_GEN_S3, /* light fully propagated */ 50 | }; 51 | 52 | enum ChunkMeshState { 53 | CHUNK_MESH_S0, /* no mesh generated */ 54 | CHUNK_MESH_S1, /* gen-state 1 mesh generated */ 55 | CHUNK_MESH_S2, /* gen-state 2 mesh generated */ 56 | CHUNK_MESH_S3 57 | }; 58 | 59 | enum ChunkFlags { 60 | BLOCK_CHANGED, 61 | }; 62 | 63 | typedef struct game_subchunk { 64 | uint32_t block[CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE]; 65 | } game_subchunk; 66 | 67 | typedef struct game_chunk { 68 | int x; // actual coordinates of chunk 69 | int z; 70 | bool dirty; 71 | uint32_t genstate; 72 | uint32_t meshstate; 73 | int offset_y; 74 | int height_y; 75 | uint32_t subchunks[MAX_SUBCHUNKS]; // number of 16x16x16 subchunks 76 | // can have special subchunk indices for special subchunk types (all-air, all-solid...) 77 | mesh_t solid[MAP_CHUNK_HEIGHT]; // a solid mesh for each subchunk 78 | mesh_t alpha; 79 | mesh_t sprite; // render twosided (same shader as solid meshes but different render state) 80 | // add per-chunk state information here (things like command blocks..., entities?) 81 | } game_chunk; 82 | 83 | // uint32_t allocate_subchunk(map); 84 | // void free_subchunk(map, uint32_t); 85 | // game_subchunk* get_subchunk(map, uint32_t); 86 | // 87 | 88 | // block: 89 | // SSSSRRRRGGGGBBBBMMMMMMMMTTTTTTTT 90 | // S: sunlight value 0-15 91 | // R: red lamplight value 0-15 92 | // G: green lamplight value 0-15 93 | // B: blue lamplight value 0-15 94 | // M: metadata 0-255 95 | // T: blocktype 0-255 96 | 97 | struct game_map { 98 | game_chunk chunks[MAP_CHUNK_WIDTH*MAP_CHUNK_WIDTH]; 99 | // caches the block data for the area around the player 100 | // blocks = [blockid | meta | sunlightlevel | torchlightlevel ] 101 | unsigned long seed; 102 | }; 103 | 104 | extern uint32_t* map_blocks; 105 | 106 | void map_init(void); 107 | void map_exit(void); 108 | void map_tick(void); 109 | void map_draw(frustum_t* frustum); 110 | void map_draw_alphapass(void); 111 | void chunk_load(int x, int z); 112 | void chunk_mark_dirty(int x, int z); 113 | void chunk_build_mesh_ptr(int bufx, int bufz, game_chunk* chunk); 114 | void chunk_build_mesh(int x, int z); 115 | void map_update_block(ivec3_t block, uint32_t value); 116 | bool map_raycast(dvec3_t origin, vec3_t dir, int len, ivec3_t* hit, ivec3_t* prehit); 117 | uint32_t block_at(int x, int y, int z); 118 | 119 | 120 | // mod which handles negative numbers 121 | static inline 122 | int mod(int a, int b) 123 | { 124 | if (b < 0) { a = -a; b = -b; } 125 | int ret = a % b; 126 | return (ret < 0) ? ret + b : ret; 127 | } 128 | 129 | 130 | // elements xyz are 0-32 131 | // w should be 0-3 132 | static inline 133 | uint32_t packvec_1010102(unsigned int x, unsigned int y, unsigned int z, unsigned int w) 134 | { 135 | assert(x <= CHUNK_SIZE && y <= CHUNK_SIZE && z <= CHUNK_SIZE && w < 4); 136 | return ((x)*(unsigned int)(1023/CHUNK_SIZE)) + (((y)*(unsigned int)(1023/CHUNK_SIZE))<<10) + (((z)*(unsigned int)(1023/CHUNK_SIZE))<<20) + ((w)<<30); 137 | } 138 | 139 | 140 | static inline 141 | size_t block_index(int x, int y, int z) 142 | { 143 | return mod(z, MAP_BLOCK_WIDTH) * (MAP_BLOCK_WIDTH * MAP_BLOCK_HEIGHT) + 144 | mod(x, MAP_BLOCK_WIDTH) * MAP_BLOCK_HEIGHT + 145 | y; 146 | } 147 | 148 | 149 | static inline 150 | uint32_t* block_column(int x, int z) 151 | { 152 | return map_blocks + 153 | mod(z, MAP_BLOCK_WIDTH) * (MAP_BLOCK_WIDTH * MAP_BLOCK_HEIGHT) + 154 | mod(x, MAP_BLOCK_WIDTH) * MAP_BLOCK_HEIGHT; 155 | } 156 | 157 | 158 | static inline 159 | uint32_t blocktype(int x, int y, int z) 160 | { 161 | return block_at(x, y, z) & 0xff; 162 | } 163 | 164 | 165 | static inline 166 | size_t block_by_coord(ivec3_t xyz) 167 | { 168 | return block_index(xyz.x, xyz.y, xyz.z); 169 | } 170 | 171 | 172 | static inline 173 | uint32_t blocktype_by_coord(ivec3_t xyz) 174 | { 175 | return blocktype(xyz.x, xyz.y, xyz.z); 176 | } 177 | 178 | 179 | static inline 180 | chunkpos_t block_chunk(ivec3_t block) 181 | { 182 | chunkpos_t c = { floor(round(block.x) / CHUNK_SIZE), 183 | floor(round(block.z) / CHUNK_SIZE) }; 184 | return c; 185 | } 186 | 187 | 188 | static inline 189 | bool is_collider(int x, int y, int z) { 190 | return blockinfo[blocktype(x, y, z)].flags & BLOCK_COLLIDER; 191 | } 192 | -------------------------------------------------------------------------------- /src/math3d.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "math3d.h" 3 | #define STB_IMAGE_IMPLEMENTATION 4 | #define STB_IMAGE_WRITE_IMPLEMENTATION 5 | #include "stb_image.h" 6 | #include "stb_image_write.h" 7 | 8 | const vec3_t m_up = {0, 1, 0}; 9 | const vec3_t m_right = {1, 0, 0}; 10 | const vec3_t m_forward = {0, 0, -1}; 11 | 12 | 13 | void m_perspective(mat44_t* m, float fovy, float aspect, float zNear, float zFar) 14 | { 15 | m_setidentity(m); 16 | float r = fovy * 0.5f; 17 | float f = cos(r) / sin(r); 18 | m->m[0] = f / aspect; 19 | m->m[5] = f; 20 | m->m[10] = -(zFar + zNear) / (zFar - zNear); 21 | m->m[11] = -1.f; 22 | m->m[14] = (-2.f * zFar * zNear) / (zFar - zNear); 23 | m->m[15] = 0; 24 | } 25 | 26 | void m_setidentity(mat44_t* m) 27 | { 28 | memset(m->m, 0, sizeof(float)*16); 29 | m->m[0] = m->m[5] = m->m[10] = m->m[15] = 1.f; 30 | } 31 | 32 | void m_fps_rotation(float pitch, float yaw, vec3_t* x, vec3_t* y, vec3_t* z) 33 | { 34 | float cosPitch = cosf(pitch); 35 | float sinPitch = sinf(pitch); 36 | float cosYaw = cosf(yaw); 37 | float sinYaw = sinf(yaw); 38 | x->x = cosYaw; 39 | x->y = 0; 40 | x->z = -sinYaw; 41 | y->x = sinYaw * sinPitch; 42 | y->y = cosPitch; 43 | y->z = cosYaw * sinPitch; 44 | z->x = sinYaw * cosPitch; 45 | z->y = -sinPitch; 46 | z->z = cosPitch * cosYaw; 47 | } 48 | 49 | void m_fpsmatrix(mat44_t* to, vec3_t eye, float pitch, float yaw) 50 | { 51 | /* equivalent to: 52 | m_setidentity(to); 53 | m_rotate(to, -pitch, 1.f, 0, 0); 54 | m_rotate(to, -yaw, 0, 1.f, 0); 55 | m_translate(to, -eye.x, -eye.y, -eye.z); 56 | */ 57 | float cosPitch = cosf(pitch); 58 | float sinPitch = sinf(pitch); 59 | float cosYaw = cosf(yaw); 60 | float sinYaw = sinf(yaw); 61 | vec3_t xaxis = { cosYaw, 0, -sinYaw }; 62 | vec3_t yaxis = { sinYaw * sinPitch, cosPitch, cosYaw * sinPitch }; 63 | vec3_t zaxis = { sinYaw * cosPitch, -sinPitch, cosPitch * cosYaw }; 64 | to->m[0] = xaxis.x; 65 | to->m[1] = yaxis.x; 66 | to->m[2] = zaxis.x; 67 | to->m[3] = 0; 68 | to->m[4] = xaxis.y; 69 | to->m[5] = yaxis.y; 70 | to->m[6] = zaxis.y; 71 | to->m[7] = 0; 72 | to->m[8] = xaxis.z; 73 | to->m[9] = yaxis.z; 74 | to->m[10] = zaxis.z; 75 | to->m[11] = 0; 76 | to->m[12] = -m_vec3dot(xaxis, eye); 77 | to->m[13] = -m_vec3dot(yaxis, eye); 78 | to->m[14] = -m_vec3dot(zaxis, eye); 79 | to->m[15] = 1.f; 80 | } 81 | 82 | 83 | void m_lookat(mat44_t* m, vec3_t eye, vec3_t at, vec3_t up) 84 | { 85 | mat44_t M; 86 | vec3_t f, u, s; 87 | 88 | m_setvec3(f, at.x - eye.x, at.y - eye.y, at.z - eye.z); 89 | f = m_vec3normalize(f); 90 | 91 | // check for when we're looking straight up or down Y 92 | if (fabsf(f.x) < ML_EPSILON && fabsf(f.z) < ML_EPSILON && fabsf(up.x) < ML_EPSILON && fabsf(up.z) < ML_EPSILON) 93 | m_setvec3(up, 0, 0, (f.y < 0) ? -1 : 1); 94 | 95 | m_setvec3(u, up.x, up.y, up.z); 96 | s = m_vec3normalize(m_vec3cross(f, u)); 97 | u = m_vec3normalize(m_vec3cross(s, f)); 98 | 99 | M.m[0] = s.x; 100 | M.m[4] = s.y; 101 | M.m[8] = s.z; 102 | M.m[1] = u.x; 103 | M.m[5] = u.y; 104 | M.m[9] = u.z; 105 | M.m[2] = -f.x; 106 | M.m[6] = -f.y; 107 | M.m[10] = -f.z; 108 | M.m[3] = M.m[7] = M.m[11] = M.m[12] = M.m[13] = M.m[14] = 0; 109 | M.m[15] = 1.f; 110 | m_copymat(m, &M); 111 | m_translate(m, -eye.x, -eye.y, -eye.z); 112 | } 113 | 114 | void m_copymat(mat44_t* to, const mat44_t* from) 115 | { 116 | memcpy(to, from, sizeof(mat44_t)); 117 | } 118 | 119 | void m_getmat33(mat33_t* to, const mat44_t* from) 120 | { 121 | float*__restrict__ a = to->m; 122 | const float*__restrict__ b = from->m; 123 | a[0] = b[0]; 124 | a[1] = b[1]; 125 | a[2] = b[2]; 126 | a[3] = b[4]; 127 | a[4] = b[5]; 128 | a[5] = b[6]; 129 | a[6] = b[8]; 130 | a[7] = b[9]; 131 | a[8] = b[10]; 132 | } 133 | 134 | void 135 | m_makefrustum(frustum_t* frustum, mat44_t* projection, mat44_t* view) 136 | { 137 | mat44_t MP; 138 | m_copymat(&MP, projection); 139 | m_matmul(&MP, view); 140 | vec4_t*__restrict__ p = frustum->planes; 141 | vec4_t*__restrict__ a = frustum->absplanes; 142 | float* m = MP.m; 143 | // right 144 | m_setvec4(p[0], m[3] - m[0], m[7] - m[4], m[11] - m[8], m[15] - m[12]); 145 | // left 146 | m_setvec4(p[1], m[3] + m[0], m[7] + m[4], m[11] + m[8], m[15] + m[12]); 147 | // down 148 | m_setvec4(p[2], m[3] + m[1], m[7] + m[5], m[11] + m[9], m[15] + m[13]); 149 | // up 150 | m_setvec4(p[3], m[3] - m[1], m[7] - m[5], m[11] - m[9], m[15] - m[13]); 151 | // far 152 | m_setvec4(p[4], m[3] - m[2], m[7] - m[6], m[11] - m[10], m[15] - m[14]); 153 | // near 154 | m_setvec4(p[5], m[3] + m[2], m[7] + m[6], m[11] + m[10], m[15] + m[14]); 155 | for (int i = 0; i < 6; ++i) { 156 | p[i] = m_normalize_plane(p[i]); 157 | a[i] = m_vec4abs(p[i]); 158 | } 159 | } 160 | 161 | // center: (max + min)/2 162 | // extents: (max - min)/2 163 | int collide_frustum_aabb(frustum_t* frustum, vec3_t center, vec3_t extent) 164 | { 165 | int result, i; 166 | vec4_t p, a; 167 | result = ML_INSIDE; 168 | for (i = 0; i < 6; ++i) { 169 | p = frustum->planes[i]; 170 | a = frustum->absplanes[i]; 171 | 172 | float d = center.x * p.x + center.y * p.y + center.z * p.z + p.w; 173 | float r = extent.x * a.x + extent.y * a.y + extent.z * a.z + a.w; 174 | if (d + r <= 0) return ML_OUTSIDE; 175 | if (d - r < 0) result = ML_INTERSECT; 176 | } 177 | return result; 178 | } 179 | 180 | // these are very voxelgame-specific 181 | int collide_frustum_aabb_xz(frustum_t* frustum, vec3_t center, vec3_t extent) 182 | { 183 | int result, i; 184 | vec4_t p, a; 185 | result = ML_INSIDE; 186 | for (i = 0; i < 6; ++i) { 187 | if (i == 2 || i == 3) continue; 188 | p = frustum->planes[i]; 189 | a = frustum->absplanes[i]; 190 | float d = center.x * p.x + center.y * p.y + center.z * p.z + p.w; 191 | float r = extent.x * a.x + extent.y * a.y + extent.z * a.z + a.w; 192 | if (d + r <= 0) return ML_OUTSIDE; 193 | if (d - r < 0) result = ML_INTERSECT; 194 | } 195 | return result; 196 | } 197 | 198 | int collide_frustum_aabb_y(frustum_t* frustum, vec3_t center, vec3_t extent) 199 | { 200 | int result, i; 201 | vec4_t p, a; 202 | result = ML_INSIDE; 203 | for (i = 2; i < 4; ++i) { 204 | p = frustum->planes[i]; 205 | a = frustum->absplanes[i]; 206 | float d = center.x * p.x + center.y * p.y + center.z * p.z + p.w; 207 | float r = extent.x * a.x + extent.y * a.y + extent.z * a.z + a.w; 208 | if (d + r <= 0) return ML_OUTSIDE; 209 | if (d - r < 0) result = ML_INTERSECT; 210 | } 211 | return result; 212 | } 213 | 214 | 215 | vec3_t m_rotatevec3(const mat44_t* m, const vec3_t *v) 216 | { 217 | vec3_t ret; 218 | ret.x = (v->x * m->m[0]) + 219 | (v->y * m->m[4]) + 220 | (v->z * m->m[8]); 221 | ret.y = (v->x * m->m[1]) + 222 | (v->y * m->m[5]) + 223 | (v->z * m->m[9]); 224 | ret.z = (v->x * m->m[2]) + 225 | (v->y * m->m[6]) + 226 | (v->z * m->m[10]); 227 | return ret; 228 | } 229 | 230 | 231 | vec3_t m_matmul33(const mat33_t* m, const vec3_t* v) 232 | { 233 | vec3_t ret; 234 | ret.x = (v->x * m->m[0]) + 235 | (v->y * m->m[3]) + 236 | (v->z * m->m[6]); 237 | ret.y = (v->x * m->m[1]) + 238 | (v->y * m->m[4]) + 239 | (v->z * m->m[7]); 240 | ret.z = (v->x * m->m[2]) + 241 | (v->y * m->m[5]) + 242 | (v->z * m->m[8]); 243 | return ret; 244 | } 245 | 246 | void 247 | m_matmul(mat44_t* to, const mat44_t* by) 248 | { 249 | const float*__restrict__ a = to->m; 250 | const float*__restrict__ b = by->m; 251 | float m[16]; 252 | 253 | m[0] = a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3]; 254 | m[1] = a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3]; 255 | m[2] = a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3]; 256 | m[3] = a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3]; 257 | m[4] = a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7]; 258 | m[5] = a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7]; 259 | m[6] = a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7]; 260 | m[7] = a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7]; 261 | m[8] = a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11]; 262 | m[9] = a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11]; 263 | m[10] = a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11]; 264 | m[11] = a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11]; 265 | m[12] = a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15]; 266 | m[13] = a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15]; 267 | m[14] = a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15]; 268 | m[15] = a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15]; 269 | 270 | memcpy(to->m, m, 16 * sizeof(float)); 271 | } 272 | 273 | void 274 | m_translate(mat44_t* m, float x, float y, float z) 275 | { 276 | mat44_t trans; 277 | m_setidentity(&trans); 278 | trans.m[12] = x; 279 | trans.m[13] = y; 280 | trans.m[14] = z; 281 | m_matmul(m, &trans); 282 | } 283 | 284 | void 285 | m_rotate(mat44_t* m, float angle, float x, float y, float z) 286 | { 287 | float cosa = cosf(angle); 288 | float sina = sinf(angle); 289 | float icosa = 1.f - cosa; 290 | float r[16] = { 291 | cosa + x*x*icosa, y*x*icosa + z*sina, z*x*icosa - y*sina, 0.f, 292 | x*y*icosa - z*sina, cosa + y*y*icosa, z*y*icosa + x*sina, 0.f, 293 | x*z*icosa + y*sina, y*z*icosa - x*sina, cosa + z*z*icosa, 0.f, 294 | 0.f, 0.f, 0.f, 1.f 295 | }; 296 | m_matmul(m, (mat44_t*)&r); 297 | } 298 | 299 | 300 | void 301 | m_transpose(mat44_t* m) 302 | { 303 | #if 0 304 | __m128 col1 = _mm_load_ps(&m->m[0]); 305 | __m128 col2 = _mm_load_ps(&m->m[4]); 306 | __m128 col3 = _mm_load_ps(&m->m[8]); 307 | __m128 col4 = _mm_load_ps(&m->m[12]); 308 | _MM_TRANSPOSE4_PS(col1, col2, col3, col4); 309 | _mm_store_ps(&m->m[0], col1); 310 | _mm_store_ps(&m->m[4], col2); 311 | _mm_store_ps(&m->m[8], col3); 312 | _mm_store_ps(&m->m[12], col4); 313 | #else 314 | ML_SWAP(m->m[1], m->m[4]); 315 | ML_SWAP(m->m[2], m->m[8]); 316 | ML_SWAP(m->m[3], m->m[12]); 317 | ML_SWAP(m->m[6], m->m[9]); 318 | ML_SWAP(m->m[7], m->m[13]); 319 | ML_SWAP(m->m[11], m->m[14]); 320 | #endif 321 | } 322 | 323 | void m_transpose33(mat33_t* m) 324 | { 325 | ML_SWAP(m->m[1], m->m[3]); 326 | ML_SWAP(m->m[2], m->m[6]); 327 | ML_SWAP(m->m[5], m->m[7]); 328 | } 329 | 330 | 331 | bool m_invert(mat44_t* to, const mat44_t* from) 332 | { 333 | float inv[16]; 334 | float* out = to->m; 335 | const float* m = from->m; 336 | int i; 337 | float det; 338 | 339 | inv[0] = m[5] * m[10] * m[15] - 340 | m[5] * m[11] * m[14] - 341 | m[9] * m[6] * m[15] + 342 | m[9] * m[7] * m[14] + 343 | m[13] * m[6] * m[11] - 344 | m[13] * m[7] * m[10]; 345 | 346 | inv[4] = -m[4] * m[10] * m[15] + 347 | m[4] * m[11] * m[14] + 348 | m[8] * m[6] * m[15] - 349 | m[8] * m[7] * m[14] - 350 | m[12] * m[6] * m[11] + 351 | m[12] * m[7] * m[10]; 352 | 353 | inv[8] = m[4] * m[9] * m[15] - 354 | m[4] * m[11] * m[13] - 355 | m[8] * m[5] * m[15] + 356 | m[8] * m[7] * m[13] + 357 | m[12] * m[5] * m[11] - 358 | m[12] * m[7] * m[9]; 359 | 360 | inv[12] = -m[4] * m[9] * m[14] + 361 | m[4] * m[10] * m[13] + 362 | m[8] * m[5] * m[14] - 363 | m[8] * m[6] * m[13] - 364 | m[12] * m[5] * m[10] + 365 | m[12] * m[6] * m[9]; 366 | 367 | inv[1] = -m[1] * m[10] * m[15] + 368 | m[1] * m[11] * m[14] + 369 | m[9] * m[2] * m[15] - 370 | m[9] * m[3] * m[14] - 371 | m[13] * m[2] * m[11] + 372 | m[13] * m[3] * m[10]; 373 | 374 | inv[5] = m[0] * m[10] * m[15] - 375 | m[0] * m[11] * m[14] - 376 | m[8] * m[2] * m[15] + 377 | m[8] * m[3] * m[14] + 378 | m[12] * m[2] * m[11] - 379 | m[12] * m[3] * m[10]; 380 | 381 | inv[9] = -m[0] * m[9] * m[15] + 382 | m[0] * m[11] * m[13] + 383 | m[8] * m[1] * m[15] - 384 | m[8] * m[3] * m[13] - 385 | m[12] * m[1] * m[11] + 386 | m[12] * m[3] * m[9]; 387 | 388 | inv[13] = m[0] * m[9] * m[14] - 389 | m[0] * m[10] * m[13] - 390 | m[8] * m[1] * m[14] + 391 | m[8] * m[2] * m[13] + 392 | m[12] * m[1] * m[10] - 393 | m[12] * m[2] * m[9]; 394 | 395 | inv[2] = m[1] * m[6] * m[15] - 396 | m[1] * m[7] * m[14] - 397 | m[5] * m[2] * m[15] + 398 | m[5] * m[3] * m[14] + 399 | m[13] * m[2] * m[7] - 400 | m[13] * m[3] * m[6]; 401 | 402 | inv[6] = -m[0] * m[6] * m[15] + 403 | m[0] * m[7] * m[14] + 404 | m[4] * m[2] * m[15] - 405 | m[4] * m[3] * m[14] - 406 | m[12] * m[2] * m[7] + 407 | m[12] * m[3] * m[6]; 408 | 409 | inv[10] = m[0] * m[5] * m[15] - 410 | m[0] * m[7] * m[13] - 411 | m[4] * m[1] * m[15] + 412 | m[4] * m[3] * m[13] + 413 | m[12] * m[1] * m[7] - 414 | m[12] * m[3] * m[5]; 415 | 416 | inv[14] = -m[0] * m[5] * m[14] + 417 | m[0] * m[6] * m[13] + 418 | m[4] * m[1] * m[14] - 419 | m[4] * m[2] * m[13] - 420 | m[12] * m[1] * m[6] + 421 | m[12] * m[2] * m[5]; 422 | 423 | inv[3] = -m[1] * m[6] * m[11] + 424 | m[1] * m[7] * m[10] + 425 | m[5] * m[2] * m[11] - 426 | m[5] * m[3] * m[10] - 427 | m[9] * m[2] * m[7] + 428 | m[9] * m[3] * m[6]; 429 | 430 | inv[7] = m[0] * m[6] * m[11] - 431 | m[0] * m[7] * m[10] - 432 | m[4] * m[2] * m[11] + 433 | m[4] * m[3] * m[10] + 434 | m[8] * m[2] * m[7] - 435 | m[8] * m[3] * m[6]; 436 | 437 | inv[11] = -m[0] * m[5] * m[11] + 438 | m[0] * m[7] * m[9] + 439 | m[4] * m[1] * m[11] - 440 | m[4] * m[3] * m[9] - 441 | m[8] * m[1] * m[7] + 442 | m[8] * m[3] * m[5]; 443 | 444 | inv[15] = m[0] * m[5] * m[10] - 445 | m[0] * m[6] * m[9] - 446 | m[4] * m[1] * m[10] + 447 | m[4] * m[2] * m[9] + 448 | m[8] * m[1] * m[6] - 449 | m[8] * m[2] * m[5]; 450 | 451 | det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]; 452 | 453 | if (det == 0) 454 | return false; 455 | 456 | det = 1.0 / det; 457 | 458 | for (i = 0; i < 16; i++) 459 | out[i] = inv[i] * det; 460 | 461 | return true; 462 | } 463 | 464 | // Must be orthonormal 465 | void m_invert_orthonormal(mat44_t* to, const mat44_t* from) 466 | { 467 | mat33_t R; 468 | vec3_t T; 469 | m_getmat33(&R, from); 470 | m_transpose33(&R); 471 | m_setvec3(T, -from->m[12], -from->m[13], -from->m[14]); 472 | T = m_matmul33(&R, &T); 473 | to->m[0] = R.m[0]; 474 | to->m[1] = R.m[1]; 475 | to->m[2] = R.m[2]; 476 | to->m[4] = R.m[3]; 477 | to->m[5] = R.m[4]; 478 | to->m[6] = R.m[5]; 479 | to->m[8] = R.m[6]; 480 | to->m[9] = R.m[7]; 481 | to->m[10] = R.m[8]; 482 | to->m[12] = T.x; 483 | to->m[13] = T.y; 484 | to->m[14] = T.z; 485 | to->m[3] = to->m[7] = to->m[11] = 0; 486 | to->m[15] = 1.f; 487 | } 488 | 489 | 490 | vec4_t m_matmulvec(const mat44_t* m, const vec4_t* v) 491 | { 492 | vec4_t ret; 493 | ret.x = (v->x * m->m[0]) + 494 | (v->y * m->m[4]) + 495 | (v->z * m->m[8]) + 496 | (v->w * m->m[12]); 497 | ret.y = (v->x * m->m[1]) + 498 | (v->y * m->m[5]) + 499 | (v->z * m->m[9]) + 500 | (v->w * m->m[13]); 501 | ret.z = (v->x * m->m[2]) + 502 | (v->y * m->m[6]) + 503 | (v->z * m->m[10]) + 504 | (v->w * m->m[14]); 505 | ret.w = (v->x * m->m[3]) + 506 | (v->y * m->m[7]) + 507 | (v->z * m->m[11]) + 508 | (v->w * m->m[15]); 509 | return ret; 510 | } 511 | 512 | vec3_t m_matmulvec3(const mat44_t* m, const vec3_t* v) 513 | { 514 | vec3_t ret; 515 | ret.x = (v->x * m->m[0]) + 516 | (v->y * m->m[4]) + 517 | (v->z * m->m[8]) + 518 | (1.f * m->m[12]); 519 | ret.y = (v->x * m->m[1]) + 520 | (v->y * m->m[5]) + 521 | (v->z * m->m[9]) + 522 | (1.f * m->m[13]); 523 | ret.z = (v->x * m->m[2]) + 524 | (v->y * m->m[6]) + 525 | (v->z * m->m[10]) + 526 | (1.f * m->m[14]); 527 | return ret; 528 | } 529 | 530 | 531 | GLuint m_compile_shader(GLenum type, const char* source) 532 | { 533 | GLuint name; 534 | GLint status; 535 | name = glCreateShader(type); 536 | glShaderSource(name, 1, &source, NULL); 537 | glCompileShader(name); 538 | glGetShaderiv(name, GL_COMPILE_STATUS, &status); 539 | if (status == GL_FALSE) { 540 | GLint length; 541 | GLchar* msg; 542 | glGetShaderiv(name, GL_INFO_LOG_LENGTH, &length); 543 | msg = (GLchar*)malloc(length); 544 | glGetShaderInfoLog(name, length, NULL, msg); 545 | fprintf(stderr, "glCompileShader failed: %s\n", msg); 546 | free(msg); 547 | glDeleteShader(name); 548 | name = 0; 549 | } 550 | return name; 551 | } 552 | 553 | GLuint m_link_program(GLuint vsh, GLuint fsh) 554 | { 555 | GLuint program; 556 | GLint status; 557 | program = glCreateProgram(); 558 | glAttachShader(program, vsh); 559 | glAttachShader(program, fsh); 560 | glLinkProgram(program); 561 | glGetProgramiv(program, GL_LINK_STATUS, &status); 562 | if (status == GL_FALSE) { 563 | GLint length; 564 | GLchar* msg; 565 | glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length); 566 | msg = (GLchar*)malloc(length); 567 | glGetProgramInfoLog(program, length, NULL, msg); 568 | fprintf(stderr, "glLinkProgram failed: %s\n", msg); 569 | free(msg); 570 | glDeleteProgram(program); 571 | program = 0; 572 | } 573 | glDetachShader(program, vsh); 574 | glDetachShader(program, fsh); 575 | return program; 576 | } 577 | 578 | 579 | void m_create_material(material_t* material, const char* vsource, const char* fsource) 580 | { 581 | GLuint vshader, fshader, program; 582 | vshader = m_compile_shader(GL_VERTEX_SHADER, vsource); 583 | fshader = m_compile_shader(GL_FRAGMENT_SHADER, fsource); 584 | if (vshader == 0) 585 | fatal_error("vshader source: %s", vsource); 586 | if (fshader == 0) 587 | fatal_error("fshader source: %s", fsource); 588 | program = m_link_program(vshader, fshader); 589 | glDeleteShader(vshader); 590 | glDeleteShader(fshader); 591 | material->program = program; 592 | glUseProgram(program); 593 | material->projmat = glGetUniformLocation(program, "projmat"); 594 | material->modelview = glGetUniformLocation(program, "modelview"); 595 | material->normalmat = glGetUniformLocation(program, "normalmat"); 596 | material->chunk_offset = glGetUniformLocation(program, "chunk_offset"); 597 | material->amb_light = glGetUniformLocation(program, "amb_light"); 598 | material->fog_color = glGetUniformLocation(program, "fog_color"); 599 | material->light_dir = glGetUniformLocation(program, "light_dir"); 600 | material->sun_dir = glGetUniformLocation(program, "sun_dir"); 601 | material->sun_color = glGetUniformLocation(program, "sun_color"); 602 | material->sky_dark = glGetUniformLocation(program, "sky_dark"); 603 | material->sky_light = glGetUniformLocation(program, "sky_light"); 604 | material->tex0 = glGetUniformLocation(program, "tex0"); 605 | glUseProgram(0); 606 | } 607 | 608 | void m_destroy_material(material_t* material) 609 | { 610 | if (material->program != 0) 611 | glDeleteProgram(material->program); 612 | material->program = 0; 613 | } 614 | 615 | static inline GLsizei mesh_stride(GLenum flags) 616 | { 617 | return ((flags & ML_POS_2F) ? 8 : 0) + 618 | ((flags & ML_POS_3F) ? 12 : 0) + 619 | ((flags & ML_POS_4UB) ? 4 : 0) + 620 | ((flags & ML_POS_10_2) ? 4 : 0) + 621 | ((flags & ML_CLR_4UB) ? 4 : 0) + 622 | ((flags & ML_N_3F) ? 12 : 0) + 623 | ((flags & ML_N_4B) ? 4 : 0) + 624 | ((flags & ML_TC_2F) ? 8 : 0) + 625 | ((flags & ML_TC_2US) ? 4 : 0); 626 | } 627 | 628 | void m_create_mesh(mesh_t* mesh, size_t n, void* data, GLenum flags, GLenum usage) 629 | { 630 | M_CHECKGL(glGenVertexArrays(1, &mesh->vao)); 631 | GLsizei stride = mesh_stride(flags); 632 | M_CHECKGL(glGenBuffers(1, &mesh->vbo)); 633 | M_CHECKGL(glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo)); 634 | M_CHECKGL(glBufferData(GL_ARRAY_BUFFER, (GLsizei)n * stride, data, usage)); 635 | M_CHECKGL(glBindBuffer(GL_ARRAY_BUFFER, 0)); 636 | 637 | GLint offset = 0; 638 | mesh->position = (flags & (ML_POS_2F + ML_POS_3F + ML_POS_4UB + ML_POS_10_2)) ? offset : -1; 639 | offset += (flags & ML_POS_2F) ? 8 : ((flags & ML_POS_3F) ? 12 : (flags & (ML_POS_4UB + ML_POS_10_2) ? 4 : 0)); 640 | mesh->normal = (flags & (ML_N_3F + ML_N_4B)) ? offset : -1; 641 | offset += (flags & ML_N_3F) ? 12 : ((flags & ML_N_4B) ? 4 : 0); 642 | mesh->texcoord = (flags & (ML_TC_2F + ML_TC_2US)) ? offset : -1; 643 | offset += (flags & ML_TC_2F) ? 8 : ((flags & ML_TC_2US) ? 4 : 0); 644 | mesh->color = (flags & ML_CLR_4UB) ? offset : -1; 645 | offset += (flags & ML_CLR_4UB) ? 4 : 0; 646 | mesh->stride = stride; 647 | mesh->mode = GL_TRIANGLES; // TODO: allow other modes? 648 | mesh->count = (GLsizei)n; 649 | mesh->flags = flags; 650 | mesh->ibo = 0; // no index buffer yet 651 | mesh->ibotype = 0; 652 | mesh->material = NULL; // no material bound yet 653 | } 654 | 655 | void m_update_mesh(mesh_t* mesh, GLintptr offset, GLsizeiptr n, const void* data) 656 | { 657 | glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo); 658 | glBufferSubData(GL_ARRAY_BUFFER, offset, n, data); 659 | glBindBuffer(GL_ARRAY_BUFFER, 0); 660 | } 661 | 662 | void m_replace_mesh(mesh_t* mesh, GLsizeiptr n, const void* data, GLenum usage) 663 | { 664 | glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo); 665 | glBufferData(GL_ARRAY_BUFFER, n, data, usage); 666 | glBindBuffer(GL_ARRAY_BUFFER, 0); 667 | } 668 | 669 | void m_create_indexed_mesh(mesh_t* mesh, size_t n, void* data, size_t ilen, GLenum indextype, void* indices, GLenum flags) 670 | { 671 | m_create_mesh(mesh, n, data, flags, GL_STATIC_DRAW); 672 | M_CHECKGL(glGenBuffers(1, &mesh->ibo)); 673 | 674 | GLsizei isize = 0; 675 | switch (indextype) { 676 | case GL_UNSIGNED_BYTE: 677 | isize = 1; break; 678 | case GL_UNSIGNED_SHORT: 679 | isize = 2; break; 680 | case GL_UNSIGNED_INT: 681 | isize = 4; break; 682 | default: 683 | fatal_error("indextype must be one of GL_UNSIGNED_[BYTE|SHORT|INT]"); 684 | } 685 | 686 | M_CHECKGL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->ibo)); 687 | M_CHECKGL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizei)ilen * isize, indices, GL_STATIC_DRAW)); 688 | M_CHECKGL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); 689 | 690 | mesh->count = (GLsizei)ilen; 691 | mesh->ibotype = indextype; 692 | } 693 | 694 | void m_destroy_mesh(mesh_t* mesh) 695 | { 696 | if (mesh->vbo != 0) { M_CHECKGL(glDeleteBuffers(1, &(mesh->vbo))); mesh->vbo = 0; } 697 | if (mesh->ibo != 0) { M_CHECKGL(glDeleteBuffers(1, &(mesh->ibo))); mesh->ibo = 0; } 698 | if (mesh->vao != 0) { M_CHECKGL(glDeleteVertexArrays(1, &(mesh->vao))); mesh->vao = 0; } 699 | mesh->material = NULL; 700 | } 701 | 702 | void m_set_material(mesh_t* mesh, material_t* material) 703 | { 704 | GLint midx; 705 | mesh->material = material; 706 | M_CHECKGL(glBindVertexArray(mesh->vao)); 707 | if (mesh->ibo > 0) 708 | M_CHECKGL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->ibo)); 709 | M_CHECKGL(glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo)); 710 | if ((midx = glGetAttribLocation(material->program, "position")) > -1) { 711 | if (mesh->position > -1) { 712 | if (mesh->flags & ML_POS_2F) 713 | M_CHECKGL(glVertexAttribPointer(midx, 2, GL_FLOAT, GL_FALSE, mesh->stride, (void*)((ptrdiff_t)mesh->position))); 714 | else if (mesh->flags & ML_POS_3F) 715 | M_CHECKGL(glVertexAttribPointer(midx, 3, GL_FLOAT, GL_FALSE, mesh->stride, (void*)((ptrdiff_t)mesh->position))); 716 | else if (mesh->flags & ML_POS_4UB) 717 | M_CHECKGL(glVertexAttribPointer(midx, 4, GL_UNSIGNED_BYTE, GL_TRUE, mesh->stride, (void*)((ptrdiff_t)mesh->position))); 718 | else // 10_10_10_2 719 | M_CHECKGL(glVertexAttribPointer(midx, 4, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, mesh->stride, (void*)((ptrdiff_t)mesh->position))); 720 | M_CHECKGL(glEnableVertexAttribArray(midx)); 721 | } else { 722 | M_CHECKGL(glDisableVertexAttribArray(midx)); 723 | } 724 | } 725 | if ((midx = glGetAttribLocation(material->program, "normal")) > -1) { 726 | if (mesh->normal > -1) { 727 | if (mesh->flags & ML_N_3F) 728 | M_CHECKGL(glVertexAttribPointer(midx, 3, GL_FLOAT, GL_FALSE, mesh->stride, (void*)((ptrdiff_t)mesh->normal))); 729 | else // 4ub 730 | M_CHECKGL(glVertexAttribPointer(midx, 4, GL_BYTE, GL_TRUE, mesh->stride, (void*)((ptrdiff_t)mesh->normal))); 731 | M_CHECKGL(glEnableVertexAttribArray(midx)); 732 | } else { 733 | M_CHECKGL(glDisableVertexAttribArray(midx)); 734 | } 735 | } 736 | if ((midx = glGetAttribLocation(material->program, "texcoord")) > -1) { 737 | if (mesh->texcoord > -1) { 738 | if (mesh->flags & ML_TC_2F) 739 | M_CHECKGL(glVertexAttribPointer(midx, 2, GL_FLOAT, GL_FALSE, mesh->stride, (void*)((ptrdiff_t)mesh->texcoord))); 740 | else // 2US 741 | M_CHECKGL(glVertexAttribPointer(midx, 2, GL_UNSIGNED_SHORT, GL_TRUE, mesh->stride, (void*)((ptrdiff_t)mesh->texcoord))); 742 | M_CHECKGL(glEnableVertexAttribArray(midx)); 743 | } else { 744 | M_CHECKGL(glDisableVertexAttribArray(midx)); 745 | } 746 | } 747 | if ((midx = glGetAttribLocation(material->program, "color")) > -1) { 748 | if (mesh->color > -1) { 749 | glVertexAttribPointer(midx, GL_BGRA, GL_UNSIGNED_BYTE, GL_TRUE, mesh->stride, (void*)((ptrdiff_t)mesh->color)); 750 | glEnableVertexAttribArray(midx); 751 | 752 | } else { 753 | glDisableVertexAttribArray(midx); 754 | } 755 | } 756 | M_CHECKGL(glBindVertexArray(0)); 757 | } 758 | 759 | void m_mtxstack_init(mtxstack_t* stack, size_t size) 760 | { 761 | if (size == 0) 762 | fatal_error("Matrix stack too small"); 763 | stack->top = 0; 764 | stack->stack = malloc(sizeof(mat44_t) * size); 765 | for (size_t i = 0; i < size; ++i) 766 | m_setidentity(stack->stack + i); 767 | } 768 | 769 | void m_mtxstack_destroy(mtxstack_t* stack) 770 | { 771 | free(stack->stack); 772 | stack->top = -1; 773 | stack->stack = NULL; 774 | } 775 | 776 | void m_pushmatrix(mtxstack_t* stack) 777 | { 778 | ++stack->top; 779 | m_copymat(stack->stack + stack->top, stack->stack + stack->top - 1); 780 | } 781 | 782 | void m_loadmatrix(mtxstack_t* stack, mat44_t* m) 783 | { 784 | m_copymat(stack->stack + stack->top, m); 785 | } 786 | 787 | void m_loadidentity(mtxstack_t* stack) 788 | { 789 | m_setidentity(stack->stack + stack->top); 790 | } 791 | 792 | void m_pushidentity(mtxstack_t* stack) 793 | { 794 | ++stack->top; 795 | m_setidentity(stack->stack + stack->top); 796 | } 797 | 798 | void m_popmatrix(mtxstack_t* stack) 799 | { 800 | if (stack->top < 0) 801 | fatal_error("Matrix stack underflow"); 802 | --stack->top; 803 | } 804 | 805 | mat44_t* m_getmatrix(mtxstack_t* stack) 806 | { 807 | if (stack->top < 0) 808 | fatal_error("Matrix stack underflow"); 809 | return stack->stack + stack->top; 810 | } 811 | 812 | void m_tex2d_load(tex2d_t* tex, const char* filename) 813 | { 814 | int x, y, n; 815 | unsigned char* data; 816 | 817 | memset(tex, 0, sizeof(tex2d_t)); 818 | data = stbi_load(filename, &x, &y, &n, 0); 819 | if (data == NULL) { 820 | fatal_error("Failed to load image %s", filename); 821 | } 822 | 823 | M_CHECKGL(glActiveTexture(GL_TEXTURE0)); 824 | M_CHECKGL(glGenTextures(1, &tex->id)); 825 | M_CHECKGL(glBindTexture(GL_TEXTURE_2D, tex->id)); 826 | M_CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); 827 | M_CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); 828 | // M_CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); 829 | // M_CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); 830 | M_CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); 831 | M_CHECKGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); 832 | tex->w = (uint16_t)x; 833 | tex->h = (uint16_t)y; 834 | 835 | switch (n) { 836 | case 4: 837 | M_CHECKGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, x, y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)); 838 | break; 839 | case 3: 840 | M_CHECKGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, x, y, 0, GL_RGB, GL_UNSIGNED_BYTE, data)); 841 | break; 842 | case 1: 843 | M_CHECKGL(glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, x, y, 0, GL_RED, GL_UNSIGNED_BYTE, data)); 844 | break; 845 | default: 846 | fatal_error("bad pixel depth %d for %s", n, filename); 847 | } 848 | 849 | //M_CHECKGL(glGenerateMipmap(GL_TEXTURE_2D)); 850 | 851 | stbi_image_free(data); 852 | M_CHECKGL(glBindTexture(GL_TEXTURE_2D, 0)); 853 | } 854 | 855 | void m_save_screenshot(const char* filename) 856 | { 857 | int ok, x, y, w, h; 858 | uint8_t* data; 859 | 860 | int viewport[4] = {0}; 861 | M_CHECKGL(glGetIntegerv(GL_VIEWPORT, viewport)); 862 | if (viewport[2] == 0 || viewport[3] == 0) { 863 | puts("Failed to get viewport information."); 864 | return; 865 | } 866 | printf("viewport: %d,%d - %d,%d\n", viewport[0], viewport[1], viewport[2], viewport[3]); 867 | 868 | data = (uint8_t*)malloc(viewport[2] * viewport[3] * 3); 869 | M_CHECKGL(glReadBuffer(GL_FRONT)); 870 | M_CHECKGL(glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGB, GL_UNSIGNED_BYTE, data)); 871 | 872 | // need to flip the image data 873 | w = viewport[2]*3; 874 | h = viewport[3]; 875 | for (y = 0; y < h/2; ++y) 876 | for (x = 0; x < w; ++x) { 877 | uint8_t p = data[y * w + x]; 878 | data[y * w + x] = data[(h - y - 1) * w + x]; 879 | data[(h - y - 1) * w + x] = p; 880 | } 881 | 882 | ok = stbi_write_png(filename, viewport[2], viewport[3], 3, data, 0); 883 | free(data); 884 | if (!ok) { 885 | printf("Failed to write to %s\n", filename); 886 | } 887 | } 888 | 889 | void m_tex2d_destroy(tex2d_t* tex) 890 | { 891 | M_CHECKGL(glDeleteTextures(1, &tex->id)); 892 | memset(tex, 0, sizeof(tex2d_t)); 893 | 894 | } 895 | 896 | void m_tex2d_bind(tex2d_t* tex, int index) 897 | { 898 | M_CHECKGL(glActiveTexture(GL_TEXTURE0 + index)); 899 | M_CHECKGL(glBindTexture(GL_TEXTURE_2D, tex->id)); 900 | } 901 | 902 | /* based on code by Pierre Terdiman 903 | http://www.codercorner.com/RayAABB.cpp 904 | */ 905 | bool collide_ray_aabb(vec3_t origin, vec3_t dir, vec3_t center, vec3_t extent) 906 | { 907 | vec3_t diff; 908 | 909 | diff.x = origin.x - center.x; 910 | if (fabsf(diff.x) > extent.x && diff.x*dir.x >= 0.0f) 911 | return false; 912 | 913 | diff.y = origin.y - center.y; 914 | if (fabsf(diff.y) > extent.y && diff.y*dir.y >= 0.0f) 915 | return false; 916 | 917 | diff.z = origin.z - center.z; 918 | if (fabsf(diff.z) > extent.z && diff.z*dir.z >= 0.0f) 919 | return false; 920 | 921 | vec3_t absdir = { fabsf(dir.x), fabsf(dir.y), fabsf(dir.z) }; 922 | float f; 923 | f = dir.y * diff.z - dir.z * diff.y; 924 | if (fabsf(f) > extent.y*absdir.z + extent.z*absdir.y) 925 | return false; 926 | f = dir.z * diff.x - dir.x * diff.z; 927 | if (fabsf(f) > extent.x*absdir.z + extent.z*absdir.x) 928 | return false; 929 | f = dir.x * diff.y - dir.y * diff.x; 930 | if (fabsf(f) > extent.x*absdir.y + extent.y*absdir.x) 931 | return false; 932 | return true; 933 | } 934 | 935 | 936 | /* http://tog.acm.org/resources/GraphicsGems/gems/BoxSphere.c */ 937 | bool collide_sphere_aabb(vec3_t pos, float radius, vec3_t center, vec3_t extent) 938 | { 939 | float dmin = 0; 940 | vec3_t bmin = { center.x - extent.x, center.y - extent.y, center.z - extent.z }; 941 | vec3_t bmax = { center.x + extent.x, center.y + extent.y, center.z + extent.z }; 942 | if (pos.x < bmin.x) dmin = pos.x - bmin.x; 943 | else if (pos.x > bmax.x) dmin = pos.x - bmax.x; 944 | if (dmin <= radius) return true; 945 | if (pos.y < bmin.y) dmin = pos.y - bmin.y; 946 | else if (pos.y > bmax.y) dmin = pos.y - bmax.y; 947 | if (dmin <= radius) return true; 948 | if (pos.z < bmin.z) dmin = pos.z - bmin.z; 949 | else if (pos.z > bmax.z) dmin = pos.z - bmax.z; 950 | if (dmin <= radius) return true; 951 | return false; 952 | } 953 | 954 | bool collide_sphere_aabb_full(vec3_t pos, float radius, vec3_t center, vec3_t extent, vec3_t* hit) 955 | { 956 | vec3_t sphereCenterRelBox; 957 | sphereCenterRelBox = m_vec3sub(pos, center); 958 | // Point on surface of box that is closest to the center of the sphere 959 | vec3_t boxPoint; 960 | 961 | // Check sphere center against box along the X axis alone. 962 | // If the sphere is off past the left edge of the box, 963 | // then the left edge is closest to the sphere. 964 | // Similar if it's past the right edge. If it's between 965 | // the left and right edges, then the sphere's own X 966 | // is closest, because that makes the X distance 0, 967 | // and you can't get much closer than that :) 968 | 969 | if (sphereCenterRelBox.x < -extent.x) boxPoint.x = -extent.x; 970 | else if (sphereCenterRelBox.x > extent.x) boxPoint.x = extent.x; 971 | else boxPoint.x = sphereCenterRelBox.x; 972 | 973 | if (sphereCenterRelBox.y < -extent.y) boxPoint.y = -extent.y; 974 | else if (sphereCenterRelBox.y > extent.y) boxPoint.y = extent.y; 975 | else boxPoint.y = sphereCenterRelBox.y; 976 | 977 | if (sphereCenterRelBox.z < -extent.z) boxPoint.z = -extent.z; 978 | else if (sphereCenterRelBox.x > extent.z) boxPoint.z = extent.z; 979 | else boxPoint.z = sphereCenterRelBox.z; 980 | 981 | // Now we have the closest point on the box, so get the distance from 982 | // that to the sphere center, and see if it's less than the radius 983 | 984 | vec3_t dist = m_vec3sub(sphereCenterRelBox, boxPoint); 985 | 986 | if (dist.x*dist.x + dist.y*dist.y + dist.z*dist.z < radius*radius) { 987 | *hit = boxPoint; 988 | return true; 989 | } 990 | return false; 991 | } 992 | 993 | bool collide_aabb_aabb_full(vec3_t center, vec3_t extent, vec3_t center2, vec3_t extent2, vec3_t *hitpoint, vec3_t *hitdelta, vec3_t *hitnormal) 994 | { 995 | float dx = center2.x - center.x; 996 | float px = (extent2.x + extent.x) - fabs(dx); 997 | if (px <= 0) 998 | return false; 999 | 1000 | float dy = center2.y - center.y; 1001 | float py = (extent2.y + extent.y) - fabs(dy); 1002 | if (py <= 0) 1003 | return false; 1004 | 1005 | float dz = center2.z - center.z; 1006 | float pz = (extent2.z + extent.z) - fabs(dz); 1007 | if (pz <= 0) 1008 | return false; 1009 | 1010 | if (px < py && px < pz) { 1011 | float sx = m_sign(dx); 1012 | hitdelta->x = px * sx; 1013 | hitnormal->x = sx; 1014 | hitdelta->y = hitdelta->z = 0; 1015 | hitnormal->y = hitnormal->z = 0; 1016 | hitpoint->x = center.x + (extent.x * sx); 1017 | hitpoint->y = center.y; 1018 | hitpoint->z = center.z; 1019 | } 1020 | if (py < px && py < pz) { 1021 | float sy = m_sign(dy); 1022 | hitdelta->y = py * sy; 1023 | hitnormal->y = sy; 1024 | hitdelta->x = hitdelta->z = 0; 1025 | hitnormal->x = hitnormal->z = 0; 1026 | hitpoint->y = center.y + (extent.y * sy); 1027 | hitpoint->x = center.x; 1028 | hitpoint->z = center.z; 1029 | } 1030 | if (pz < px && py < py) { 1031 | float sz = m_sign(dz); 1032 | hitdelta->z = pz * sz; 1033 | hitnormal->z = sz; 1034 | hitdelta->x = hitdelta->y = 0; 1035 | hitnormal->x = hitnormal->y = 0; 1036 | hitpoint->z = center.z + (extent.z * sz); 1037 | hitpoint->x = center.x; 1038 | hitpoint->y = center.y; 1039 | } 1040 | return true; 1041 | } 1042 | 1043 | 1044 | bool collide_segment_aabb(vec3_t pos, vec3_t delta, vec3_t padding, vec3_t center, vec3_t extent, 1045 | float* time, vec3_t* hit, vec3_t* hitdelta, vec3_t* normal) 1046 | { 1047 | float scaleX = 1.f / delta.x; 1048 | float scaleY = 1.f / delta.y; 1049 | float scaleZ = 1.f / delta.z; 1050 | float signX = m_sign(scaleX); 1051 | float signY = m_sign(scaleY); 1052 | float signZ = m_sign(scaleZ); 1053 | float nearTimeX = (center.x - signX * (extent.x + padding.x) - pos.x) * scaleX; 1054 | float nearTimeY = (center.y - signY * (extent.y + padding.y) - pos.y) * scaleY; 1055 | float nearTimeZ = (center.z - signZ * (extent.z + padding.z) - pos.z) * scaleZ; 1056 | float farTimeX = (center.x + signX * (extent.x + padding.x) - pos.x) * scaleX; 1057 | float farTimeY = (center.y + signY * (extent.y + padding.y) - pos.y) * scaleY; 1058 | float farTimeZ = (center.z + signZ * (extent.z + padding.z) - pos.z) * scaleZ; 1059 | if (nearTimeX > farTimeY || nearTimeY > farTimeX || 1060 | nearTimeX > farTimeZ || nearTimeZ > farTimeX) 1061 | return false; 1062 | float nearTime = ML_MAX(ML_MAX(nearTimeX, nearTimeY), nearTimeZ); 1063 | float farTime = ML_MIN(ML_MIN(farTimeX, farTimeY), farTimeZ); 1064 | if (nearTime >= 1.f || farTime <= 0.f) 1065 | return false; 1066 | 1067 | float t = m_clamp(nearTime, 0, 1.f); 1068 | vec3_t dir = delta; 1069 | m_vec3normalize(dir); 1070 | *time = t; 1071 | hitdelta->x = t * delta.x; 1072 | hitdelta->y = t * delta.y; 1073 | hitdelta->z = t * delta.z; 1074 | hit->x = pos.x + hitdelta->x; 1075 | hit->y = pos.y + hitdelta->y; 1076 | hit->z = pos.z + hitdelta->z; 1077 | if (nearTimeX > nearTimeY && nearTimeX > nearTimeZ) { 1078 | normal->x = -signX; 1079 | normal->y = normal->z = 0.f; 1080 | } else if (nearTimeY > nearTimeX && nearTimeY > nearTimeZ) { 1081 | normal->y = -signY; 1082 | normal->x = normal->z = 0.f; 1083 | } else { 1084 | normal->z = -signZ; 1085 | normal->x = normal->y = 0.f; 1086 | } 1087 | return true; 1088 | } 1089 | 1090 | bool intersect_moving_aabb_aabb(aabb_t a, aabb_t b, vec3_t va, vec3_t vb, float* tfirst, float* tlast) 1091 | { 1092 | if (collide_aabb_aabb(a.center, a.extent, b.center, b.extent)) { 1093 | *tfirst = *tlast = 0.f; 1094 | return true; 1095 | } 1096 | 1097 | vec3_t v = m_vec3sub(vb, va); 1098 | *tfirst = 0; 1099 | *tlast = 1.f; 1100 | 1101 | // for each axis, determine time of first and last contact (if any) 1102 | { 1103 | float amax_x = a.center.x + a.extent.x; 1104 | float amin_x = a.center.x - a.extent.x; 1105 | float bmax_x = b.center.x + b.extent.x; 1106 | float bmin_x = b.center.x - b.extent.x; 1107 | if (v.x < 0) { 1108 | if (bmax_x < amin_x) return false; 1109 | if (amax_x < bmin_x) *tfirst = ML_MAX((amax_x - bmin_x) / v.x, *tfirst); 1110 | if (bmax_x > amin_x) *tlast = ML_MIN((amin_x - bmax_x) / v.x, *tlast); 1111 | } 1112 | if (v.x > 0) { 1113 | if (bmin_x > amax_x) return false; 1114 | if (bmax_x < amin_x) *tfirst = ML_MAX((amin_x - bmax_x) / v.x, *tfirst); 1115 | if (amax_x > bmin_x) *tlast = ML_MIN((amax_x - bmin_x) / v.x, *tlast); 1116 | } 1117 | // No overlap possible if time of first contact occurs after time of last contact 1118 | if (*tfirst > *tlast) return false; 1119 | } 1120 | 1121 | { 1122 | float amax_y = a.center.y + a.extent.y; 1123 | float amin_y = a.center.y - a.extent.y; 1124 | float bmax_y = b.center.y + b.extent.y; 1125 | float bmin_y = b.center.y - b.extent.y; 1126 | if (v.y < 0) { 1127 | if (bmax_y < amin_y) return false; 1128 | if (amax_y < bmin_y) *tfirst = ML_MAX((amax_y - bmin_y) / v.y, *tfirst); 1129 | if (bmax_y > amin_y) *tlast = ML_MIN((amin_y - bmax_y) / v.y, *tlast); 1130 | } 1131 | if (v.y > 0) { 1132 | if (bmin_y > amax_y) return false; 1133 | if (bmax_y < amin_y) *tfirst = ML_MAX((amin_y - bmax_y) / v.y, *tfirst); 1134 | if (amax_y > bmin_y) *tlast = ML_MIN((amax_y - bmin_y) / v.y, *tlast); 1135 | } 1136 | // No overlap possible if time of first contact occurs after time of last contact 1137 | if (*tfirst > *tlast) return false; 1138 | } 1139 | 1140 | { 1141 | float amax_z = a.center.z + a.extent.z; 1142 | float amin_z = a.center.z - a.extent.z; 1143 | float bmax_z = b.center.z + b.extent.z; 1144 | float bmin_z = b.center.z - b.extent.z; 1145 | if (v.z < 0) { 1146 | if (bmax_z < amin_z) return false; 1147 | if (amax_z < bmin_z) *tfirst = ML_MAX((amax_z - bmin_z) / v.z, *tfirst); 1148 | if (bmax_z > amin_z) *tlast = ML_MIN((amin_z - bmax_z) / v.z, *tlast); 1149 | } 1150 | if (v.z > 0) { 1151 | if (bmin_z > amax_z) return false; 1152 | if (bmax_z < amin_z) *tfirst = ML_MAX((amin_z - bmax_z) / v.z, *tfirst); 1153 | if (amax_z > bmin_z) *tlast = ML_MIN((amax_z - bmin_z) / v.z, *tlast); 1154 | } 1155 | // No overlap possible if time of first contact occurs after time of last contact 1156 | if (*tfirst > *tlast) return false; 1157 | } 1158 | 1159 | return true; 1160 | } 1161 | -------------------------------------------------------------------------------- /src/math3d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "common.h" 7 | 8 | #define ML_PI 3.14159265358979323846 /* pi */ 9 | #define ML_TWO_PI (3.14159265358979323846 * 2.0) 10 | #define ML_EPSILON (1.0e-4f) 11 | #define ML_EPSILON2 (1.0e-5f) 12 | #define ML_E 2.71828182845904523536 13 | #define ML_LOG2E 1.44269504088896340736 14 | #define ML_PI_2 1.57079632679489661923 15 | #define ML_PI_4 0.785398163397448309616 16 | #define ML_MIN(a, b) (((b) < (a)) ? (b) : (a)) 17 | #define ML_MAX(a, b) (((b) > (a)) ? (b) : (a)) 18 | 19 | #define ML_DEG2RAD(d) (((d) * ML_PI) / 180.0) 20 | #define ML_RAD2DEG(r) (((r) * 180.0) / ML_PI) 21 | 22 | enum ml_CollisionResult { 23 | ML_OUTSIDE, 24 | ML_INSIDE, 25 | ML_INTERSECT 26 | }; 27 | 28 | enum ml_MeshFlags { 29 | ML_POS_2F = 0x01, 30 | ML_POS_3F = 0x02, 31 | ML_POS_4UB = 0x04, 32 | ML_POS_10_2 = 0x08, 33 | ML_N_3F = 0x10, 34 | ML_N_4B = 0x20, 35 | ML_TC_2F = 0x40, 36 | ML_TC_2US = 0x80, 37 | ML_CLR_4UB = 0x100 38 | }; 39 | 40 | 41 | typedef struct vec2 { 42 | float x, y; 43 | } vec2_t; 44 | 45 | typedef struct vec3 { 46 | float x, y, z; 47 | } vec3_t; 48 | 49 | typedef struct vec4 { 50 | float x, y, z, w; 51 | } vec4_t; 52 | 53 | typedef struct clr { 54 | uint8_t a, r, g, b; 55 | } clr_t; 56 | 57 | typedef struct ivec2 { 58 | int x, y; 59 | } ivec2_t; 60 | 61 | typedef struct ivec3 { 62 | int x, y, z; 63 | } ivec3_t; 64 | 65 | typedef struct ivec4 { 66 | int x, y, z, w; 67 | } ivec4_t; 68 | 69 | typedef struct dvec3 { 70 | double x, y, z; 71 | } dvec3_t; 72 | 73 | typedef struct chunkpos { 74 | int x, z; 75 | } chunkpos_t; 76 | 77 | typedef struct mat44 { 78 | float m[16]; 79 | } mat44_t; 80 | 81 | typedef struct mat33 { 82 | float m[9]; 83 | } mat33_t; 84 | 85 | typedef struct frustum { 86 | vec4_t planes[6]; 87 | vec4_t absplanes[6]; 88 | } frustum_t; 89 | 90 | typedef struct aabb_t { 91 | vec3_t center; 92 | vec3_t extent; 93 | } aabb_t; 94 | 95 | 96 | #pragma pack(push, 4) 97 | 98 | // order always has to be 99 | // position 100 | // normal 101 | // texcoord 102 | // color 103 | 104 | typedef struct posclrvert { 105 | vec3_t pos; 106 | uint32_t clr; 107 | } posclrvert_t; 108 | 109 | typedef struct posnormalclrvert { 110 | vec3_t pos; 111 | vec3_t n; 112 | uint32_t clr; 113 | } posnormalclrvert_t; 114 | 115 | typedef struct posnormalvert { 116 | vec3_t pos; 117 | vec3_t n; 118 | } posnormalvert_t; 119 | 120 | typedef struct uivert { 121 | vec2_t pos; 122 | vec2_t tc; 123 | uint32_t clr; 124 | } uivert_t; 125 | 126 | #pragma pack(pop) 127 | 128 | 129 | // A material identifies a 130 | // shader and any special 131 | // considerations concerning 132 | // that shader 133 | typedef struct material { 134 | GLuint program; 135 | GLint projmat; 136 | GLint modelview; 137 | GLint normalmat; 138 | GLint chunk_offset; 139 | GLint amb_light; 140 | GLint fog_color; 141 | GLint light_dir; 142 | GLint tex0; 143 | GLint sun_dir; 144 | GLint sun_color; 145 | GLint sky_dark; 146 | GLint sky_light; 147 | } material_t; 148 | 149 | 150 | // Revised concept of mesh and render object: 151 | // A mesh is a render object - you need a VAO 152 | // to render a mesh in GL 3.2+ core profile 153 | // anyway. 154 | // The mesh can be bound to different materials, 155 | // and it needs to be bound to a material before 156 | // it is rendered. 157 | typedef struct mesh { 158 | material_t* material; 159 | GLuint vao; 160 | GLuint vbo; // vertex buffer 161 | GLuint ibo; // index buffer (may be 0 if not used) 162 | GLint position; // -1 if not present, else offset 163 | GLint normal; 164 | GLint texcoord; 165 | GLint color; 166 | GLsizei stride; 167 | GLenum mode; 168 | GLenum ibotype; 169 | GLsizei count; 170 | GLenum flags; 171 | } mesh_t; 172 | 173 | 174 | typedef struct tex2d_t { 175 | GLuint id; 176 | uint16_t w; 177 | uint16_t h; 178 | } tex2d_t; 179 | 180 | typedef struct mtxstack { 181 | int top; 182 | mat44_t* stack; 183 | } mtxstack_t; 184 | 185 | 186 | // 3D math 187 | 188 | void m_perspective(mat44_t* m, float fovy, float aspect, float zNear, float zFar); 189 | void m_setidentity(mat44_t* m); 190 | void m_lookat(mat44_t* m, vec3_t eye, vec3_t at, vec3_t up); 191 | void m_copymat(mat44_t* to, const mat44_t* from); 192 | void m_fps_rotation(float pitch, float yaw, vec3_t* x, vec3_t* y, vec3_t* z); 193 | void m_fpsmatrix(mat44_t* to, vec3_t eye, float pitch, float yaw); 194 | void m_matmul(mat44_t* to, const mat44_t* by); 195 | vec4_t m_matmulvec(const mat44_t* m, const vec4_t* v); 196 | vec3_t m_matmulvec3(const mat44_t* m, const vec3_t* v); 197 | vec3_t m_rotatevec3(const mat44_t* m, const vec3_t* v); 198 | void m_translate(mat44_t* m, float x, float y, float z); 199 | void m_rotate(mat44_t* m, float angle, float x, float y, float z); 200 | void m_getmat33(mat33_t* to, const mat44_t* from); 201 | vec3_t m_matmul33(const mat33_t* m, const vec3_t* v); 202 | void m_transpose(mat44_t* m); 203 | void m_transpose33(mat33_t* m); 204 | bool m_invert(mat44_t* to, const mat44_t* from); 205 | void m_invert_orthonormal(mat44_t* to, const mat44_t* from); 206 | void m_makefrustum(frustum_t* frustum, mat44_t* projection, mat44_t* view); 207 | 208 | 209 | // GL helpers 210 | 211 | GLuint m_compile_shader(GLenum type, const char* source); 212 | GLuint m_link_program(GLuint vsh, GLuint fsh); 213 | void m_create_material(material_t* material, const char* vsource, const char* fsource); 214 | void m_destroy_material(material_t* material); 215 | void m_create_mesh(mesh_t* mesh, size_t n, void* data, GLenum flags, GLenum usage); 216 | void m_create_indexed_mesh(mesh_t* mesh, size_t n, void* data, size_t ilen, GLenum indextype, void* indices, GLenum flags); 217 | void m_destroy_mesh(mesh_t* mesh); 218 | void m_update_mesh(mesh_t *mesh, GLintptr offset, GLsizeiptr n, const void* data); 219 | void m_replace_mesh(mesh_t *mesh, GLsizeiptr n, const void* data, GLenum usage); 220 | void m_set_material(mesh_t* mesh, material_t* material); 221 | void m_tex2d_load(tex2d_t* tex, const char* filename); 222 | void m_tex2d_destroy(tex2d_t* tex); 223 | void m_tex2d_bind(tex2d_t* tex, int index); 224 | void m_save_screenshot(const char* filename); 225 | 226 | 227 | // Matrix stack 228 | 229 | void m_mtxstack_init(mtxstack_t* stack, size_t size); 230 | void m_mtxstack_destroy(mtxstack_t* stack); 231 | void m_pushmatrix(mtxstack_t* stack); 232 | void m_loadmatrix(mtxstack_t* stack, mat44_t* m); 233 | void m_pushidentity(mtxstack_t* stack); 234 | void m_loadidentity(mtxstack_t* stack); 235 | void m_popmatrix(mtxstack_t* stack); 236 | mat44_t* m_getmatrix(mtxstack_t* stack); 237 | 238 | 239 | // Collision routines 240 | 241 | int collide_frustum_aabb(frustum_t* frustum, vec3_t center, vec3_t extent); 242 | int collide_frustum_aabb_xz(frustum_t* frustum, vec3_t center, vec3_t extent); 243 | int collide_frustum_aabb_y(frustum_t* frustum, vec3_t center, vec3_t extent); 244 | bool collide_ray_aabb(vec3_t origin, vec3_t dir, vec3_t center, vec3_t extent); 245 | bool collide_sphere_aabb(vec3_t pos, float radius, vec3_t center, vec3_t extent); 246 | bool collide_sphere_aabb_full(vec3_t pos, float radius, vec3_t center, vec3_t extent, vec3_t* hit); 247 | bool collide_segment_aabb(vec3_t pos, vec3_t delta, vec3_t padding, 248 | vec3_t center, vec3_t extent, 249 | float* time, vec3_t* hit, vec3_t* hitdelta, vec3_t* normal); 250 | bool collide_aabb_aabb_full(vec3_t center, vec3_t extent, 251 | vec3_t center2, vec3_t extent2, vec3_t *hitpoint, vec3_t *hitdelta, vec3_t *hitnormal); 252 | bool intersect_moving_aabb_aabb(aabb_t a, aabb_t b, vec3_t va, vec3_t vb, float* tfirst, float* tlast); 253 | 254 | 255 | // inline functions 256 | 257 | #if M_CHECKGL_ENABLED 258 | #define M_CHECKGL(call) do { call; m_checkgl(__FILE__, __LINE__, #call); } while (0) 259 | static inline 260 | void m_checkgl(const char* file, int line, const char* call) 261 | { 262 | GLenum err; 263 | char* msg; 264 | do { 265 | err = glGetError(); 266 | switch (err) { 267 | case GL_INVALID_ENUM: msg = "GL_INVALID_ENUM"; break; 268 | case GL_INVALID_VALUE: msg = "GL_INVALID_VALUE"; break; 269 | case GL_INVALID_OPERATION: msg = "GL_INVALID_OPERATION"; break; 270 | case GL_INVALID_FRAMEBUFFER_OPERATION: msg = "GL_INVALID_FRAMEBUFFER_OPERATION"; break; 271 | case GL_OUT_OF_MEMORY: msg = "GL_OUT_OF_MEMORY"; break; 272 | default: msg = "(other)"; break; 273 | } 274 | static int checked = 0; 275 | if (err != GL_NO_ERROR && !checked) { 276 | checked = 1; 277 | fprintf(stderr, "GL error (%s:%d): (#%x) %s - %s\n", file, line, err, msg, call); 278 | } 279 | } while (err != GL_NO_ERROR); 280 | } 281 | #else 282 | #define M_CHECKGL(call) call 283 | #endif 284 | 285 | static inline 286 | bool m_fisvalid(float f) 287 | { 288 | return (f >= -FLT_MAX && f <= FLT_MAX); 289 | } 290 | 291 | static inline 292 | float m_sign(float x) 293 | { 294 | return (x > 0) ? 1.f : (x < 0) ? -1.f : 0; 295 | } 296 | 297 | static inline 298 | float m_clamp(float t, float lo, float hi) 299 | { 300 | return (t < lo) ? lo : ((t > hi) ? hi : t); 301 | } 302 | 303 | static inline 304 | double m_clampd(double t, double lo, double hi) 305 | { 306 | return (t < lo) ? lo : ((t > hi) ? hi : t); 307 | } 308 | 309 | static inline 310 | vec3_t m_clampVec3(vec3_t t, float lo, float hi) 311 | { 312 | vec3_t ret = { m_clamp(t.x, lo, hi), 313 | m_clamp(t.y, lo, hi), 314 | m_clamp(t.z, lo, hi) }; 315 | return ret; 316 | } 317 | 318 | static inline 319 | float m_wrap(float t, float lo, float hi) 320 | { 321 | while (t < lo) 322 | t = hi - (lo - t); 323 | while (t > hi) 324 | t = lo + (hi - t); 325 | return t; 326 | } 327 | 328 | static inline 329 | vec2_t m_vec2(float x, float y) 330 | { 331 | vec2_t v = { x, y }; 332 | return v; 333 | } 334 | 335 | static inline 336 | vec3_t m_vec3(float x, float y, float z) 337 | { 338 | vec3_t v = { x, y, z }; 339 | return v; 340 | } 341 | 342 | static inline 343 | vec4_t m_vec4(float x, float y, float z, float w) 344 | { 345 | vec4_t v = { x, y, z, w }; 346 | return v; 347 | } 348 | 349 | static inline 350 | dvec3_t m_dvec3(double x, double y, double z) 351 | { 352 | dvec3_t v = { x, y, z }; 353 | return v; 354 | } 355 | 356 | static inline 357 | void m_uniform_mat44(GLint index, mat44_t* mat) 358 | { 359 | if (index != -1) 360 | glUniformMatrix4fv(index, 1, GL_FALSE, mat->m); 361 | } 362 | 363 | static inline 364 | void m_uniform_mat33(GLint index, mat33_t* mat) 365 | { 366 | if (index != -1) 367 | glUniformMatrix3fv(index, 1, GL_FALSE, mat->m); 368 | } 369 | 370 | static inline 371 | void m_uniform_vec2(GLint index, vec2_t* v) 372 | { 373 | if (index != -1) 374 | glUniform2fv(index, 1, (GLfloat*)v); 375 | } 376 | 377 | static inline 378 | void m_uniform_vec3(GLint index, vec3_t* v) 379 | { 380 | if (index != -1) 381 | glUniform3fv(index, 1, (GLfloat*)v); 382 | } 383 | 384 | static inline 385 | void m_uniform_vec4(GLint index, vec4_t* v) 386 | { 387 | if (index != -1) 388 | glUniform4fv(index, 1, (GLfloat*)v); 389 | } 390 | 391 | static inline 392 | void m_uniform_i(GLint index, int i) 393 | { 394 | if (index != -1) 395 | glUniform1i(index, i); 396 | } 397 | 398 | static inline 399 | void m_use(material_t* material) 400 | { 401 | if (material != NULL) 402 | M_CHECKGL(glUseProgram(material->program)); 403 | else 404 | M_CHECKGL(glUseProgram(0)); 405 | } 406 | 407 | static inline 408 | void m_draw(const mesh_t* mesh) 409 | { 410 | M_CHECKGL(glBindVertexArray(mesh->vao)); 411 | if (mesh->ibo > 0) 412 | M_CHECKGL(glDrawElements(mesh->mode, mesh->count, mesh->ibotype, 0)); 413 | else 414 | M_CHECKGL(glDrawArrays(mesh->mode, 0, mesh->count)); 415 | glBindVertexArray(0); 416 | } 417 | 418 | 419 | static inline 420 | void m_draw_range(const mesh_t* mesh, GLint first, GLsizei count) 421 | { 422 | M_CHECKGL(glBindVertexArray(mesh->vao)); 423 | if (mesh->ibo > 0) 424 | M_CHECKGL(glDrawElements(mesh->mode, mesh->count, mesh->ibotype, 0)); 425 | else 426 | M_CHECKGL(glDrawArrays(mesh->mode, first, count)); 427 | glBindVertexArray(0); 428 | } 429 | 430 | 431 | static inline 432 | vec3_t m_xaxis44(const mat44_t* from) 433 | { 434 | return *(vec3_t*)&from->m[0]; 435 | } 436 | 437 | static inline 438 | vec3_t m_yaxis44(const mat44_t* from) 439 | { 440 | return *(vec3_t*)&from->m[4]; 441 | } 442 | 443 | static inline 444 | vec3_t m_zaxis44(const mat44_t* from) 445 | { 446 | return *(vec3_t*)&from->m[8]; 447 | } 448 | 449 | 450 | #define m_setvec2(v, a, b) { (v).x = (a); (v).y = (b); } 451 | #define m_setvec3(v, a, b, c) { (v).x = (a); (v).y = (b); (v).z = (c); } 452 | #define m_setvec4(v, a, b, c, d) { (v).x = (a); (v).y = (b); (v).z = (c); (v).w = (d); } 453 | 454 | static inline 455 | float m_vec3dot(const vec3_t a, const vec3_t b) 456 | { 457 | return a.x * b.x + a.y * b.y + a.z * b.z; 458 | } 459 | 460 | static inline 461 | vec3_t m_vec3cross(const vec3_t a, const vec3_t b) 462 | { 463 | vec3_t to; 464 | to.x = a.y*b.z - a.z*b.y; 465 | to.y = a.z*b.x - a.x*b.z; 466 | to.z = a.x*b.y - a.y*b.x; 467 | return to; 468 | } 469 | 470 | static inline 471 | vec2_t m_vec2add(const vec2_t a, const vec2_t b) 472 | { 473 | vec2_t to = { a.x + b.x, a.y + b.y }; 474 | return to; 475 | } 476 | 477 | static inline 478 | vec2_t m_vec2addf(vec2_t tc, float by) 479 | { 480 | tc.x += by; 481 | tc.y += by; 482 | return tc; 483 | } 484 | 485 | static inline 486 | vec3_t m_vec3add(const vec3_t a, const vec3_t b) 487 | { 488 | vec3_t to = { a.x + b.x, a.y + b.y, a.z + b.z }; 489 | return to; 490 | } 491 | 492 | static inline 493 | vec4_t m_vec4add(const vec4_t a, const vec4_t b) 494 | { 495 | vec4_t to = { a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w }; 496 | return to; 497 | } 498 | 499 | static inline 500 | vec3_t m_vec3sub(const vec3_t a, const vec3_t b) 501 | { 502 | vec3_t to = { a.x - b.x, a.y - b.y, a.z - b.z }; 503 | return to; 504 | } 505 | 506 | static inline 507 | vec3_t m_vec3scale(const vec3_t a, float f) 508 | { 509 | vec3_t to = { a.x * f, a.y * f, a.z * f }; 510 | return to; 511 | } 512 | 513 | static inline 514 | vec4_t m_vec4scale(const vec4_t a, float f) 515 | { 516 | vec4_t to = { a.x * f, a.y * f, a.z * f, a.w * f }; 517 | return to; 518 | } 519 | 520 | static inline 521 | float m_vec3len2(const vec3_t v) 522 | { 523 | return v.x*v.x + v.y*v.y + v.z*v.z; 524 | } 525 | 526 | static inline 527 | float m_vec3len(const vec3_t v) 528 | { 529 | return sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); 530 | } 531 | 532 | static inline 533 | vec3_t m_vec3normalize(vec3_t v) 534 | { 535 | float invlen = 1.f / m_vec3len(v); 536 | return m_vec3scale(v, invlen); 537 | } 538 | 539 | static inline 540 | vec3_t m_vec3invert(const vec3_t v) 541 | { 542 | vec3_t to = { -v.x, -v.y, -v.z }; 543 | return to; 544 | } 545 | 546 | static inline 547 | vec4_t m_vec4abs(const vec4_t v) 548 | { 549 | vec4_t to = { fabs(v.x), fabs(v.y), fabs(v.z), fabs(v.w) }; 550 | return to; 551 | } 552 | 553 | static inline 554 | float m_fastinvsqrt(float x) { 555 | union { 556 | float f; 557 | int bits; 558 | } v; 559 | float xhalf = 0.5f * x; 560 | v.f = x; 561 | v.bits = 0x5f3759df - (v.bits >> 1); // what the fuck? 562 | x = v.f; 563 | x = x*(1.5f-(xhalf*x*x)); 564 | return x; 565 | } 566 | 567 | static inline 568 | vec3_t m_vec3fastnormalize(vec3_t v) 569 | { 570 | return m_vec3scale(v, m_fastinvsqrt(v.x*v.x + v.y*v.y + v.z*v.z)); 571 | } 572 | 573 | static inline 574 | vec4_t m_normalize_plane(vec4_t plane) 575 | { 576 | vec3_t n = {plane.x, plane.y, plane.z}; 577 | float len = m_vec3len(n); 578 | n = m_vec3scale(n, 1.f / len); 579 | plane.x = n.x; 580 | plane.y = n.y; 581 | plane.z = n.z; 582 | plane.w = plane.w / len; 583 | return plane; 584 | } 585 | 586 | static inline 587 | vec3_t m_dvec3tovec3(dvec3_t v) 588 | { 589 | return m_vec3(v.x, v.y, v.z); 590 | } 591 | 592 | static inline 593 | dvec3_t m_dvec3add(dvec3_t a, dvec3_t b) 594 | { 595 | return m_dvec3(a.x + b.x, a.y + b.y, a.z + b.z); 596 | } 597 | 598 | static inline 599 | dvec3_t m_dvec3sub(dvec3_t a, dvec3_t b) 600 | { 601 | return m_dvec3(a.x - b.x, a.y - b.y, a.z - b.z); 602 | } 603 | 604 | static inline 605 | clr_t m_makergb(uint8_t r, uint8_t g, uint8_t b) 606 | { 607 | clr_t c = { 0xff, r, g, b }; 608 | return c; 609 | } 610 | 611 | static inline 612 | clr_t m_makeargb(uint8_t a, uint8_t r, uint8_t g, uint8_t b) 613 | { 614 | clr_t c = { a, r, g, b }; 615 | return c; 616 | } 617 | 618 | static inline 619 | clr_t m_makergba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) 620 | { 621 | clr_t c = { a, r, g, b }; 622 | return c; 623 | } 624 | 625 | static inline 626 | int collide_plane_aabb(vec4_t plane, vec3_t center, vec3_t extent) 627 | { 628 | vec4_t absplane = { fabs(plane.x), fabs(plane.y), fabs(plane.z), fabs(plane.w) }; 629 | float d = center.x * plane.x + center.y * plane.y + center.z * plane.z + plane.w; 630 | float r = extent.x * absplane.x + extent.y * absplane.y + extent.z * absplane.z + absplane.w; 631 | if (d + r <= 0) return ML_OUTSIDE; 632 | if (d - r < 0) return ML_INTERSECT; 633 | return ML_INSIDE; 634 | } 635 | 636 | static inline 637 | bool collide_aabb_aabb(vec3_t center1, vec3_t extent1, vec3_t center2, vec3_t extent2) 638 | { 639 | return (fabs(center1.x - center2.x) < extent1.x + extent2.x) && 640 | (fabs(center1.y - center2.y) < extent1.y + extent2.y) && 641 | (fabs(center1.z - center2.z) < extent1.z + extent2.z); 642 | } 643 | 644 | static inline 645 | bool collide_point_aabb(vec3_t point, vec3_t center, vec3_t extent) 646 | { 647 | return (fabs(center.x - point.x) < extent.x) && 648 | (fabs(center.y - point.y) < extent.y) && 649 | (fabs(center.z - point.z) < extent.z); 650 | } 651 | 652 | 653 | 654 | // constants 655 | 656 | extern const vec3_t m_up; 657 | extern const vec3_t m_right; 658 | extern const vec3_t m_forward; 659 | 660 | 661 | // TODO: GL state stack - track state as a stack of uint64_ts... 662 | -------------------------------------------------------------------------------- /src/noise.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "stb_perlin.h" 4 | #include "common.h" 5 | 6 | /* 7 | * A C port of "OpenSimplex (Simplectic) Noise in Java". 8 | * https://gist.github.com/KdotJPG/b1270127455a94ac5d19 9 | * 10 | * (v1.0.1 With new gradient set and corresponding normalization factor, 9/19/14) 11 | * 12 | * This is free and unencumbered software released into the public domain. 13 | * 14 | * Anyone is free to copy, modify, publish, use, compile, sell, or 15 | * distribute this software, either in source code form or as a compiled 16 | * binary, for any purpose, commercial or non-commercial, and by any 17 | * means. 18 | * 19 | * In jurisdictions that recognize copyright laws, the author or authors 20 | * of this software dedicate any and all copyright interest in the 21 | * software to the public domain. We make this dedication for the benefit 22 | * of the public at large and to the detriment of our heirs and 23 | * successors. We intend this dedication to be an overt act of 24 | * relinquishment in perpetuity of all present and future rights to this 25 | * software under copyright law. 26 | * 27 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 29 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 30 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 31 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 32 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | * OTHER DEALINGS IN THE SOFTWARE. 34 | */ 35 | 36 | /* init the noise function using the given seed */ 37 | void opensimplex_init(uint64_t seed); 38 | 39 | /* generate 2D, 3D, 4D noise data */ 40 | double opensimplex_noise_2d(double x, double y); 41 | double opensimplex_noise_3d(double x, double y, double z); 42 | double opensimplex_noise_4d(double x, double y, double z, double w); 43 | 44 | /* 45 | * A speed-improved simplex noise algorithm for 2D 46 | * 47 | * Based on example code by Stefan Gustavson (stegu@itn.liu.se). 48 | * Optimisations by Peter Eastman (peastman@drizzle.stanford.edu). 49 | * Better rank ordering method by Stefan Gustavson in 2012. 50 | * 51 | * This could be speeded up even further, but it's useful as it is. 52 | * 53 | * Version 2012-03-09 54 | * 55 | * This code was placed in the public domain by its original author, 56 | * Stefan Gustavson. You may use it as you see fit, but 57 | * attribution is appreciated. 58 | * 59 | */ 60 | 61 | void simplex_init(uint64_t seed); 62 | double simplex_noise_2d(double x, double y); 63 | 64 | 65 | static inline 66 | double fbm_simplex_2d(double x, double y, double gain, double frequency, double lacunarity, int octaves) 67 | { 68 | double sum = 0.0; 69 | double amplitude = 1.0; 70 | for (int i = 0; i < octaves; ++i) { 71 | sum += simplex_noise_2d(x * frequency, y * frequency) * amplitude; 72 | frequency *= lacunarity; 73 | amplitude *= gain; 74 | } 75 | return sum; 76 | } 77 | 78 | 79 | static inline 80 | double fbm_opensimplex_3d(double x, double y, double z, double gain, double frequency, double lacunarity, int octaves) 81 | { 82 | double sum = 0.0; 83 | double amplitude = 1.0; 84 | for (int i = 0; i < octaves; ++i) { 85 | sum += opensimplex_noise_3d(x * frequency, y * frequency, z * frequency) * amplitude; 86 | frequency *= lacunarity; 87 | amplitude *= gain; 88 | } 89 | return sum; 90 | } 91 | -------------------------------------------------------------------------------- /src/objfile.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "math3d.h" 3 | #include "objfile.h" 4 | 5 | #define LINEBUF_SIZE 1024 6 | 7 | static 8 | bool read_line(char* to, const char** data) 9 | { 10 | char* wp = to; 11 | const char* buf = *data; 12 | while (wp == to) { 13 | if (!*buf) 14 | return false; 15 | // skip whitespace 16 | while (*buf && isspace(*buf)) 17 | ++buf; 18 | // skip comments 19 | if (*buf == '#') { 20 | while (*buf && *buf != '\n') 21 | ++buf; 22 | continue; 23 | } 24 | // copy line data 25 | while (*buf && *buf != '\n') { 26 | *wp++ = *buf++; 27 | if (wp - to >= LINEBUF_SIZE) 28 | fatal_error("objLoad: Line too long"); 29 | } 30 | } 31 | *data = buf; 32 | *wp = '\0'; 33 | return true; 34 | } 35 | 36 | /* 37 | Based on stb.h: stb_strtok_raw() 38 | */ 39 | static 40 | char* strtok_x(char* output, char* tokens, const char* delim) 41 | { 42 | while (*tokens && strchr(delim, *tokens) != NULL) 43 | tokens++; 44 | while (*tokens && strchr(delim, *tokens) == NULL) 45 | *output++ = *tokens++; 46 | *output = '\0'; 47 | return *tokens ? tokens + 1 : tokens; 48 | } 49 | 50 | // this data 51 | 52 | static 53 | void push_vert(obj_t* m, float* v, size_t n) 54 | { 55 | if (n != 3) 56 | fatal_error("Only 3D vertices allowed in obj: n = %zu", n); 57 | while (m->nverts + n > m->vcap) { 58 | m->vcap *= 2; 59 | m->verts = realloc(m->verts, m->vcap * sizeof(float)); 60 | } 61 | memcpy(m->verts + m->nverts, v, n * sizeof(float)); 62 | m->nverts += n; 63 | } 64 | 65 | static 66 | void push_face(obj_t* m, uint32_t* f, size_t n) 67 | { 68 | if (n != 3) 69 | fatal_error("Only triangles allowed in obj: n = %zu", n); 70 | while (m->nindices + n > m->fcap) { 71 | m->fcap *= 2; 72 | m->indices = realloc(m->indices, m->fcap * sizeof(uint32_t)); 73 | } 74 | memcpy(m->indices + m->nindices, f, n * sizeof(uint32_t)); 75 | m->nindices += n; 76 | } 77 | 78 | void obj_load(obj_t* mesh, const char* data, float vscale) 79 | { 80 | char linebuf[LINEBUF_SIZE]; 81 | char tokbuf[LINEBUF_SIZE]; 82 | const char* delim = " \t\r\n"; 83 | float tmpvert[4]; 84 | uint32_t tmpindex[3]; 85 | size_t i; 86 | 87 | memset(mesh, 0, sizeof(obj_t)); 88 | mesh->vcap = 1024; 89 | mesh->fcap = 1024; 90 | mesh->verts = malloc(mesh->vcap * sizeof(float)); 91 | mesh->indices = malloc(mesh->fcap * sizeof(uint32_t)); 92 | 93 | while (read_line(linebuf, &data)) { 94 | char* tok = strtok_x(tokbuf, linebuf, delim); 95 | if (strcmp(tokbuf, "v") == 0) { 96 | // vertex 97 | i = 0; 98 | do { 99 | tok = strtok_x(tokbuf, tok, delim); 100 | tmpvert[i++] = atof(tokbuf) * vscale; 101 | if (i > 4) 102 | fatal_error("Too many dimensions (%d) in .obj vertex", i); 103 | } while (*tok != '\0'); 104 | push_vert(mesh, tmpvert, i); 105 | } else if (strcmp(tokbuf, "f") == 0) { 106 | i = 0; 107 | do { 108 | tok = strtok_x(tokbuf, tok, delim); 109 | // - 1 because .obj indices are 1-based 110 | tmpindex[i++] = (uint32_t)atol(tokbuf) - 1; 111 | if (i > 3) 112 | fatal_error("Too many vertices in face (%d)", i); 113 | } while (*tok != '\0'); 114 | push_face(mesh, tmpindex, i); 115 | } else { 116 | // TODO: vt, vn, f v/vt/vn 117 | fatal_error("Unhandled token: '%s'", tokbuf); 118 | } 119 | } 120 | } 121 | 122 | void obj_free(obj_t* mesh) 123 | { 124 | free(mesh->verts); 125 | free(mesh->indices); 126 | memset(mesh, 0, sizeof(obj_t)); 127 | } 128 | 129 | 130 | void obj_normals(obj_t* obj, void** vertexdata, size_t* vertexsize, GLenum* meshflags) 131 | { 132 | size_t i; 133 | size_t nvertices = obj->nverts / 3; 134 | posnormalvert_t* verts = malloc(sizeof(posnormalvert_t) * nvertices); 135 | for (i = 0; i < nvertices; ++i) { 136 | memcpy(&verts[i].pos.x, obj->verts + (i * 3), sizeof(float) * 3); 137 | } 138 | 139 | for (i = 0; i < obj->nindices; i += 3) { 140 | if (obj->indices[i + 0] > nvertices) 141 | fatal_error("index out of bound: %u", obj->indices[i + 0]); 142 | if (obj->indices[i + 1] > nvertices) 143 | fatal_error("index out of bound: %u", obj->indices[i + 1]); 144 | if (obj->indices[i + 2] > nvertices) 145 | fatal_error("index out of bound: %u", obj->indices[i + 2]); 146 | vec3_t t1 = verts[obj->indices[i + 0]].pos; 147 | vec3_t t2 = verts[obj->indices[i + 1]].pos; 148 | vec3_t t3 = verts[obj->indices[i + 2]].pos; 149 | vec3_t n = m_vec3normalize(m_vec3cross(m_vec3sub(t2, t1), m_vec3sub(t3, t1))); 150 | verts[obj->indices[i + 0]].n = n; 151 | verts[obj->indices[i + 1]].n = n; 152 | verts[obj->indices[i + 2]].n = n; 153 | } 154 | 155 | *vertexsize = sizeof(posnormalvert_t); 156 | *vertexdata = verts; 157 | *meshflags = ML_POS_3F | ML_N_3F; 158 | } 159 | 160 | void obj_createmesh(mesh_t* mesh, obj_t* obj, obj_meshgenfn fn) 161 | { 162 | 163 | void* vtxdata; 164 | GLenum meshflags; 165 | size_t vertexsize; 166 | (*fn)(obj, &vtxdata, &vertexsize, &meshflags); 167 | 168 | m_create_indexed_mesh(mesh, 169 | obj->nverts / 3, 170 | vtxdata, 171 | obj->nindices, 172 | GL_UNSIGNED_INT, 173 | obj->indices, 174 | meshflags); 175 | free(vtxdata); 176 | } 177 | -------------------------------------------------------------------------------- /src/objfile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct obj_t { 4 | float* verts; 5 | uint32_t* indices; 6 | size_t nverts; 7 | size_t vcap; 8 | size_t nindices; 9 | size_t fcap; 10 | } obj_t; 11 | 12 | typedef void (*obj_meshgenfn)(obj_t* obj, void** vertexdata, size_t* vertexsize, GLenum* meshflags); 13 | 14 | 15 | void obj_load(obj_t* mesh, const char* data, float vscale); 16 | void obj_free(obj_t* mesh); 17 | void obj_normals(obj_t* obj, void** vertexdata, size_t* vertexsize, GLenum* meshflags); 18 | void obj_createmesh(mesh_t* mesh, obj_t* obj, obj_meshgenfn fn); 19 | 20 | -------------------------------------------------------------------------------- /src/player.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "math3d.h" 3 | #include "game.h" 4 | #include "player.h" 5 | #include "script.h" 6 | 7 | 8 | static struct playervars pv; 9 | 10 | struct playervars* player_vars() { 11 | return &pv; 12 | } 13 | 14 | static 15 | void player_look(float dt) 16 | { 17 | struct inputstate *in = &game.input; 18 | struct camera *cam = &game.camera; 19 | const float xsens = 1.f / ML_TWO_PI; 20 | const float ysens = 1.f / ML_TWO_PI; 21 | float dyaw = in->mouse_xrel * dt * xsens; 22 | float dpitch = in->mouse_yrel * dt * ysens; 23 | cam->yaw = m_wrap(cam->yaw - dyaw, 0.f, ML_TWO_PI); 24 | cam->pitch = m_clamp(cam->pitch - dpitch, -ML_PI_2, ML_PI_2); 25 | in->mouse_xrel = 0; 26 | in->mouse_yrel = 0; 27 | } 28 | 29 | 30 | static 31 | void player_dumb_collide() 32 | { 33 | struct player *p = &game.player; 34 | ivec3_t footblock = { round(p->pos.x), round(p->pos.y), round(p->pos.z) }; 35 | 36 | int groundblock = footblock.y; 37 | while (is_collider(footblock.x, groundblock, footblock.z)) 38 | ++groundblock; 39 | while (!is_collider(footblock.x, groundblock, footblock.z) && groundblock >= 0) 40 | --groundblock; 41 | if (groundblock < 0) 42 | return; 43 | 44 | float groundlevel = (float)groundblock + 0.5f; 45 | if (p->pos.y - FEETDISTANCE < groundlevel) { 46 | p->pos.y = groundlevel + FEETDISTANCE; 47 | p->walking = true; 48 | } 49 | } 50 | 51 | static 52 | void player_fps_collide(float dt) 53 | { 54 | struct player *p = &game.player; 55 | ivec3_t footblock = { round(p->pos.x), round(p->pos.y), round(p->pos.z) }; 56 | 57 | int groundblock = footblock.y; 58 | while (is_collider(footblock.x, groundblock, footblock.z)) 59 | ++groundblock; 60 | while (!is_collider(footblock.x, groundblock, footblock.z) && groundblock >= 0) 61 | --groundblock; 62 | if (groundblock < 0) 63 | return; 64 | 65 | float groundlevel = (float)groundblock + 0.5f; 66 | if (p->pos.y - FEETDISTANCE < groundlevel) { 67 | p->pos.y = groundlevel + FEETDISTANCE; 68 | p->walking = true; 69 | } else { 70 | p->walking = false; 71 | } 72 | } 73 | 74 | void player_init() 75 | { 76 | struct player *p = &game.player; 77 | dvec3_t offs = { 0, OCEAN_LEVEL + 2, 0 }; 78 | p->pos = offs; 79 | m_setvec3(p->vel, 0, 0, 0); 80 | p->crouch_fade = 0; 81 | p->walking = false; 82 | p->sprinting = false; 83 | p->crouching = false; 84 | } 85 | 86 | void player_move_to_spawn() 87 | { 88 | dvec3_t p = game.player.pos; 89 | while (blocktype(p.x, p.y-2, p.z) != BLOCK_AIR || 90 | blocktype(p.x, p.y-1, p.z) != BLOCK_AIR || 91 | blocktype(p.x, p.y, p.z) != BLOCK_AIR || 92 | blocktype(p.x + 1, p.y, p.z) != BLOCK_AIR || 93 | blocktype(p.x - 1, p.y, p.z) != BLOCK_AIR || 94 | blocktype(p.x, p.y, p.z + 1) != BLOCK_AIR || 95 | blocktype(p.x, p.y, p.z - 1) != BLOCK_AIR) 96 | p.y += 1.0; 97 | game.player.pos = p; 98 | } 99 | 100 | 101 | void flight_move(struct player *p, float dt) 102 | { 103 | struct inputstate *in = &game.input; 104 | player_look(dt); 105 | 106 | float speed = pv.flyspeed; 107 | vec3_t movedir = {0, 0, 0}; 108 | if (in->move_left) movedir.x -= 1.f; 109 | if (in->move_right) movedir.x += 1.f; 110 | if (in->move_forward) movedir.z -= 1.f; 111 | if (in->move_backward) movedir.z += 1.f; 112 | if (in->move_jump) movedir.y += 1.f; 113 | if (in->move_crouch) movedir.y -= 1.f; 114 | //if (in->move_sprint) speed += pv.sprintspeed; 115 | 116 | if (movedir.x != 0 || movedir.y != 0 || movedir.z != 0) { 117 | // normalize move dir? but strafing feels good! 118 | mat44_t m; 119 | m_setidentity(&m); 120 | m_rotate(&m, game.camera.yaw, 0, 1.f, 0); 121 | vec3_t movevec = m_vec3scale(m_matmulvec3(&m, &movedir), speed*dt); 122 | 123 | p->vel = m_vec3add(p->vel, movevec); 124 | } 125 | 126 | player_dumb_collide(); 127 | 128 | // drag 129 | p->vel = m_vec3scale(p->vel, 1.f - pv.flyfriction); 130 | 131 | p->pos.x += p->vel.x * dt; 132 | p->pos.y += p->vel.y * dt; 133 | p->pos.z += p->vel.z * dt; 134 | } 135 | 136 | void fps_move(struct player *p, float dt) 137 | { 138 | struct inputstate *in = &game.input; 139 | player_look(dt); 140 | 141 | float speed = pv.accel; 142 | vec3_t movedir = {0, 0, 0}; 143 | if (in->move_left) movedir.x -= 1.f; 144 | if (in->move_right) movedir.x += 1.f; 145 | if (in->move_forward) movedir.z -= 1.f; 146 | if (in->move_backward) movedir.z += 1.f; 147 | 148 | if (p->walking && in->move_jump) movedir.y += pv.jumpspeed; 149 | 150 | if (movedir.x != 0 || movedir.y != 0 || movedir.z != 0) { 151 | // normalize move dir? but strafing feels good! 152 | mat44_t m; 153 | m_setidentity(&m); 154 | m_rotate(&m, game.camera.yaw, 0, 1.f, 0); 155 | vec3_t movevec = m_vec3scale(m_matmulvec3(&m, &movedir), speed*dt); 156 | 157 | p->vel = m_vec3add(p->vel, movevec); 158 | } 159 | 160 | p->vel.y += pv.gravity; 161 | 162 | player_fps_collide(dt); 163 | 164 | p->pos.x += p->vel.x * dt; 165 | p->pos.y += p->vel.y * dt; 166 | p->pos.z += p->vel.z * dt; 167 | 168 | // drag 169 | p->vel = m_vec3scale(p->vel, 1.f - pv.friction); 170 | 171 | } 172 | 173 | void player_tick(float dt) 174 | { 175 | // update script vars 176 | pv.accel = script_get("player.accel"); 177 | pv.friction = script_get("player.friction"); 178 | pv.gravity = script_get("player.gravity"); 179 | pv.flyspeed = script_get("player.flyspeed"); 180 | pv.flyfriction = script_get("player.flyfriction"); 181 | pv.height = script_get("player.height"); 182 | pv.crouchheight = script_get("player.crouchheight"); 183 | pv.camoffset = script_get("player.camoffset"); 184 | pv.crouchcamoffset = script_get("player.crouchcamoffset"); 185 | pv.jumpspeed = script_get("player.jumpspeed"); 186 | 187 | struct player *p = &game.player; 188 | // update animations 189 | p->crouch_fade = m_clamp(p->crouch_fade + (p->crouching?dt:-dt)*5.f, 0.f, 1.f); 190 | ++game.player.bobcount; 191 | if (game.player.bobcount > 200) 192 | game.player.bobcount = 0; 193 | 194 | 195 | if (game.camera.mode == CAMERA_FLIGHT) { 196 | flight_move(p, dt); 197 | return; 198 | } else if (game.camera.mode == CAMERA_FPS) { 199 | fps_move(p, dt); 200 | } 201 | 202 | p->prev_chunk = p->chunk; 203 | p->prev_block = p->block; 204 | p->prev_pos = p->pos; 205 | p->prev_vel = p->vel; 206 | } 207 | -------------------------------------------------------------------------------- /src/player.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "math3d.h" 3 | 4 | // Updated from lua once per frame 5 | struct playervars { 6 | float accel; 7 | float friction; 8 | float gravity; 9 | float flyspeed; 10 | float flyfriction; 11 | float crouchheight; 12 | float height; 13 | float camoffset; 14 | float crouchcamoffset; 15 | float jumpspeed; 16 | }; 17 | 18 | 19 | // collision with block 20 | struct contact { 21 | ivec3_t block; 22 | vec3_t point; 23 | vec3_t normal; 24 | float time; 25 | }; 26 | 27 | struct player { 28 | chunkpos_t chunk; 29 | ivec3_t block; 30 | dvec3_t pos; 31 | vec3_t vel; 32 | 33 | chunkpos_t prev_chunk; 34 | ivec3_t prev_block; 35 | dvec3_t prev_pos; 36 | vec3_t prev_vel; 37 | 38 | bool walking; 39 | bool sprinting; 40 | bool crouching; 41 | 42 | int bobcount; 43 | float crouch_fade; 44 | 45 | float health; 46 | float air; 47 | uint32_t xp; 48 | uint32_t level; 49 | 50 | struct contact contacts[3*3*4]; 51 | int ncontacts; 52 | }; 53 | 54 | struct inputstate { 55 | ivec3_t picked_block; 56 | ivec3_t prepicked_block; 57 | bool enable_ground; 58 | bool move_sprint; 59 | bool move_left; 60 | bool move_right; 61 | bool move_forward; 62 | bool move_backward; 63 | bool move_jump; 64 | bool move_crouch; 65 | int mouse_xrel; 66 | int mouse_yrel; 67 | }; 68 | 69 | 70 | void player_init(void); 71 | void player_tick(float dt); 72 | void player_move_to_spawn(void); 73 | struct playervars* player_vars(); 74 | 75 | // TODO: Make tweakable 76 | #define FEETDISTANCE 0.5f 77 | #define CROUCHCENTEROFFSET 0.1f 78 | #define CENTEROFFSET 0.4f // pos + centeroffset = center of player 79 | -------------------------------------------------------------------------------- /src/rnd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | /* RNGs, hash functions etc. */ 5 | 6 | /* LCG random generator, based on stb.h */ 7 | 8 | static inline unsigned long lcg_rand(unsigned long* seed) { 9 | *seed = *seed * 2147001325 + 715136305; // BCPL generator 10 | // shuffle non-random bits to the middle, and xor to decorrelate with seed 11 | return 0x31415926 ^ ((*seed >> 16) + (*seed << 16)); 12 | } 13 | 14 | static inline double lcg_frand(unsigned long* seed) { 15 | return lcg_rand(seed) / ((double) (1 << 16) * (1 << 16)); 16 | } 17 | 18 | static inline unsigned long djb2_hash(unsigned char *str) { 19 | unsigned long hash = 5381; 20 | int c; 21 | while ((c = *str++) != 0) 22 | hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ 23 | return hash; 24 | } 25 | 26 | /* RNG stream, from the OpenSimplex noise code */ 27 | static inline uint64_t rand64(uint64_t seed) { 28 | return seed * UINT64_C(6364136223846793005) + UINT64_C(1442695040888963407); 29 | } 30 | 31 | // spatially localized hash function for chunks 32 | static inline uint64_t chunk_hash(int x, int y, int z) { 33 | uint64_t hash = 0; 34 | for (size_t i = 0; i < 21; ++i) { 35 | hash += (z & 1) + ((x & 1) << 1) + ((y & 1) << 2); 36 | x >>= 1; 37 | y >>= 1; 38 | z >>= 1; 39 | } 40 | return hash; 41 | } 42 | -------------------------------------------------------------------------------- /src/roam_linux.c: -------------------------------------------------------------------------------- 1 | // main source file for Linux 2 | 3 | #include 4 | 5 | #define ML_SWAP(a, b) do { __typeof__ (a) _swap_##__LINE__ = (a); (a) = (b); (b) = _swap_##__LINE__; } while (0) 6 | 7 | #include "stb.c" 8 | #include "blocks.c" 9 | #include "gen.c" 10 | #include "geometry.c" 11 | #include "map.c" 12 | #include "math3d.c" 13 | #include "noise.c" 14 | #include "objfile.c" 15 | #include "player.c" 16 | #include "script.c" 17 | #include "sky.c" 18 | #include "stb.c" 19 | #include "sys.c" 20 | #include "u8.c" 21 | #include "ui.c" 22 | #include "main.c" 23 | 24 | #include 25 | 26 | uint64_t sys_urandom() 27 | { 28 | FILE* f = fopen("/dev/urandom", "rb"); 29 | if (f == NULL) f = fopen("/dev/random", "rb"); 30 | if (f == NULL) return time(NULL); 31 | uint64_t seed; 32 | fread(&seed, sizeof(uint64_t), 1, f); 33 | fclose(f); 34 | return seed; 35 | } 36 | 37 | int64_t sys_timems() 38 | { 39 | struct timeval tv; 40 | if (gettimeofday(&tv, NULL) == 0) 41 | return (int64_t)tv.tv_sec * 1000 + (int64_t)tv.tv_usec / 1000; 42 | fatal_error("failed to get current time"); 43 | } 44 | 45 | 46 | 47 | int main(int argc, char* argv[]) { 48 | return roam_main(argc, argv); 49 | } 50 | -------------------------------------------------------------------------------- /src/roam_windows.c: -------------------------------------------------------------------------------- 1 | // main source file for Windows 2 | 3 | #define __restrict__ 4 | #define __typeof__ decltype 5 | #define noreturn 6 | #define inline __forceinline 7 | #define snprintf _snprintf 8 | 9 | #define ML_SWAP(a, b) do { a=(a+b) - (b=a); } while (0) 10 | 11 | #include "stb.c" 12 | #include "blocks.c" 13 | #include "gen.c" 14 | #include "geometry.c" 15 | #include "map.c" 16 | #include "math3d.c" 17 | #include "noise.c" 18 | #include "objfile.c" 19 | #include "player.c" 20 | #include "script.c" 21 | #include "sky.c" 22 | #include "stb.c" 23 | #include "sys.c" 24 | #include "u8.c" 25 | #include "ui.c" 26 | #include "main.c" 27 | 28 | /** 29 | * This file has no copyright assigned and is placed in the Public Domain. 30 | * This file is part of the w64 mingw-runtime package. 31 | * No warranty is given; refer to the file DISCLAIMER.PD within this package. 32 | */ 33 | #include 34 | 35 | struct timezone 36 | { 37 | __int32 tz_minuteswest; /* minutes W of Greenwich */ 38 | bool tz_dsttime; /* type of dst correction */ 39 | }; 40 | 41 | struct timespec { 42 | __int32 tv_sec; /* seconds */ 43 | __int32 tv_nsec; /* nanoseconds */ 44 | }; 45 | 46 | #define FILETIME_1970 116444736000000000ull /* seconds between 1/1/1601 and 1/1/1970 */ 47 | #define HECTONANOSEC_PER_SEC 10000000ull 48 | 49 | int getntptimeofday (struct timespec *, struct timezone *); 50 | 51 | int getntptimeofday (struct timespec *tp, struct timezone *z) 52 | { 53 | int res = 0; 54 | union { 55 | unsigned long long ns100; /*time since 1 Jan 1601 in 100ns units */ 56 | FILETIME ft; 57 | } _now; 58 | TIME_ZONE_INFORMATION TimeZoneInformation; 59 | DWORD tzi; 60 | 61 | if (z != NULL) 62 | { 63 | if ((tzi = GetTimeZoneInformation(&TimeZoneInformation)) != TIME_ZONE_ID_INVALID) { 64 | z->tz_minuteswest = TimeZoneInformation.Bias; 65 | if (tzi == TIME_ZONE_ID_DAYLIGHT) 66 | z->tz_dsttime = 1; 67 | else 68 | z->tz_dsttime = 0; 69 | } 70 | else 71 | { 72 | z->tz_minuteswest = 0; 73 | z->tz_dsttime = 0; 74 | } 75 | } 76 | 77 | if (tp != NULL) { 78 | GetSystemTimeAsFileTime (&_now.ft); /* 100-nanoseconds since 1-1-1601 */ 79 | /* The actual accuracy on XP seems to be 125,000 nanoseconds = 125 microseconds = 0.125 milliseconds */ 80 | _now.ns100 -= FILETIME_1970; /* 100 nano-seconds since 1-1-1970 */ 81 | tp->tv_sec = _now.ns100 / HECTONANOSEC_PER_SEC; /* seconds since 1-1-1970 */ 82 | tp->tv_nsec = (long) (_now.ns100 % HECTONANOSEC_PER_SEC) * 100; /* nanoseconds */ 83 | } 84 | return res; 85 | } 86 | 87 | int gettimeofday (struct timeval *p, void *z) 88 | { 89 | struct timespec tp; 90 | 91 | if (getntptimeofday (&tp, (struct timezone *) z)) 92 | return -1; 93 | p->tv_sec=tp.tv_sec; 94 | p->tv_usec=(tp.tv_nsec/1000); 95 | return 0; 96 | } 97 | 98 | int64_t sys_timems() 99 | { 100 | struct timeval tv; 101 | if (gettimeofday(&tv, NULL) == 0) 102 | return (int64_t)tv.tv_sec * 1000 + (int64_t)tv.tv_usec / 1000; 103 | fatal_error("failed to get current time"); 104 | } 105 | 106 | 107 | uint64_t sys_urandom() 108 | { 109 | // TODO 110 | return 0; 111 | } 112 | 113 | 114 | int main(int argc, char* argv[]) { 115 | return roam_main(argc, argv); 116 | } 117 | -------------------------------------------------------------------------------- /src/script.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "math3d.h" 3 | #include "ui.h" 4 | #include "game.h" 5 | #include "script.h" 6 | #include "stb.h" 7 | 8 | 9 | #define MAX_DEFUNS 32 10 | 11 | struct defun_data { 12 | const char* name; 13 | void (*cb)(int, char**); 14 | }; 15 | 16 | static struct defun_data defuns[MAX_DEFUNS]; 17 | 18 | static stb_sdict* vars; 19 | 20 | 21 | static int is_boolean_true(const char* str) 22 | { 23 | return 24 | stb_stricmp(str, "true") == 0 || 25 | stb_stricmp(str, "1") == 0 || 26 | stb_stricmp(str, "yes") == 0; 27 | } 28 | 29 | 30 | static void script_quit(int argc, char** argv) 31 | { 32 | game.game_active = false; 33 | } 34 | 35 | static void script_debug_mode(int argc, char** argv) 36 | { 37 | game.debug_mode = (argc > 0) ? is_boolean_true(argv[1]) : true; 38 | } 39 | 40 | static void script_wireframe(int argc, char** argv) 41 | { 42 | game.wireframe = (argc > 0) ? is_boolean_true(argv[1]) : true; 43 | } 44 | 45 | static void script_teleport(int argc, char** argv) 46 | { 47 | ui_console_printf("teleport: %d", argc); 48 | if (argc == 2) { 49 | game.player.pos.x = atof(argv[1]); 50 | game.player.pos.z = atof(argv[2]); 51 | } else if (argc == 3) { 52 | game.player.pos.x = atof(argv[1]); 53 | game.player.pos.y = atof(argv[2]); 54 | game.player.pos.z = atof(argv[3]); 55 | } 56 | } 57 | 58 | void script_init() 59 | { 60 | vars = stb_sdict_new(0); 61 | memset(defuns, 0, sizeof(defuns)); 62 | script_defun("quit", script_quit); 63 | script_defun("debug", script_debug_mode); 64 | script_defun("wireframe", script_wireframe); 65 | script_defun("teleport", script_teleport); 66 | } 67 | 68 | 69 | void script_exit() 70 | { 71 | } 72 | 73 | 74 | void script_tick() 75 | { 76 | } 77 | 78 | 79 | static void script_call(int argc, char** argv) 80 | { 81 | printf("call: %s\n", argv[0]); 82 | for (struct defun_data* d = defuns; d->name; ++d) { 83 | if (strcmp(d->name, argv[0]) == 0) { 84 | (*d->cb)(argc, argv); 85 | return; 86 | } 87 | } 88 | ui_console_printf("undefined: %s", argv[0]); 89 | } 90 | 91 | 92 | int script_exec(char *cmd) 93 | { 94 | int count; 95 | char **tokens; 96 | tokens = stb_tokens_quoted(cmd, " \t", &count); 97 | if (count == 3 && strcmp(tokens[1], "=") == 0) { 98 | ui_console_printf("%s = %s", tokens[0], tokens[2]); 99 | void* prev = stb_sdict_change(vars, tokens[0], strdup(tokens[2])); 100 | if (prev != NULL) 101 | free(prev); 102 | } else if (count > 0) { 103 | script_call(count-1, tokens); 104 | } 105 | free(tokens); 106 | return 0; 107 | } 108 | 109 | int script_dofile(const char* filename) 110 | { 111 | if (!sys_isfile(filename)) { 112 | ui_console_printf("script not found: %s", filename); 113 | return 0; 114 | } 115 | int len; 116 | char** lines = stb_stringfile_trimmed((char*)filename, &len, '#'); 117 | for (int i = 0; i < len; ++i) { 118 | script_exec(lines[i]); 119 | } 120 | free(lines); 121 | return 0; 122 | } 123 | 124 | 125 | void script_defun(const char *name, void (*cb)(int argc, char** argv)) 126 | { 127 | for (unsigned int i = 0; i < stb_arrcount(defuns); ++i) { 128 | if (defuns[i].name == 0) { 129 | defuns[i].name = name; 130 | defuns[i].cb = cb; 131 | ui_console_printf("def %s", name); 132 | return; 133 | } 134 | } 135 | ui_console_printf("max defuns reached when defining '%s'", name); 136 | } 137 | 138 | double script_get(const char *name) 139 | { 140 | char* val = (char*)stb_sdict_get(vars, (char*)name); 141 | if (val) 142 | return atof(val); 143 | return 0.0; 144 | } 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /src/script.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef void* script_state; 4 | 5 | void script_init(void); 6 | void script_exit(void); 7 | void script_tick(void); 8 | 9 | // execute script code 10 | int script_exec(char *cmd); 11 | 12 | // execute script file 13 | int script_dofile(const char* filename); 14 | 15 | void script_defun(const char *name, void (*cb)(int, char**)); 16 | 17 | double script_get(const char *name); 18 | -------------------------------------------------------------------------------- /src/shaders.h: -------------------------------------------------------------------------------- 1 | /* 2 | * To be included only in main.c 3 | */ 4 | 5 | static const char* basic_vshader = "#version 330\n" 6 | "uniform mat4 projmat;\n" 7 | "uniform mat4 modelview;\n" 8 | "uniform mat3 normalmat;\n" 9 | "layout (location = 0) in vec3 position;\n" 10 | "layout (location = 1) in vec3 normal;\n" 11 | "layout (location = 2) in vec4 color;\n" 12 | "out vec3 out_normal;\n" 13 | "out vec4 out_color;\n" 14 | "out float out_depth;\n" 15 | "void main() {\n" 16 | " out_color = color;\n" 17 | " out_depth = length((modelview * vec4(position, 1)).xyz);\n" 18 | " out_normal = normalmat * normal;\n" 19 | " gl_Position = projmat * modelview * vec4(position, 1);\n" 20 | "}\n"; 21 | 22 | static const char* basic_fshader = "#version 330\n" 23 | "precision highp float;\n" 24 | "uniform vec3 amb_light;\n" 25 | "uniform vec4 fog_color;\n" 26 | "uniform vec3 light_dir;\n" 27 | "in vec3 out_normal;\n" 28 | "in vec4 out_color;\n" 29 | "in float out_depth;\n" 30 | "out vec4 fragment;\n" 31 | "vec3 fog(vec3 color, vec3 fcolor, float depth, float density){\n" 32 | " const float e = 2.71828182845904523536028747135266249;\n" 33 | " float f = pow(e, -pow(depth*density, 2));\n" 34 | " return mix(fcolor, color, f);\n" 35 | "}\n" 36 | "void main() {\n" 37 | " float intensity = max(0.0, dot(normalize(out_normal), normalize(light_dir)));\n" 38 | " vec3 basecolor = out_color.xyz * intensity + amb_light.xyz * (1.0 - intensity);\n" 39 | " vec3 fogged = fog(basecolor, fog_color.xyz, out_depth, fog_color.w);\n" 40 | " fragment = vec4(fogged, 1);\n" 41 | "}\n"; 42 | 43 | static const char* ui_vshader = "#version 330\n" 44 | "uniform vec2 screensize;\n" 45 | "layout (location = 0) in vec2 position;\n" 46 | "layout (location = 1) in vec2 texcoord;\n" 47 | "layout (location = 2) in vec4 color;\n" 48 | "out vec2 out_texcoord;\n" 49 | "out vec4 out_color;\n" 50 | "void main() {\n" 51 | " vec2 offset = screensize/2;\n" 52 | " vec2 eyepos = (position - offset) / offset;\n" 53 | " out_texcoord = texcoord;\n" 54 | " out_color = color;\n" 55 | " gl_Position = vec4(eyepos, 0, 1);\n" 56 | "}\n"; 57 | 58 | static const char* ui_fshader = "#version 330\n" 59 | "precision highp float;\n" 60 | "in vec2 out_texcoord;\n" 61 | "in vec4 out_color;\n" 62 | "out vec4 fragment;\n" 63 | "uniform sampler2D tex0;\n" 64 | "void main() {\n" 65 | " fragment = texture(tex0, out_texcoord) * out_color;\n" 66 | "}\n"; 67 | 68 | 69 | static const char* debug_vshader = "#version 330\n" 70 | "uniform mat4 projmat;\n" 71 | "uniform mat4 modelview;\n" 72 | "layout (location = 0) in vec3 position;\n" 73 | "layout (location = 1) in vec4 color;\n" 74 | "out vec4 out_color;\n" 75 | "void main() {\n" 76 | " out_color = color;\n" 77 | " vec4 sspos = projmat * modelview * vec4(position, 1);\n" 78 | " sspos.z -= 0.005;\n" 79 | " gl_Position = sspos;\n" 80 | "}\n"; 81 | 82 | static const char* debug_fshader = "#version 330\n" 83 | "precision highp float;\n" 84 | "in vec4 out_color;\n" 85 | "out vec4 fragment;\n" 86 | "void main() {\n" 87 | " fragment = out_color;\n" 88 | "}\n"; 89 | 90 | static const char* chunk_vshader = "#version 330\n" 91 | "uniform mat4 projmat;\n" 92 | "uniform mat4 modelview;\n" 93 | "uniform vec3 chunk_offset;\n" 94 | "layout (location = 0) in vec4 position;\n" 95 | "layout (location = 1) in vec2 texcoord;\n" 96 | "layout (location = 2) in vec4 color;\n" 97 | "out vec2 out_texcoord;\n" 98 | "out vec4 out_color;\n" 99 | "out vec3 out_color2;\n" 100 | "out float out_depth;\n" 101 | "void main() {\n" 102 | " vec3 pos = chunk_offset.xyz + position.xyz;\n" 103 | " vec4 tpos = modelview * vec4(pos.xyz, 1);\n" 104 | " out_color = color;\n" 105 | " out_color2 = max(vec3(10, 10, 10) - (vec3(1, 1, 1) * length(tpos.xyz)), vec3(0, 0, 0)) * vec3(0.1, 0.1, 0.1);\n" 106 | " out_depth = length(tpos.xyz);\n" 107 | " out_texcoord = texcoord;\n" 108 | " gl_Position = projmat * tpos;\n" 109 | "}\n"; 110 | 111 | // amb_light = color and intensity of skylight 112 | // out_color.xyz = torchlight level (rgb) 113 | // out_color.w = sunlight level 114 | static const char* chunk_fshader = "#version 330\n" 115 | "precision highp float;\n" 116 | "uniform vec3 amb_light;\n" 117 | "uniform vec4 fog_color;\n" 118 | "uniform sampler2D tex0;\n" 119 | "in vec2 out_texcoord;\n" 120 | "in vec4 out_color;\n" 121 | "in vec3 out_color2;\n" 122 | "in float out_depth;\n" 123 | "out vec4 fragment;\n" 124 | "vec3 fog(vec3 color, vec3 fcolor, float depth, float density){\n" 125 | " const float e = 2.71828182845904523536028747135266249;\n" 126 | " float f = pow(e, -pow(depth*density, 2));\n" 127 | " return mix(fcolor, color, f);\n" 128 | "}\n" 129 | "void main() {\n" 130 | " vec4 tex = texture(tex0, out_texcoord);\n" 131 | " if (tex.w == 0) discard;\n" 132 | " vec3 light = clamp(out_color.xyz + (amb_light.xyz * out_color.w) + (vec3(0.5, 0.5, 0.5) * out_color2.xyz), 0, 1);\n" 133 | // " vec3 light = clamp(((amb_light.xyz * out_color.w)), 0, 1);\n" 134 | // " vec3 base = light.xyz;\n" 135 | " vec3 base = clamp(pow(tex.xyz, vec3(2.2)) * light.xyz, 0, 1);\n" 136 | // " fragment = vec4(base.xyz, 1);\n" 137 | " vec3 fogged = fog(base, fog_color.xyz, out_depth, fog_color.w);\n" 138 | " fragment = vec4(pow(fogged, vec3(1/2.2)), 1);\n" 139 | "}\n"; 140 | 141 | // TODO: color and alpha based on biome and depth 142 | static const char* chunkalpha_fshader = "#version 330\n" 143 | "precision highp float;\n" 144 | "uniform vec3 amb_light;\n" 145 | "uniform vec4 fog_color;\n" 146 | "uniform sampler2D tex0;\n" 147 | "in vec2 out_texcoord;\n" 148 | "in vec4 out_color;\n" 149 | "in float out_depth;\n" 150 | "out vec4 fragment;\n" 151 | "vec3 fog(vec3 color, vec3 fcolor, float depth, float density){\n" 152 | " const float e = 2.71828182845904523536028747135266249;\n" 153 | " float f = pow(e, -pow(depth*density, 2));\n" 154 | " return mix(fcolor, color, f);\n" 155 | "}\n" 156 | "void main() {\n" 157 | " vec4 tex = texture(tex0, out_texcoord);\n" 158 | " if (tex.w == 0) discard;\n" 159 | " vec3 light = clamp(out_color.xyz + ((amb_light.xyz * out_color.w)), 0, 1);\n" 160 | " vec3 base = pow(tex.xyz, vec3(2.2)) * light.xyz;\n" 161 | // " fragment = vec4(base.xyz, tex.w);\n" 162 | " vec3 fogged = fog(base, fog_color.xyz, out_depth, fog_color.w);\n" 163 | " fragment = vec4(pow(fogged, vec3(1/2.2)), tex.w);\n" 164 | "}\n"; 165 | 166 | 167 | static const char* sky_vshader = "#version 330\n" 168 | "uniform mat4 projmat;\n" 169 | "uniform mat4 modelview;\n" 170 | "layout (location = 0) in vec3 position;\n" 171 | "out float gradient;\n" 172 | "out vec3 out_normal;\n" 173 | "void main(){\n" 174 | " gradient = position.y;\n" 175 | " out_normal = normalize(-position);\n" 176 | " gl_Position = projmat * modelview * vec4(position, 1.0);\n" 177 | "}\n"; 178 | 179 | static const char* sky_fshader = "#version 330\n" 180 | "precision highp float;\n" 181 | "uniform vec3 sun_dir;\n" 182 | "uniform vec3 sun_color;\n" 183 | "uniform vec3 sky_dark;\n" 184 | "uniform vec3 sky_light;\n" 185 | "in float gradient;\n" 186 | "in vec3 out_normal;\n" 187 | "out vec4 fragment;\n" 188 | "void main() {\n" 189 | " float sunamt = pow(max(0.0, dot(out_normal, sun_dir)), 5.0);\n" 190 | " vec3 skybase = mix(sky_light, sky_dark, clamp(gradient, 0.0, 1.0));\n" 191 | " fragment = vec4(mix(skybase, sun_color, sunamt), 1);\n" 192 | "}\n"; 193 | -------------------------------------------------------------------------------- /src/sky.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "math3d.h" 3 | #include "game.h" 4 | #include "sky.h" 5 | #include "geometry.h" 6 | #include "script.h" 7 | 8 | static mesh_t mesh; 9 | 10 | 11 | void sky_init() 12 | { 13 | make_hemisphere(&mesh, 5.f, 4); 14 | m_set_material(&mesh, game.materials + MAT_SKY); 15 | sky_tick(0); 16 | } 17 | 18 | 19 | void sky_exit() 20 | { 21 | m_destroy_mesh(&mesh); 22 | } 23 | 24 | 25 | void sky_draw() 26 | { 27 | material_t* material = mesh.material; 28 | glCullFace(GL_FRONT); 29 | glDepthFunc(GL_EQUAL); 30 | glDepthRange(1, 1); 31 | mat44_t skyview; 32 | vec3_t origo = {0, 0, 0}; 33 | if (game.camera.mode != CAMERA_3RDPERSON) 34 | m_fpsmatrix(&skyview, origo, game.camera.pitch, game.camera.yaw); 35 | else { 36 | vec3_t at = m_dvec3tovec3(m_dvec3sub(game.player.pos, game.camera.pos)); 37 | m_lookat(&skyview, m_vec3(0, 0, 0), at, m_up); 38 | } 39 | m_use(material); 40 | m_uniform_mat44(material->projmat, m_getmatrix(&game.projection)); 41 | m_uniform_mat44(material->modelview, &skyview); 42 | m_uniform_vec3(material->sun_dir, &game.light_dir); 43 | m_uniform_vec3(material->sun_color, &game.sun_color); 44 | m_uniform_vec3(material->sky_dark, &game.sky_dark); 45 | m_uniform_vec3(material->sky_light, &game.sky_light); 46 | m_draw(&mesh); 47 | m_use(NULL); 48 | glDepthFunc(GL_LESS); 49 | glDepthRange(0, 1); 50 | glCullFace(GL_BACK); 51 | } 52 | 53 | static 54 | vec3_t sun_mix(const vec3_t* colors, double day_amt, double dusk_amt, double night_amt, double dawn_amt) 55 | { 56 | vec3_t c; 57 | c.x = colors[0].x*day_amt + colors[1].x*dusk_amt + colors[2].x*night_amt + colors[3].x*dawn_amt; 58 | c.y = colors[0].y*day_amt + colors[1].y*dusk_amt + colors[2].y*night_amt + colors[3].y*dawn_amt; 59 | c.z = colors[0].z*day_amt + colors[1].z*dusk_amt + colors[2].z*night_amt + colors[3].z*dawn_amt; 60 | return c; 61 | } 62 | 63 | static inline 64 | vec3_t mkrgb(uint32_t rgb) 65 | { 66 | vec3_t c = {((float)((rgb>>16)&0xff)/255.f), 67 | ((float)((rgb>>8)&0xff)/255.f), 68 | ((float)((rgb)&0xff)/255.f) }; 69 | return c; 70 | } 71 | 72 | void sky_tick(float dt) 73 | { 74 | double daylength; 75 | if (game.fast_day_mode) 76 | daylength = script_get("game.fast_day_length"); 77 | else 78 | daylength = script_get("game.day_length"); 79 | double step = (dt / daylength); 80 | game.time_of_day += step; 81 | while (game.time_of_day >= 1.0) { 82 | game.day += 1; 83 | game.time_of_day -= 1.0; 84 | } 85 | 86 | double t = game.time_of_day; 87 | double day_amt, night_amt, dawn_amt, dusk_amt; 88 | 89 | const double day_length = 0.5; 90 | const double dawn_length = 0.15; 91 | const double dusk_length = 0.1; 92 | const double night_length = 0.25; 93 | 94 | if (t >= 0 && t < day_length) { 95 | day_amt = 1.0; 96 | night_amt = dawn_amt = dusk_amt = 0; 97 | } else if (t >= day_length && t < (day_length + dusk_length)) { 98 | double f = (t - day_length) * (1.0 / dusk_length); // 0-1 99 | dusk_amt = sin(f * ML_PI); 100 | if (f < 0.5) { 101 | day_amt = 1.0 - dusk_amt; 102 | night_amt = 0.0; 103 | } else { 104 | day_amt = 0.0; 105 | night_amt = 1.0 - dusk_amt; 106 | } 107 | dawn_amt = 0; 108 | } else if (t >= (day_length + dusk_length) && t < (day_length + dusk_length + night_length)) { 109 | night_amt = 1.0; 110 | dawn_amt = dusk_amt = day_amt = 0; 111 | } else { 112 | double f = (t - (day_length + dusk_length + night_length)) * (1.0 / dawn_length); // 0-1 113 | dawn_amt = sin(f * ML_PI); 114 | if (f < 0.5) { 115 | night_amt = 1.0 - dawn_amt; 116 | day_amt = 0.0; 117 | } else { 118 | night_amt = 0.0; 119 | day_amt = 1.0 - dawn_amt; 120 | } 121 | dusk_amt = 0; 122 | } 123 | 124 | //double mag = 1.0 / sqrt(day_amt*day_amt + night_amt*night_amt + dawn_amt*dawn_amt + dusk_amt*dusk_amt); 125 | //day_amt *= mag; 126 | //night_amt *= mag; 127 | //dawn_amt *= mag; 128 | //dusk_amt *= mag; 129 | 130 | double low_light = 0.1; 131 | double lightlevel = ML_MAX(day_amt, low_light); 132 | game.light_level = lightlevel; 133 | 134 | #define MKRGB(rgb) mkrgb(0x##rgb) 135 | 136 | // day, dusk, night, dawn 137 | vec3_t ambient[4]; 138 | ambient[0] = MKRGB(ffffff); 139 | ambient[1] = MKRGB(544769); 140 | ambient[2] = MKRGB(101010); 141 | ambient[3] = MKRGB(6f2168); 142 | vec3_t sky_dark[4]; 143 | sky_dark[0] = MKRGB(3F6CB4); 144 | sky_dark[1] = MKRGB(40538e); 145 | sky_dark[2] = MKRGB(000000); 146 | sky_dark[3] = MKRGB(3d2163); 147 | vec3_t sky_light[4]; 148 | sky_light[0] = MKRGB(00AAFF); 149 | sky_light[1] = MKRGB(6a6ca5); 150 | sky_light[2] = MKRGB(171b33); 151 | sky_light[3] = MKRGB(e16e7a); 152 | vec3_t sun_color[4]; 153 | sun_color[0] = MKRGB(E8EAE7); 154 | sun_color[1] = MKRGB(fdf2c9); 155 | sun_color[2] = MKRGB(e2f3fa); 156 | sun_color[3] = MKRGB(fefebb); 157 | vec3_t fog[4]; 158 | fog[0] = MKRGB(7ed4ff); 159 | fog[1] = MKRGB(ad6369); 160 | fog[2] = MKRGB(383e60); 161 | fog[3] = MKRGB(f7847a); 162 | 163 | const float fogdensity[4] = { 164 | 0.007*0.25, 165 | 0.0133*0.25, 166 | 0.008*0.25, 167 | 0.0166*0.25 168 | }; 169 | 170 | game.amb_light = sun_mix(ambient, day_amt, dusk_amt, night_amt, dawn_amt); 171 | game.sky_dark = sun_mix(sky_dark, day_amt, dusk_amt, night_amt, dawn_amt); 172 | game.sky_light = sun_mix(sky_light, day_amt, dusk_amt, night_amt, dawn_amt); 173 | game.sun_color = sun_mix(sun_color, day_amt, dusk_amt, night_amt, dawn_amt); 174 | vec3_t fogc = sun_mix(fog, day_amt, dusk_amt, night_amt, dawn_amt); 175 | float fogd = fogdensity[0]*day_amt + fogdensity[1]*dusk_amt + fogdensity[2]*night_amt + fogdensity[3]*dawn_amt; 176 | 177 | m_setvec4(game.fog_color, fogc.x, fogc.y, fogc.z, fogd); 178 | m_setvec3(game.light_dir, cos(t * ML_TWO_PI), -sin(t * ML_TWO_PI), 0); 179 | game.light_dir = m_vec3normalize(game.light_dir); 180 | } 181 | -------------------------------------------------------------------------------- /src/sky.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void sky_init(void); 4 | void sky_exit(void); 5 | void sky_draw(void); 6 | void sky_tick(float dt); 7 | -------------------------------------------------------------------------------- /src/stb.c: -------------------------------------------------------------------------------- 1 | /* just to get STB warnings as seldom as possible */ 2 | #define STB_DEFINE 3 | #include "stb.h" 4 | -------------------------------------------------------------------------------- /src/sys.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | char *sys_readfile(const char *filename) 9 | { 10 | FILE *f = fopen(filename, "rb"); 11 | if (f == NULL) 12 | return NULL; 13 | fseek(f, 0, SEEK_END); 14 | long flen = ftell(f); 15 | fseek(f, 0, SEEK_SET); 16 | char *data = (char *)malloc(flen + 1); 17 | fread(data, 1, flen, f); 18 | data[flen] = '\0'; 19 | fclose(f); 20 | return data; 21 | } 22 | 23 | 24 | // reallocate if buffer size is too small, else reuse buffer 25 | // returns buffer size 26 | char* sys_readfile_realloc(const char* filename, char* buffer, size_t* len) 27 | { 28 | FILE *f = fopen(filename, "rb"); 29 | if (f == NULL) 30 | return NULL; 31 | fseek(f, 0, SEEK_END); 32 | long flen = ftell(f); 33 | fseek(f, 0, SEEK_SET); 34 | if ((long)*len < flen + 1) { 35 | buffer = (char *)realloc(buffer, flen + 1); 36 | *len = flen + 1; 37 | } 38 | fread(buffer, 1, flen, f); 39 | buffer[flen] = '\0'; 40 | fclose(f); 41 | return buffer; 42 | } 43 | 44 | 45 | int sys_isfile(const char* filename) 46 | { 47 | int ret; 48 | struct stat tmp; 49 | ret = stat(filename, &tmp); 50 | if (ret == -1) 51 | return 0; 52 | return 1; 53 | } 54 | -------------------------------------------------------------------------------- /src/u8.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* returns a unicode character or EOF 6 | doesn't handle more than 4-byte sequences 7 | Valid utf-8 sequences look like this : 8 | 0xxxxxxx 9 | 110xxxxx 10xxxxxx 10 | 1110xxxx 10xxxxxx 10xxxxxx 11 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 12 | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 13 | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 14 | */ 15 | int u8_getc(FILE* fp) { 16 | int c[4]; 17 | c[0] = getc(fp); 18 | if (c[0] < 0x80) return c[0]; 19 | if (c[0] < 0xC2) goto error; 20 | c[1] = getc(fp); 21 | if (c[1] < 0) return c[1]; 22 | if ((c[1] & 0xc0) != 0x80) goto error1; 23 | if (c[0] < 0xe0) { 24 | return (c[0] << 6) + c[1] - 0x3080; 25 | } else if (c[0] < 0xf0) { 26 | if (c[0] == 0xe0 && c[1] < 0xa0) goto error1; /* overlong */ 27 | c[2] = getc(fp); 28 | if (c[2] < 0) return c[2]; 29 | if ((c[2] & 0xc0) != 0x80) goto error2; 30 | return (c[0] << 12) + (c[1] << 6) + c[2] - 0xe2080; 31 | } else if (c[0] < 0xf5) { 32 | if (c[0] == 0xf0 && c[1] < 0x90) goto error1; /* overlong */ 33 | if (c[0] == 0xf4 && c[1] >= 0x90) goto error1; /* > U+10FFFF */ 34 | c[2] = getc(fp); 35 | if (c[2] < 0) return c[2]; 36 | if ((c[2] & 0xc0) != 0x80) goto error2; 37 | c[3] = getc(fp); 38 | if (c[3] < 0) return c[3]; 39 | if ((c[2] & 0xc0) != 0x80) goto error3; 40 | return (c[0] << 18) + (c[1] << 12) + (c[2] << 6) + c[3] - 0x3c82080; 41 | } 42 | /* > U+10FFFF */ 43 | goto error; 44 | error3: ungetc(c[3], fp); 45 | error2: ungetc(c[2], fp); 46 | error1: ungetc(c[1], fp); 47 | error: return '?'; 48 | } 49 | -------------------------------------------------------------------------------- /src/u8.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | int u8_getc(FILE* fp); 3 | -------------------------------------------------------------------------------- /src/ui.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "math3d.h" 3 | #include "ui.h" 4 | #include "game.h" 5 | #include "easing.h" 6 | #include "stb.h" 7 | #include "script.h" 8 | 9 | // UI drawing 10 | static material_t* ui_material = NULL; 11 | static tex2d_t ui_font; 12 | static GLint ui_screensize_index = -1; 13 | static GLint ui_tex0_index = -1; 14 | static GLuint ui_vao = 0; 15 | static GLuint ui_vbo = 0; 16 | static GLsizei ui_count = 0; 17 | static float ui_scale = 1.5; 18 | #define MAX_UI_VERTICES 8192 19 | static uivert_t ui_vertices[MAX_UI_VERTICES]; 20 | static GLsizei ui_maxcount = 0; 21 | 22 | // debug 3d drawing 23 | #define MAX_DEBUG_LINEVERTS 8192 24 | static material_t* debug_material = NULL; 25 | static GLint debug_projmat_index = -1; 26 | static GLint debug_modelview_index = -1; 27 | static posclrvert_t debug_lines[MAX_DEBUG_LINEVERTS]; 28 | static size_t debug_linevertcount = 0; 29 | static GLuint debug_vao = -1; 30 | static GLuint debug_vbo = -1; 31 | static size_t debug_maxcount = 0; 32 | 33 | static bool console_enabled = false; 34 | static bool console_first_char = true; // hack to discard toggle key 35 | static float console_fade = 0.0f; 36 | #define MAX_CONSOLE_INPUT 1024 37 | #define CONSOLE_SCROLLBACK 100 38 | static char console_cmdline[MAX_CONSOLE_INPUT]; 39 | static char console_scrollback[CONSOLE_SCROLLBACK][MAX_CONSOLE_INPUT]; 40 | static int console_scrollback_pos = 0; 41 | 42 | #define UI_CHAR_W (9) 43 | #define UI_CHAR_H (9) 44 | 45 | 46 | void ui_init(material_t* uimat, material_t* debugmat) 47 | { 48 | ui_material = uimat; 49 | ui_screensize_index = glGetUniformLocation(uimat->program, "screensize"); 50 | ui_tex0_index = glGetUniformLocation(uimat->program, "tex0"); 51 | 52 | debug_material = debugmat; 53 | debug_projmat_index = glGetUniformLocation(debugmat->program, "projmat"); 54 | debug_modelview_index = glGetUniformLocation(debugmat->program, "modelview"); 55 | printf("projmat: %d, modelview: %d\n", debug_projmat_index, debug_modelview_index); 56 | 57 | m_tex2d_load(&ui_font, "data/font.png"); 58 | 59 | glGenBuffers(1, &ui_vbo); 60 | glGenVertexArrays(1, &ui_vao); 61 | glBindVertexArray(ui_vao); 62 | glBindBuffer(GL_ARRAY_BUFFER, ui_vbo); 63 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(uivert_t), 0); 64 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(uivert_t), (void*)(1 * sizeof(vec2_t))); 65 | glVertexAttribPointer(2, GL_BGRA, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(uivert_t), (void*)(2 * sizeof(vec2_t))); 66 | glEnableVertexAttribArray(0); 67 | glEnableVertexAttribArray(1); 68 | glEnableVertexAttribArray(2); 69 | glBindVertexArray(0); 70 | ui_count = 0; 71 | 72 | glGenBuffers(1, &debug_vbo); 73 | glGenVertexArrays(1, &debug_vao); 74 | glBindVertexArray(debug_vao); 75 | glBindBuffer(GL_ARRAY_BUFFER, debug_vbo); 76 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(posclrvert_t), 0); 77 | glVertexAttribPointer(1, GL_BGRA, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(posclrvert_t), (void*)(sizeof(vec3_t))); 78 | glEnableVertexAttribArray(0); 79 | glEnableVertexAttribArray(1); 80 | glBindVertexArray(0); 81 | debug_linevertcount = 0; 82 | } 83 | 84 | void ui_exit() 85 | { 86 | m_tex2d_destroy(&ui_font); 87 | glDeleteBuffers(1, &ui_vbo); 88 | glDeleteVertexArrays(1, &ui_vao); 89 | glDeleteBuffers(1, &debug_vbo); 90 | glDeleteVertexArrays(1, &debug_vao); 91 | } 92 | 93 | void ui_tick(float dt) 94 | { 95 | ui_scale = script_get("ui.scale"); 96 | 97 | if (console_enabled) 98 | console_fade = m_clamp(console_fade + dt*1.5f, 0, 1.f); 99 | else 100 | console_fade = m_clamp(console_fade - dt*1.5f, 0, 1.f); 101 | } 102 | 103 | bool ui_console_enabled() 104 | { 105 | return console_enabled; 106 | } 107 | 108 | 109 | void ui_draw(SDL_Point* viewport) 110 | { 111 | const int NLINES = 32; 112 | if (console_enabled || console_fade > 0) { 113 | int console_width = 640 * ui_scale; 114 | console_width = ((console_width > viewport->x) ? viewport->x : console_width); 115 | size_t max_display = console_width/UI_CHAR_W*ui_scale - 1; 116 | size_t len, offset; 117 | float elastic_fade = enPerlinInOut(console_fade); 118 | uint32_t alpha = (uint32_t)(ML_MAX(elastic_fade, 0.1f)*255.5f); 119 | float yoffs = (float)(NLINES*UI_CHAR_H*ui_scale) * elastic_fade; 120 | ui_rect(0, viewport->y - (int)yoffs, console_width, NLINES*UI_CHAR_H*ui_scale, ((alpha * 5 / 6)<<24)|0x2c3e50); 121 | ui_rect(0, viewport->y - (int)yoffs - UI_CHAR_H*ui_scale - 4, console_width, UI_CHAR_H*ui_scale + 4, (alpha<<24)|0x34495e); 122 | int sby = viewport->y - (int)yoffs + 2; 123 | int sbpos = (console_scrollback_pos - 1) % CONSOLE_SCROLLBACK; 124 | if (sbpos < 0) 125 | sbpos = CONSOLE_SCROLLBACK - 1; 126 | while (sbpos != console_scrollback_pos && sby < viewport->y) { 127 | if (console_scrollback[sbpos][0] == '\0') 128 | break; 129 | len = strlen(console_scrollback[sbpos]); 130 | offset = (len > max_display) ? (len - max_display) : 0; 131 | ui_text(2, sby, (alpha<<24)|0xbdc3c7, "%s", console_scrollback[sbpos] + offset); 132 | sby += UI_CHAR_H*ui_scale; 133 | sbpos = (sbpos - 1) % CONSOLE_SCROLLBACK; 134 | if (sbpos < 0) 135 | sbpos = CONSOLE_SCROLLBACK - 1; 136 | } 137 | len = strlen(console_cmdline); 138 | offset = (len > (max_display - 1)) ? (len - (max_display - 1)) : 0; 139 | ui_text(2, viewport->y - (int)yoffs - UI_CHAR_H*ui_scale - 2, (alpha<<24)|0xeeeeec, "#%s", console_cmdline + offset); 140 | } 141 | 142 | if (ui_count > 0) { 143 | vec2_t screensize = { (float)viewport->x, (float)viewport->y }; 144 | glEnable(GL_BLEND); 145 | // glDisable(GL_CULL_FACE); 146 | glDisable(GL_DEPTH_TEST); 147 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 148 | glUseProgram(ui_material->program); 149 | m_tex2d_bind(&ui_font, 0); 150 | glUniform2fv(ui_screensize_index, 1, (float*)&screensize); 151 | glUniform1i(ui_tex0_index, 0); 152 | glBindVertexArray(ui_vao); 153 | glBindBuffer(GL_ARRAY_BUFFER, ui_vbo); 154 | if (ui_count > ui_maxcount) { 155 | glBufferData(GL_ARRAY_BUFFER, (unsigned long)(ui_count)*sizeof(uivert_t), ui_vertices, GL_DYNAMIC_DRAW); 156 | ui_maxcount = ui_count; 157 | } else { 158 | glBufferSubData(GL_ARRAY_BUFFER, 0, (unsigned long)(ui_count)*sizeof(uivert_t), ui_vertices); 159 | } 160 | glDrawArrays(GL_TRIANGLES, 0, ui_count); 161 | glBindVertexArray(0); 162 | glUseProgram(0); 163 | glDisable(GL_BLEND); 164 | // glEnable(GL_CULL_FACE); 165 | glEnable(GL_DEPTH_TEST); 166 | ui_count = 0; 167 | } 168 | } 169 | 170 | void ui_set_scale(float scale) 171 | { 172 | ui_scale = scale; 173 | } 174 | 175 | #define MAX_TEXT_LEN 256 176 | 177 | void ui_text_measure(int* w, int* h, const char* str, ...) 178 | { 179 | char buf[MAX_TEXT_LEN]; 180 | va_list va_args; 181 | va_start(va_args, str); 182 | vsnprintf(buf, MAX_TEXT_LEN, str, va_args); 183 | va_end(va_args); 184 | int scale = ui_scale * UI_CHAR_H; 185 | 186 | size_t len; 187 | len = strlen(buf); 188 | int cx = 0, cy = scale, mx = 0; 189 | for (size_t i = 0; i < len; ++i) { 190 | if (buf[i] == '\n') { 191 | mx = (cx > mx) ? cx : mx; 192 | cx = 0; 193 | cy += scale; 194 | } 195 | cx += scale; 196 | } 197 | *w = mx; 198 | *h = cy; 199 | } 200 | 201 | 202 | void ui_text(float x, float y, uint32_t clr, const char* str, ...) 203 | { 204 | char buf[MAX_TEXT_LEN]; 205 | size_t len; 206 | int scale; 207 | float d, v; 208 | uivert_t* ptr = ui_vertices + ui_count; 209 | va_list va_args; 210 | va_start(va_args, str); 211 | vsnprintf(buf, MAX_TEXT_LEN, str, va_args); 212 | va_end(va_args); 213 | len = strlen(buf); 214 | if (ui_count + len*6 > MAX_UI_VERTICES) 215 | return; 216 | 217 | scale = ui_scale * UI_CHAR_H; 218 | 219 | int cx = 0, w = 0, h = scale; 220 | for (size_t i = 0; i < len; ++i) { 221 | if (buf[i] == '\n') { 222 | w = (cx > w) ? cx : w; 223 | cx = 0; 224 | h += scale; 225 | } 226 | cx += scale; 227 | } 228 | 229 | d = (float)UI_CHAR_W / (float)ui_font.w; 230 | v = (float)UI_CHAR_H / (float)ui_font.h; 231 | 232 | vec2_t rpos = { x, y }; 233 | for (size_t i = 0; i < len; ++i) { 234 | if (buf[i] == '\n') { 235 | rpos.x = x; 236 | rpos.y -= scale; 237 | continue; 238 | } else if (buf[i] == ' ') { 239 | rpos.x += scale; 240 | continue; 241 | } 242 | vec2_t tc = { (buf[i] - ' ') * d, 0.f }; 243 | vec2_t p1 = { rpos.x, rpos.y }; 244 | vec2_t p2 = { rpos.x + scale, rpos.y + scale }; 245 | vec2_t t1 = { tc.x, 0.f }; 246 | vec2_t t2 = { tc.x + d, v }; 247 | uivert_t quad[6]; 248 | quad[0].pos = p1, quad[0].tc.x = t1.x, quad[0].tc.y = t2.y, quad[0].clr = clr; 249 | quad[1].pos = p2, quad[1].tc.x = t2.x, quad[1].tc.y = t1.y, quad[1].clr = clr; 250 | quad[2].pos.x = p1.x, quad[2].pos.y = p2.y, quad[2].tc = t1, quad[2].clr = clr; 251 | quad[3].pos = p2, quad[3].tc.x = t2.x, quad[3].tc.y = t1.y, quad[3].clr = clr; 252 | quad[4].pos = p1, quad[4].tc.x = t1.x, quad[4].tc.y = t2.y, quad[4].clr = clr; 253 | quad[5].pos.x = p2.x, quad[5].pos.y = p1.y, quad[5].tc = t2, quad[5].clr = clr; 254 | 255 | memcpy(ptr, quad, sizeof(quad)); 256 | ptr += 6; 257 | ui_count += 6; 258 | rpos.x += scale; 259 | } 260 | } 261 | 262 | void ui_rect(float x, float y, float w, float h, uint32_t clr) 263 | { 264 | float tl = 0.5f / (float)ui_font.w; 265 | float br = 7.5f / (float)ui_font.w; 266 | float v = 2.f / (float)ui_font.h; 267 | if (ui_count + 6 > MAX_UI_VERTICES) 268 | return; 269 | uivert_t quad[6] = { 270 | { { x, y }, { tl, v }, clr }, 271 | { { x + w, y }, { br, v }, clr }, 272 | { { x, y + h }, { tl, 0 }, clr }, 273 | { { x, y + h }, { tl, 0 }, clr }, 274 | { { x + w, y }, { br, v }, clr }, 275 | { { x + w, y + h }, { br, 0 }, clr }, 276 | }; 277 | memcpy(ui_vertices + ui_count, quad, sizeof(quad)); 278 | ui_count += 6; 279 | } 280 | 281 | 282 | void ui_debug_line(vec3_t p1, vec3_t p2, uint32_t clr) 283 | { 284 | if (debug_linevertcount + 2 > MAX_DEBUG_LINEVERTS) 285 | return; 286 | debug_lines[debug_linevertcount + 0].pos = p1; 287 | debug_lines[debug_linevertcount + 0].clr = clr; 288 | debug_lines[debug_linevertcount + 1].pos = p2; 289 | debug_lines[debug_linevertcount + 1].clr = clr; 290 | debug_linevertcount += 2; 291 | 292 | //printf("(%.1f, %.1f, %.1f) - (%.1f, %.1f, %.1f)\n", 293 | // p1.x, p1.y, p1.z, p2.x, p2.y, p2.z); 294 | } 295 | 296 | void ui_debug_aabb(vec3_t center, vec3_t extent, uint32_t clr) 297 | { 298 | vec3_t maxp = m_vec3add(center, extent); 299 | vec3_t minp = m_vec3sub(center, extent); 300 | vec3_t corners[8] = { 301 | { minp.x, minp.y, minp.z }, 302 | { maxp.x, minp.y, minp.z }, 303 | { maxp.x, minp.y, maxp.z }, 304 | { minp.x, minp.y, maxp.z }, 305 | 306 | { minp.x, maxp.y, minp.z }, 307 | { maxp.x, maxp.y, minp.z }, 308 | { maxp.x, maxp.y, maxp.z }, 309 | { minp.x, maxp.y, maxp.z }, 310 | }; 311 | ui_debug_line(corners[0], corners[1], clr); 312 | ui_debug_line(corners[1], corners[2], clr); 313 | ui_debug_line(corners[2], corners[3], clr); 314 | ui_debug_line(corners[3], corners[0], clr); 315 | 316 | ui_debug_line(corners[4], corners[5], clr); 317 | ui_debug_line(corners[5], corners[6], clr); 318 | ui_debug_line(corners[6], corners[7], clr); 319 | ui_debug_line(corners[7], corners[4], clr); 320 | 321 | ui_debug_line(corners[0], corners[4], clr); 322 | ui_debug_line(corners[1], corners[5], clr); 323 | ui_debug_line(corners[2], corners[6], clr); 324 | ui_debug_line(corners[3], corners[7], clr); 325 | } 326 | 327 | void ui_debug_block(ivec3_t block, uint32_t clr) 328 | { 329 | vec3_t pos; 330 | vec3_t ext; 331 | chunkpos_t camera = player_chunk(); 332 | m_setvec3(pos, 333 | block.x - camera.x*CHUNK_SIZE, 334 | block.y, 335 | block.z - camera.z*CHUNK_SIZE); 336 | m_setvec3(ext, 0.5f, 0.5f, 0.5f); 337 | ui_debug_aabb(pos, ext, clr); 338 | } 339 | 340 | 341 | void ui_debug_point(vec3_t p, uint32_t clr) 342 | { 343 | vec3_t p0, p1, p2, p3, p4, p5; 344 | p0 = p1 = p2 = p3 = p4 = p5 = p; 345 | p0.x -= 0.05f; 346 | p1.x += 0.05f; 347 | p2.y -= 0.05f; 348 | p3.y += 0.05f; 349 | p4.z -= 0.05f; 350 | p5.z += 0.05f; 351 | ui_debug_line(p0, p1, clr); 352 | ui_debug_line(p2, p3, clr); 353 | ui_debug_line(p4, p5, clr); 354 | } 355 | 356 | void ui_debug_sphere(vec3_t p, float r, uint32_t clr) 357 | { 358 | #define SPHERE_NDIV 7 359 | 360 | ui_debug_point(p, clr); 361 | 362 | for (int i = 0; i < SPHERE_NDIV; ++i) { 363 | float st = sin(((float)i / SPHERE_NDIV) * ML_TWO_PI); 364 | float ct = cos(((float)i / SPHERE_NDIV) * ML_TWO_PI); 365 | int i2 = i + 1; 366 | if (i2 == SPHERE_NDIV) 367 | i2 = 0; 368 | float st1 = sin(((float)i2 / SPHERE_NDIV) * ML_TWO_PI); 369 | float ct1 = cos(((float)i2 / SPHERE_NDIV) * ML_TWO_PI); 370 | vec3_t p1 = { p.x + r * st, p.y, p.z + r * ct }; 371 | vec3_t p2 = { p.x + r * st1, p.y, p.z + r * ct1 }; 372 | vec3_t p3 = { p.x + r * st, p.y + r * ct, p.z }; 373 | vec3_t p4 = { p.x + r * st1, p.y + r * ct1, p.z }; 374 | ui_debug_line(p1, p2, clr); 375 | ui_debug_line(p3, p4, clr); 376 | } 377 | } 378 | 379 | void ui_draw_debug(mtxstack_t* projection, mtxstack_t* modelview) 380 | { 381 | if (debug_linevertcount > 0) { 382 | glEnable(GL_BLEND); 383 | glEnable(GL_DEPTH_TEST); 384 | glDepthFunc(GL_LEQUAL); 385 | glDepthMask(GL_FALSE); 386 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 387 | glUseProgram(debug_material->program); 388 | m_uniform_mat44(debug_projmat_index, m_getmatrix(projection)); 389 | m_uniform_mat44(debug_modelview_index, m_getmatrix(modelview)); 390 | glBindVertexArray(debug_vao); 391 | glBindBuffer(GL_ARRAY_BUFFER, debug_vbo); 392 | if (debug_linevertcount > debug_maxcount) { 393 | glBufferData(GL_ARRAY_BUFFER, sizeof(posclrvert_t)*debug_linevertcount, debug_lines, GL_DYNAMIC_DRAW); 394 | debug_maxcount = debug_linevertcount; 395 | } else { 396 | glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(posclrvert_t)*debug_linevertcount, debug_lines); 397 | } 398 | glDrawArrays(GL_LINES, 0, debug_linevertcount); 399 | glBindVertexArray(0); 400 | glUseProgram(0); 401 | glDisable(GL_BLEND); 402 | glEnable(GL_DEPTH_TEST); 403 | glDepthFunc(GL_LESS); 404 | glDepthMask(GL_TRUE); 405 | debug_linevertcount = 0; 406 | } 407 | } 408 | 409 | void ui_console_toggle(bool enable) 410 | { 411 | console_enabled = enable; 412 | if (console_enabled) { 413 | SDL_StartTextInput(); 414 | } else { 415 | SDL_StopTextInput(); 416 | } 417 | } 418 | 419 | void ui_add_console_line(const char* txt) 420 | { 421 | strmcpy(console_scrollback[console_scrollback_pos], txt, MAX_CONSOLE_INPUT); 422 | console_scrollback_pos = (console_scrollback_pos + 1) % CONSOLE_SCROLLBACK; 423 | printf("> %s\n", txt); 424 | } 425 | 426 | void ui_console_printf(const char* fmt, ...) 427 | { 428 | char buf[MAX_TEXT_LEN]; 429 | va_list va_args; 430 | va_start(va_args, fmt); 431 | vsnprintf(buf, MAX_TEXT_LEN, fmt, va_args); 432 | va_end(va_args); 433 | ui_add_console_line(buf); 434 | } 435 | 436 | 437 | void ui_execute_console_command(char* cmd) 438 | { 439 | int ret = script_exec(cmd); 440 | if (ret != 0) { 441 | printf("TODO: handle ret != 0 from script command (ret = %d)\n", ret); 442 | } 443 | } 444 | 445 | bool ui_console_handle_event(SDL_Event* event) 446 | { 447 | if (!console_enabled) 448 | return false; 449 | switch (event->type) { 450 | case SDL_KEYDOWN: 451 | if (event->key.keysym.sym == SDLK_BACKQUOTE || event->key.keysym.sym == SDLK_ESCAPE) { 452 | ui_console_toggle(false); 453 | } else if (event->key.keysym.sym == SDLK_RETURN) { 454 | ui_execute_console_command(console_cmdline); 455 | memset(console_cmdline, 0, MAX_CONSOLE_INPUT); 456 | } else if (event->key.keysym.sym == SDLK_BACKSPACE) { 457 | char* s = console_cmdline; 458 | size_t len = strlen(s); 459 | while (len > 0) { 460 | if ((s[len-1] & 0x80) == 0) { 461 | s[len-1] = 0; 462 | break; 463 | } 464 | if ((s[len-1] & 0xc0) == 0x80) { 465 | /* byte from a multibyte sequence */ 466 | s[len-1] = 0; 467 | --len; 468 | } 469 | if ((s[len-1] & 0xc0) == 0xc0) { 470 | /* first byte of a multibyte sequence */ 471 | s[len-1] = 0; 472 | break; 473 | } 474 | } 475 | } 476 | return true; 477 | case SDL_TEXTINPUT: 478 | //printf("%s (%c) (%d)\n", event->text.text, event->text.text[0], event->text.text[0]); 479 | if (console_first_char) { 480 | console_first_char = false; 481 | printf("Discarding %s\n", event->text.text); 482 | return true; 483 | } 484 | if (event->text.text[0] < 0) { 485 | strmcat(console_cmdline, "?", MAX_CONSOLE_INPUT); 486 | } else { 487 | strmcat(console_cmdline, event->text.text, MAX_CONSOLE_INPUT); 488 | } 489 | return true; 490 | } 491 | return false; 492 | } 493 | 494 | -------------------------------------------------------------------------------- /src/ui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | #include "math3d.h" 4 | 5 | void ui_init(material_t* ui, material_t* debug); 6 | void ui_exit(void); 7 | void ui_tick(float dt); 8 | void ui_draw(SDL_Point* viewport); 9 | 10 | void ui_set_scale(float scale); 11 | void ui_text_measure(int* w, int* h, const char* str, ...); 12 | void ui_text(float x, float y, uint32_t clr, const char* str, ...); 13 | void ui_rect(float x, float y, float w, float h, uint32_t clr); 14 | 15 | void ui_debug_line(vec3_t p1, vec3_t p2, uint32_t clr); 16 | void ui_debug_aabb(vec3_t center, vec3_t extent, uint32_t clr); 17 | void ui_debug_block(ivec3_t block, uint32_t clr); 18 | void ui_debug_point(vec3_t p, uint32_t clr); 19 | void ui_debug_sphere(vec3_t p, float r, uint32_t clr); 20 | void ui_draw_debug(mtxstack_t* projection, mtxstack_t* modelview); 21 | 22 | void ui_console_toggle(bool enable); 23 | bool ui_console_enabled(void); 24 | bool ui_console_handle_event(SDL_Event* event); 25 | void ui_add_console_line(const char* txt); 26 | void ui_console_printf(const char* fmt, ...); 27 | --------------------------------------------------------------------------------