├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── example-scene.json ├── kart.mtl ├── kart.obj ├── shaders │ ├── frag.glsl │ ├── skybox-frag.glsl │ ├── skybox-vert.glsl │ └── vert.glsl ├── sphere.mtl ├── sphere.obj ├── test-room.json └── textures │ ├── grass.bmp │ ├── grass.png │ └── skybox │ ├── back.png │ ├── bottom.png │ ├── front.png │ ├── left.png │ ├── right.png │ └── top.png ├── build.zig ├── didot-app └── app.zig ├── didot-glfw ├── build.zig └── window.zig ├── didot-image ├── bmp.zig ├── image.zig ├── png.zig └── ppm.zig ├── didot-models ├── models.zig └── obj.zig ├── didot-objects ├── assets.zig ├── components.zig ├── main.zig └── objects.zig ├── didot-ode ├── README.md ├── build.zig └── physics.zig ├── didot-opengl ├── build.zig ├── c.zig ├── graphics.zig └── main.zig ├── didot-webgl ├── build.zig ├── graphics.zig └── js.zig ├── didot-x11 ├── build.zig ├── c.zig └── window.zig ├── didot-zwl ├── build.zig ├── window.zig └── zwl │ ├── .github │ └── workflows │ │ └── ci.yml │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── build.zig │ ├── examples │ └── softlogo.zig │ ├── logo.bgra │ └── src │ ├── opengl.zig │ ├── wayland.zig │ ├── windows.zig │ ├── windows │ ├── bits.zig │ ├── gdi32.zig │ ├── kernel32.zig │ └── user32.zig │ ├── x11.zig │ ├── x11 │ ├── auth.zig │ ├── display_info.zig │ └── types.zig │ ├── xlib.zig │ └── zwl.zig ├── didot.zig ├── docs ├── data.js ├── index.html └── main.js ├── examples ├── kart-and-cubes.png ├── kart-and-cubes │ └── example-scene.zig ├── planet-test │ └── example-scene.zig └── test-portal │ └── example-scene.zig └── zalgebra ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── gyro.zzz └── src ├── main.zig ├── mat4.zig ├── quaternion.zig ├── vec2.zig ├── vec3.zig └── vec4.zig /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache 2 | zig-out 3 | didot-ode/ode-0.16.2 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 zenith391 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Didot 2 | A Zig 3D game engine. 3 | 4 | --- 5 | 6 | ![Demo featuring skybox, karts, grass and a cube](https://raw.githubusercontent.com/zenith391/didot/master/examples/kart-and-cubes.png) 7 | 8 | ## Introduction 9 | 10 | Didot is a multi-threaded 3D game engine programmed in Zig and aimed at high-level constructs: you manipulate game objects and meshes instead of OpenGL calls and batches. 11 | 12 | It improves developers life by splitting the engine into multiple modules in order to have easier porting to other platforms. For example, you can change the windowing module from `didot-glfw` to `didot-x11` to use Xlib instead of depending on GLFW without any change to your game's code, as porting (except for shaders) is transparent to the developer's code. 13 | 14 | ## Installation 15 | Prerequisites: 16 | - Zig compiler (`master` branch, didot is currently tested with commit `0aef1fa`) 17 | 18 | You only need to launch your terminal and execute those commands in an empty directory: 19 | ```sh 20 | git clone https://github.com/zenith391/didot 21 | zig init-exe 22 | ``` 23 | And then, change the resulting `build.zig` file looks like this: 24 | ```zig 25 | const didot = @import("didot/build.zig"); 26 | const Builder = @import("std").build.Builder; 27 | 28 | pub fn build(b: *Builder) void { 29 | // ... 30 | exe.setBuildMode(mode); 31 | try didot.addEngineToExe(exe, .{ 32 | .prefix = "didot/" 33 | }); 34 | exe.install(); 35 | // ... 36 | } 37 | ``` 38 | 39 | ## Using Didot 40 | Showing a cube: 41 | ```zig 42 | const std = @import("std"); 43 | const zlm = @import("zlm"); 44 | usingnamespace @import("didot-graphics"); 45 | usingnamespace @import("didot-objects"); 46 | usingnamespace @import("didot-app"); 47 | 48 | const Vec3 = zlm.Vec3; 49 | const Allocator = std.mem.Allocator; 50 | 51 | fn init(allocator: *Allocator, app: *Application) !void { 52 | var shader = try ShaderProgram.create(@embedFile("vert.glsl"), @embedFile("frag.glsl")); 53 | 54 | var camera = try GameObject.createObject(allocator, null); 55 | camera.getComponent(Transform).?.* = .{ 56 | .position = Vec3.new(1.5, 1.5, -0.5), 57 | .rotation = Vec3.new(-120.0, -15.0, 0).toRadians() 58 | }; 59 | try camera.addComponent(Camera { .shader = shader }); 60 | try app.scene.add(camera); 61 | 62 | var cube = try GameObject.createObject(allocator, "Mesh/Cube"); 63 | cube.getComponent(Transform).?.position = Vec3.new(-1.2, 0.75, -3); 64 | try app.scene.add(cube); 65 | } 66 | 67 | pub fn main() !void { 68 | var gp = std.heap.GeneralPurposeAllocator(.{}) {}; 69 | const allocator = &gp.allocator; 70 | 71 | var scene = try Scene.create(allocator, null); 72 | comptime var systems = Systems {}; 73 | var app = Application(systems) { 74 | .title = "Test Cube", 75 | .initFn = init 76 | }; 77 | try app.run(allocator, scene); 78 | } 79 | ``` 80 | 81 | And to make that into a textured cube, only a few lines are necessary: 82 | ```zig 83 | try scene.assetManager.put("Texture/Grass", try TextureAsset.init2D(allocator, "assets/textures/grass.png", "png")); 84 | var material = Material { .texturePath = "Texture/Grass" }; 85 | cube.material = material; 86 | ``` 87 | First line loads `assets/textures/grass.png` to `Texture/Grass`. 88 | Second line creates a Material with the `Texture/Grass` texture. 89 | Third line links the cube to the newly created Material. 90 | 91 | You can also look at the [example](https://github.com/zenith391/didot/blob/master/examples/test-portal/example-scene.zig) to see how to make camera movement or load models from OBJ files or even load scenes from JSON files. 92 | 93 | Systems (currently): 94 | ```zig 95 | fn exampleSystem(query: Query(.{*Transform})) !void { 96 | var iterator = query.iterator(); 97 | while (iterator.next()) |o| { 98 | std.log.info("Someone's at position {} !", .{o.transform.position}); 99 | } 100 | } 101 | 102 | pub fn main() !void { 103 | // ... 104 | comptime var systems = Systems {}; 105 | systems.addSystem(exampleSystem); 106 | var app = Application(systems) { 107 | .title = "Test Cube", 108 | .initFn = init 109 | }; 110 | try app.run(allocator, scene); 111 | } 112 | ``` 113 | 114 | [API reference](https://zenith391.github.io/didot/#root) 115 | 116 | ## Features 117 | - [Scene editor](https://github.com/zenith391/didot-editor) 118 | - Assets manager 119 | - OpenGL backend 120 | - Shaders 121 | - Meshes 122 | - Materials 123 | - Textures 124 | - Windowing 125 | - GLFW backend 126 | - X11 backend 127 | - Model loader 128 | - OBJ files 129 | - Image loader 130 | - BMP files 131 | - PNG files 132 | - Application system for easier use 133 | - Game objects system 134 | -------------------------------------------------------------------------------- /assets/example-scene.json: -------------------------------------------------------------------------------- 1 | { 2 | "objects": [ 3 | { 4 | "name": "Camera", 5 | "position": [1.5, 1.5, -0.5], 6 | "rotation": [-120, -15, 0], 7 | "scale": [1, 1, 1], 8 | "components": { 9 | "Camera": { 10 | "shaders": { 11 | "default": "Shaders/Default", 12 | "skybox": "Shaders/Skybox" 13 | } 14 | } 15 | } 16 | }, 17 | { 18 | "name": "Light", 19 | "position": [1, 5, -5], 20 | "rotation": [0, 0, 0], 21 | "scale": [1, 1, 1], 22 | "mesh": "Mesh/Cube", 23 | "material": { 24 | "ambient": [1, 1, 1] 25 | }, 26 | "components": { 27 | "PointLight": { 28 | "color": [1, 1, 1], 29 | "constant": 1.0, 30 | "linear": 0.018, 31 | "quadratic": 0.016 32 | } 33 | } 34 | }, 35 | { 36 | "name": "Kart", 37 | "position": [0.7, 0.75, -5], 38 | "rotation": [0, 0, 0], 39 | "scale": [1, 1, 1], 40 | "mesh": "Meshes/Kart" 41 | }, 42 | { 43 | "name": "Terrain", 44 | "position": [10, -0.75, -10], 45 | "rotation": [0, 0, 0], 46 | "scale": [20, 1, 20], 47 | "mesh": "Mesh/Cube", 48 | "material": { 49 | "texture": "Texture/Grass Texture" 50 | } 51 | }, 52 | { 53 | "name": "Cube", 54 | "position": [-1.2, 0.75, 3], 55 | "rotation": [0, 0, 0], 56 | "scale": [1, 1, 1], 57 | "mesh": "Mesh/Cube", 58 | "material": { 59 | "ambient": [0.2, 0.1, 0.1], 60 | "diffuse": [0.8, 0.8, 0.8] 61 | } 62 | } 63 | ], 64 | "assets": { 65 | "Meshes/Kart": { 66 | "mesh": { 67 | "path": "assets/kart.obj", 68 | "format": "obj" 69 | } 70 | }, 71 | "Texture/Grass Texture": { 72 | "texture": { 73 | "path": "assets/grass.bmp", 74 | "format": "bmp" 75 | } 76 | }, 77 | "Shaders/Default": { 78 | "shader": { 79 | "vertex": "assets/shaders/vert.glsl", 80 | "fragment": "assets/shaders/frag.glsl" 81 | } 82 | }, 83 | "Shaders/Skybox": { 84 | "shader": { 85 | "vertex": "assets/shaders/skybox-vert.glsl", 86 | "fragment": "assets/shaders/skybox-frag.glsl" 87 | } 88 | } 89 | }, 90 | "settings": { 91 | "maxPointLights": 1 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /assets/kart.mtl: -------------------------------------------------------------------------------- 1 | newmtl Grey_-_20 2 | Ka 0.0000 0.0000 0.0000 3 | Kd 0.2000 0.2000 0.2000 4 | Ks 1.0000 1.0000 1.0000 5 | Tf 0.0000 0.0000 0.0000 6 | d 1.0000 7 | Ns 0 8 | 9 | newmtl Grey_-_40 10 | Ka 0.0000 0.0000 0.0000 11 | Kd 0.4000 0.4000 0.4000 12 | Ks 1.0000 1.0000 1.0000 13 | Tf 0.0000 0.0000 0.0000 14 | d 1.0000 15 | Ns 0 16 | 17 | newmtl Grey_-_60 18 | Ka 0.0000 0.0000 0.0000 19 | Kd 0.6000 0.6000 0.6000 20 | Ks 1.0000 1.0000 1.0000 21 | Tf 0.0000 0.0000 0.0000 22 | d 1.0000 23 | Ns 0 24 | 25 | newmtl Grey_-_80 26 | Ka 0.0000 0.0000 0.0000 27 | Kd 0.8000 0.8000 0.8000 28 | Ks 1.0000 1.0000 1.0000 29 | Tf 0.0000 0.0000 0.0000 30 | d 1.0000 31 | Ns 0 32 | 33 | newmtl Blue 34 | Ka 0.0000 0.0000 0.0000 35 | Kd 0.3843 0.6588 0.8078 36 | Ks 1.0000 1.0000 1.0000 37 | Tf 0.0000 0.0000 0.0000 38 | d 1.0000 39 | Ns 0 40 | 41 | -------------------------------------------------------------------------------- /assets/shaders/frag.glsl: -------------------------------------------------------------------------------- 1 | in vec2 texCoord; 2 | in vec3 normal; 3 | in vec3 fragPos; 4 | out vec4 outColor; 5 | 6 | struct PointLight { 7 | vec3 position; 8 | vec3 color; 9 | 10 | float constant; 11 | float linear; 12 | float quadratic; 13 | }; 14 | 15 | struct Material { 16 | vec3 ambient; 17 | vec3 diffuse; 18 | vec3 specular; 19 | float shininess; 20 | }; 21 | 22 | uniform PointLight light; 23 | uniform Material material; 24 | 25 | uniform vec3 viewPos; 26 | uniform sampler2D tex; 27 | uniform bool useTex; 28 | uniform bool useLight; 29 | 30 | uniform float xTiling; 31 | uniform float yTiling; 32 | 33 | vec3 computeLight(vec3 norm, vec3 viewDir, PointLight light) { 34 | vec3 lightDir = normalize(light.position - fragPos); 35 | float distance = length(light.position - fragPos); 36 | float attenuation = 1.0 / 37 | (light.constant + 38 | light.quadratic * (distance*distance) + 39 | light.linear * distance); 40 | 41 | // diffuse 42 | float diff = max(dot(norm, lightDir), 0.0); 43 | vec3 diffuse = diff * material.diffuse * light.color * attenuation; 44 | 45 | // specular 46 | float specularStrength = 0.5; 47 | vec3 reflectDir = reflect(-lightDir, norm); 48 | float spec = specularStrength * pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); 49 | vec3 specular = spec * material.specular * light.color * attenuation; 50 | return (diffuse + specular) * light.color; 51 | } 52 | 53 | void main() { 54 | vec3 result = material.ambient; 55 | 56 | if (useLight) { 57 | vec3 norm = normalize(normal); 58 | vec3 viewDir = normalize(viewPos - fragPos); 59 | result += computeLight(norm, viewDir, light); 60 | } else { 61 | result = material.ambient + material.diffuse; 62 | } 63 | 64 | if (useTex) { 65 | vec2 fTexCoord = vec2(texCoord.x * xTiling, texCoord.y * yTiling); 66 | outColor = texture(tex, fTexCoord) * vec4(result, 1.0); 67 | } else { 68 | outColor = vec4(result, 1.0); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /assets/shaders/skybox-frag.glsl: -------------------------------------------------------------------------------- 1 | in vec3 texCoord; 2 | out vec4 outColor; 3 | 4 | uniform samplerCube skybox; 5 | 6 | void main() { 7 | outColor = texture(skybox, texCoord); 8 | } 9 | -------------------------------------------------------------------------------- /assets/shaders/skybox-vert.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout (location=0) in vec3 position; 4 | layout (location=1) in vec3 mNormal; 5 | layout (location=2) in vec2 texcoord; 6 | 7 | out vec3 texCoord; 8 | 9 | uniform mat4 projection; 10 | uniform mat4 view; 11 | 12 | void main() { 13 | texCoord = position; 14 | gl_Position = projection * view * vec4(position, 1.0); 15 | } 16 | -------------------------------------------------------------------------------- /assets/shaders/vert.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout (location=0) in vec3 position; 4 | layout (location=1) in vec3 mNormal; 5 | layout (location=2) in vec2 texcoord; 6 | 7 | out vec2 texCoord; 8 | out vec3 normal; 9 | out vec3 fragPos; 10 | 11 | uniform mat4 projMatrix; 12 | uniform mat4 viewMatrix; 13 | uniform mat4 modelMatrix; 14 | 15 | void main() { 16 | gl_Position = projMatrix * viewMatrix * modelMatrix * vec4(position, 1.0); 17 | fragPos = vec3(modelMatrix * vec4(position, 1.0)); 18 | 19 | texCoord = texcoord; 20 | normal = mNormal; 21 | } 22 | -------------------------------------------------------------------------------- /assets/sphere.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 1 3 | 4 | newmtl None 5 | Ns 500 6 | Ka 0.8 0.8 0.8 7 | Kd 0.8 0.8 0.8 8 | Ks 0.8 0.8 0.8 9 | d 1 10 | illum 2 11 | -------------------------------------------------------------------------------- /assets/test-room.json: -------------------------------------------------------------------------------- 1 | { 2 | "objects": [ 3 | { 4 | "type": "camera", 5 | "name": "Camera", 6 | "position": [1.5, 1.5, -0.5], 7 | "rotation": [-120, -15, 0], 8 | "scale": [1, 1, 1], 9 | "shaders": { 10 | "default": "Shaders/Default", 11 | "skybox": "Shaders/Skybox" 12 | }, 13 | "components": { 14 | "CameraController": {} 15 | } 16 | }, 17 | { 18 | "type": "point_light", 19 | "name": "Light", 20 | "position": [1, 5, -5], 21 | "rotation": [0, 0, 0], 22 | "scale": [1, 1, 1], 23 | "mesh": "Meshes/Primitives/Cube", 24 | "material": { 25 | "ambient": [1, 1, 1] 26 | } 27 | }, 28 | { 29 | "type": "normal", 30 | "name": "Terrain", 31 | "position": [10, -0.75, -10], 32 | "rotation": [0, 0, 0], 33 | "scale": [250, 0.1, 250], 34 | "mesh": "Meshes/Primitives/Cube", 35 | "material": { 36 | "texture": "Textures/Grass Texture" 37 | } 38 | }, 39 | { 40 | "type": "normal", 41 | "name": "Cube", 42 | "position": [-1.2, 0.75, 3], 43 | "rotation": [0, 0, 0], 44 | "scale": [1, 2, 1], 45 | "mesh": "Meshes/Primitives/Cube", 46 | "material": { 47 | "ambient": [0.2, 0.1, 0.1], 48 | "diffuse": [0.8, 0.8, 0.8] 49 | }, 50 | "components": { 51 | "Rigidbody": {} 52 | } 53 | } 54 | ], 55 | "assets": { 56 | "Meshes/Kart": { 57 | "mesh": { 58 | "path": "assets/kart.obj", 59 | "format": "obj" 60 | } 61 | }, 62 | "Textures/Concrete": { 63 | "texture": { 64 | "path": "assets/textures/concrete2.png", 65 | "format": "png", 66 | "tiling": [2, 2] 67 | } 68 | }, 69 | "Shaders/Default": { 70 | "shader": { 71 | "vertex": "assets/shaders/vert.glsl", 72 | "fragment": "assets/shaders/frag.glsl" 73 | } 74 | }, 75 | "Shaders/Skybox": { 76 | "shader": { 77 | "vertex": "assets/shaders/skybox-vert.glsl", 78 | "fragment": "assets/shaders/skybox-frag.glsl" 79 | } 80 | } 81 | }, 82 | "settings": { 83 | "maxPointLights": 1 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /assets/textures/grass.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenith391/didot/a096869f934bb1005c1122c40029eafa51e76e58/assets/textures/grass.bmp -------------------------------------------------------------------------------- /assets/textures/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenith391/didot/a096869f934bb1005c1122c40029eafa51e76e58/assets/textures/grass.png -------------------------------------------------------------------------------- /assets/textures/skybox/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenith391/didot/a096869f934bb1005c1122c40029eafa51e76e58/assets/textures/skybox/back.png -------------------------------------------------------------------------------- /assets/textures/skybox/bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenith391/didot/a096869f934bb1005c1122c40029eafa51e76e58/assets/textures/skybox/bottom.png -------------------------------------------------------------------------------- /assets/textures/skybox/front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenith391/didot/a096869f934bb1005c1122c40029eafa51e76e58/assets/textures/skybox/front.png -------------------------------------------------------------------------------- /assets/textures/skybox/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenith391/didot/a096869f934bb1005c1122c40029eafa51e76e58/assets/textures/skybox/left.png -------------------------------------------------------------------------------- /assets/textures/skybox/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenith391/didot/a096869f934bb1005c1122c40029eafa51e76e58/assets/textures/skybox/right.png -------------------------------------------------------------------------------- /assets/textures/skybox/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenith391/didot/a096869f934bb1005c1122c40029eafa51e76e58/assets/textures/skybox/top.png -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Builder = std.build.Builder; 3 | const LibExeObjStep = std.build.LibExeObjStep; 4 | const Pkg = std.build.Pkg; 5 | 6 | pub const EngineConfig = struct { 7 | /// Path to where didot is relative to the game's build.zig file. Must end with a slash or be empty. 8 | prefix: []const u8 = "", 9 | windowModule: []const u8 = "didot-glfw", 10 | physicsModule: []const u8 = "didot-ode", 11 | graphicsModule: []const u8 = "didot-opengl", 12 | /// Whether or not to automatically set the recommended window module depending on the target platform. 13 | /// Currently, didot-x11 will be used for Linux and didot-glfw for other platforms. 14 | /// Temporarily disabled! 15 | autoWindow: bool = true, 16 | /// Whether or not to include the physics module 17 | usePhysics: bool = false, 18 | embedAssets: bool = false, 19 | }; 20 | 21 | pub fn addEngineToExe(step: *LibExeObjStep, comptime config: EngineConfig) !void { 22 | const prefix = config.prefix; 23 | 24 | const zlm = Pkg { 25 | .name = "zalgebra", 26 | .path = std.build.FileSource.relative(prefix ++ "zalgebra/src/main.zig") 27 | }; 28 | const image = Pkg { 29 | .name = "didot-image", 30 | .path = std.build.FileSource.relative(prefix ++ "didot-image/image.zig") 31 | }; 32 | 33 | const windowModule = comptime blk: { 34 | // if (config.autoWindow) { 35 | // const target = step.target.toTarget().os.tag; 36 | // break :blk switch (target) { 37 | // .linux => "didot-x11", 38 | // else => "didot-glfw" 39 | // }; 40 | // } else { 41 | break :blk config.windowModule; 42 | // } 43 | }; 44 | 45 | if (!comptime std.mem.eql(u8, windowModule, "didot-glfw")) { 46 | @compileError("windowing module must be didot-glfw"); 47 | } else if (!comptime std.mem.eql(u8, config.graphicsModule, "didot-opengl")) { 48 | @compileError("graphics module must be OpenGL"); 49 | } else if (!comptime std.mem.eql(u8, config.physicsModule, "didot-ode")) { 50 | @compileError("graphics module must be ODE"); 51 | } 52 | 53 | //const windowPath = windowModule ++ "/build.zig"; 54 | const window = (try @import("didot-glfw/build.zig").build(step, config)) orelse Pkg { 55 | .name = "didot-window", 56 | .path = std.build.FileSource.relative(prefix ++ windowModule ++ "/window.zig"), 57 | .dependencies = &[_]Pkg{zlm} 58 | }; 59 | 60 | const graphics = Pkg { 61 | .name = "didot-graphics", 62 | .path = std.build.FileSource.relative(prefix ++ config.graphicsModule ++ "/main.zig"), 63 | .dependencies = &[_]Pkg{window,image,zlm} 64 | }; 65 | try @import("didot-opengl/build.zig").build(step); 66 | 67 | const models = Pkg { 68 | .name = "didot-models", 69 | .path = std.build.FileSource.relative(prefix ++ "didot-models/models.zig"), 70 | .dependencies = &[_]Pkg{zlm,graphics} 71 | }; 72 | 73 | if (config.embedAssets) { 74 | try createBundle(step); 75 | } 76 | 77 | const objects = Pkg { 78 | .name = "didot-objects", 79 | .path = std.build.FileSource.relative(prefix ++ "didot-objects/main.zig"), 80 | .dependencies = &[_]Pkg{zlm,graphics,models,image} 81 | }; 82 | 83 | const physics = Pkg { 84 | .name = "didot-physics", 85 | .path = std.build.FileSource.relative(prefix ++ config.physicsModule ++ "/physics.zig"), 86 | .dependencies = &[_]Pkg{objects, zlm} 87 | }; 88 | if (config.usePhysics) { 89 | try @import("didot-ode/build.zig").build(step); 90 | step.addPackage(physics); 91 | } 92 | 93 | const app = Pkg { 94 | .name = "didot-app", 95 | .path = std.build.FileSource.relative(prefix ++ "didot-app/app.zig"), 96 | .dependencies = &[_]Pkg{objects,graphics} 97 | }; 98 | 99 | step.addPackage(zlm); 100 | step.addPackage(image); 101 | step.addPackage(window); 102 | step.addPackage(graphics); 103 | step.addPackage(objects); 104 | step.addPackage(models); 105 | step.addPackage(app); 106 | } 107 | 108 | fn createBundle(step: *LibExeObjStep) !void { 109 | const b = step.builder; 110 | const allocator = b.allocator; 111 | var dir = try std.fs.openDirAbsolute(b.build_root, .{}); 112 | defer dir.close(); 113 | 114 | var cacheDir = try dir.openDir(b.cache_root, .{}); 115 | defer cacheDir.close(); 116 | (try cacheDir.makeOpenPath("assets", .{})).close(); // mkdir "assets" 117 | 118 | const fullName = try std.mem.concat(allocator, u8, &[_][]const u8 {"assets/", step.name, ".bundle"}); 119 | const cacheFile = try cacheDir.createFile(fullName, .{ .truncate = true }); 120 | defer cacheFile.close(); 121 | allocator.free(fullName); 122 | const writer = cacheFile.writer(); 123 | 124 | const dirPath = try dir.realpathAlloc(allocator, "assets"); 125 | defer allocator.free(dirPath); 126 | 127 | var walkedDir = try std.fs.openDirAbsolute(dirPath, .{ .iterate = true }); 128 | defer walkedDir.close(); 129 | 130 | var count: u64 = 0; 131 | 132 | // Count the number of items 133 | { 134 | var walker = try walkedDir.walk(allocator); 135 | defer walker.deinit(); 136 | while ((try walker.next()) != null) { 137 | count += 1; 138 | } 139 | } 140 | 141 | var walker = try walkedDir.walk(allocator); 142 | defer walker.deinit(); 143 | 144 | try writer.writeByte(0); // no compression 145 | try writer.writeIntLittle(u64, count); 146 | while (try walker.next()) |entry| { 147 | if (entry.kind == .File) { 148 | const path = entry.path[dirPath.len+1..]; 149 | const file = try std.fs.openFileAbsolute(entry.path, .{}); 150 | defer file.close(); 151 | const data = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); 152 | defer allocator.free(data); 153 | 154 | try writer.writeIntLittle(u32, @intCast(u32, path.len)); 155 | try writer.writeIntLittle(u32, @intCast(u32, data.len)); 156 | try writer.writeAll(path); 157 | try writer.writeAll(data); 158 | } 159 | } 160 | } 161 | 162 | pub fn build(b: *Builder) !void { 163 | var target = b.standardTargetOptions(.{}); 164 | var mode = b.standardReleaseOptions(); 165 | const stripExample = b.option(bool, "strip-example", "Attempt to minify examples by stripping them and changing release mode.") orelse false; 166 | const wasm = b.option(bool, "wasm-target", "Compile the code to run on WASM backend.") orelse false; 167 | 168 | if (@hasField(LibExeObjStep, "emit_docs")) { 169 | const otest = b.addTest("didot.zig"); 170 | otest.emit_docs = true; 171 | //otest.emit_bin = false; 172 | try addEngineToExe(otest, .{ 173 | .autoWindow = false, 174 | .usePhysics = true 175 | }); 176 | 177 | const test_step = b.step("doc", "Test and generate documentation for Didot"); 178 | test_step.dependOn(&otest.step); 179 | } else { 180 | const no_doc = b.addSystemCommand(&[_][]const u8{"echo", "Please build with the latest version of Zig to be able to emit documentation."}); 181 | const no_doc_step = b.step("doc", "Test and generate documentation for Didot"); 182 | no_doc_step.dependOn(&no_doc.step); 183 | } 184 | 185 | const examples = [_][2][]const u8 { 186 | .{"test-portal", "examples/test-portal/example-scene.zig"}, 187 | .{"kart-and-cubes", "examples/kart-and-cubes/example-scene.zig"}, 188 | .{"planet-test", "examples/planet-test/example-scene.zig"} 189 | }; 190 | 191 | const engineConfig = EngineConfig { 192 | .windowModule = "didot-glfw", 193 | .graphicsModule = "didot-opengl", 194 | .autoWindow = false, 195 | .usePhysics = true, 196 | .embedAssets = true 197 | }; 198 | 199 | if (wasm) { 200 | target = try std.zig.CrossTarget.parse(.{ 201 | .arch_os_abi = "wasm64-freestanding-gnu" 202 | }); 203 | } 204 | 205 | inline for (examples) |example| { 206 | const name = example[0]; 207 | const path = example[1]; 208 | 209 | const exe = b.addExecutable(name, path); 210 | exe.setTarget(target); 211 | exe.setBuildMode(if (stripExample) std.builtin.Mode.ReleaseSmall else mode); 212 | try addEngineToExe(exe, engineConfig); 213 | exe.single_threaded = stripExample; 214 | exe.strip = stripExample; 215 | exe.install(); 216 | 217 | const run_cmd = exe.run(); 218 | run_cmd.step.dependOn(&exe.install_step.?.step); 219 | 220 | const run_step = b.step(name, "Test Didot with the " ++ name ++ " example"); 221 | run_step.dependOn(&run_cmd.step); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /didot-app/app.zig: -------------------------------------------------------------------------------- 1 | //! The application module is used for managing the lifecycle of a video game. 2 | const graphics = @import("didot-graphics"); 3 | const objects = @import("didot-objects"); 4 | const std = @import("std"); 5 | const single_threaded = @import("builtin").single_threaded; 6 | const Allocator = std.mem.Allocator; 7 | const Window = graphics.Window; 8 | const Scene = objects.Scene; 9 | 10 | const System = struct { 11 | function: anytype, 12 | type: type, 13 | }; 14 | 15 | pub const Systems = struct { 16 | items: []const System = &[0]System {}, 17 | 18 | pub fn addSystem(comptime self: *Systems, system: anytype) void { 19 | const T = @TypeOf(system); 20 | const arr = [1]System { .{ .type = T, .function = system } }; 21 | self.items = self.items ++ arr; 22 | } 23 | }; 24 | 25 | pub fn log( 26 | comptime message_level: std.log.Level, 27 | comptime scope: @Type(.EnumLiteral), 28 | comptime format: []const u8, 29 | args: anytype, 30 | ) void { 31 | if (@enumToInt(message_level) <= @enumToInt(std.log.level)) { 32 | const level_txt = switch (message_level) { 33 | .emerg => "emergency", 34 | .alert => "alert", 35 | .crit => "critical", 36 | .err => "error", 37 | .warn => "warning", 38 | .notice => "notice", 39 | .info => "info", 40 | .debug => "debug", 41 | }; 42 | const prefix2 = if (scope == .default) " -> " else " (" ++ @tagName(scope) ++ ") -> "; 43 | if (@import("builtin").cpu.arch == .wasm64) { 44 | // TODO 45 | } else { 46 | const stderr = std.io.getStdErr().writer(); 47 | const held = std.debug.getStderrMutex().acquire(); 48 | defer held.release(); 49 | nosuspend stderr.print("[" ++ level_txt ++ "]" ++ prefix2 ++ format ++ "\n", args) catch return; 50 | } 51 | } 52 | } 53 | 54 | const SystemList = std.ArrayList(usize); 55 | 56 | /// Helper class for using Didot. 57 | pub fn Application(comptime systems: Systems) type { 58 | const App = struct { 59 | const Self = @This(); 60 | /// How many time per second updates should be called, defaults to 60 updates/s. 61 | updateTarget: u32 = 60, 62 | window: Window = undefined, 63 | /// The current scene, this is set by init() and start(). 64 | /// It can also be set manually to change scene in-game. 65 | scene: *Scene = undefined, 66 | title: [:0]const u8 = "Didot Game", 67 | allocator: *Allocator = undefined, 68 | /// Optional function to be called on application init. 69 | initFn: ?fn(allocator: *Allocator, app: *Self) callconv(if (std.io.is_async) .Async else .Unspecified) anyerror!void = null, 70 | closing: bool = false, 71 | timer: std.time.Timer = undefined, 72 | 73 | /// Initialize the application using the given allocator and scene. 74 | /// This creates a window, init primitives and call the init function if set. 75 | pub fn init(self: *Self, allocator: *Allocator, scene: *Scene) !void { 76 | var window = try Window.create(); 77 | errdefer window.deinit(); 78 | window.setTitle(self.title); 79 | self.scene = scene; 80 | errdefer scene.deinit(); 81 | self.window = window; 82 | self.window.setMain(); 83 | self.allocator = allocator; 84 | self.timer = try std.time.Timer.start(); 85 | objects.initPrimitives(); 86 | 87 | try scene.assetManager.put("Mesh/Cube", .{ 88 | .objectPtr = @ptrToInt(&objects.PrimitiveCubeMesh), 89 | .unloadable = false, 90 | .objectType = .Mesh 91 | }); 92 | try scene.assetManager.put("Mesh/Plane", .{ 93 | .objectPtr = @ptrToInt(&objects.PrimitivePlaneMesh), 94 | .unloadable = false, 95 | .objectType = .Mesh 96 | }); 97 | 98 | if (self.initFn) |func| { 99 | if (std.io.is_async) { 100 | var stack = try allocator.alignedAlloc(u8, 16, @frameSize(func)); 101 | defer allocator.free(stack); 102 | var result: anyerror!void = undefined; 103 | try await @asyncCall(stack, &result, func, .{allocator, self}); 104 | } else { 105 | try func(allocator, self); 106 | } 107 | } 108 | } 109 | 110 | fn printErr(err: anyerror) void { 111 | std.log.err("{s}", .{@errorName(err)}); 112 | if (@errorReturnTrace()) |trace| { 113 | std.debug.dumpStackTrace(trace.*); 114 | } 115 | } 116 | 117 | fn updateTick(self: *Self, comptime doSleep: bool) void { 118 | // var arena = std.heap.ArenaAllocator.init(self.allocator); 119 | // const allocator = &arena.allocator; 120 | // defer arena.deinit(); 121 | 122 | const time_per_frame = (1 / @intToFloat(f64, self.updateTarget)) * std.time.ns_per_s; 123 | const time = self.timer.lap(); 124 | const dt = @floatCast(f32, time_per_frame / @intToFloat(f64, time)); 125 | _ = dt; 126 | 127 | inline for (systems.items) |sys| { 128 | const info = @typeInfo(sys.type).Fn; 129 | var tuple: std.meta.ArgsTuple(sys.type) = undefined; 130 | var skip: bool = false; 131 | comptime var isForEach: bool = false; 132 | comptime var forEachTypes: []const type = &[0]type {}; 133 | 134 | inline for (info.args) |arg, i| { 135 | const key = comptime std.fmt.comptimePrint("{}", .{i}); 136 | const Type = arg.arg_type.?; 137 | if (comptime std.mem.eql(u8, @typeName(Type), "SystemQuery")) { 138 | var query = Type { .scene = self.scene }; 139 | @field(tuple, key) = query; 140 | } else if (comptime std.meta.trait.isSingleItemPtr(Type)) { 141 | isForEach = true; 142 | forEachTypes = forEachTypes ++ &[_]type {Type}; 143 | } else { 144 | @compileError("Invalid argument type: " ++ @typeName(Type)); 145 | } 146 | } 147 | 148 | if (!skip) { 149 | const opts: std.builtin.CallOptions = .{}; 150 | if (isForEach) { 151 | const tupleTypes = [1]type {type} ** forEachTypes.len; 152 | const TupleType = std.meta.Tuple(&tupleTypes); 153 | comptime var queryTuple: TupleType = undefined; 154 | comptime { 155 | inline for (forEachTypes) |compType, i| { 156 | const name = std.fmt.comptimePrint("{}", .{i}); 157 | @field(queryTuple, name) = compType; 158 | } 159 | } 160 | 161 | var query = objects.Query(queryTuple) { .scene = self.scene }; 162 | var it = query.iterator(); 163 | 164 | while (it.next()) |o| { 165 | inline for (info.args) |arg, i| { 166 | if (comptime std.meta.trait.isSingleItemPtr(arg.arg_type.?)) { 167 | const key = comptime std.fmt.comptimePrint("{}", .{i}); 168 | const name = @typeName(std.meta.Child(arg.arg_type.?)); 169 | comptime var fieldName: [name.len]u8 = undefined; 170 | fieldName = name.*; 171 | fieldName[0] = comptime std.ascii.toLower(fieldName[0]); 172 | @field(tuple, key) = @field(o, &fieldName); 173 | } 174 | } 175 | try @call(opts, sys.function, tuple); 176 | } 177 | } else { 178 | try @call(opts, sys.function, tuple); 179 | } 180 | } 181 | } 182 | 183 | const updateLength = self.timer.read(); 184 | if (doSleep) { 185 | const wait = @floatToInt(u64, 186 | std.math.max(0, @floor( 187 | (1.0 / @intToFloat(f64, self.updateTarget)) * std.time.ns_per_s 188 | - @intToFloat(f64, updateLength))) 189 | ); 190 | std.time.sleep(wait); 191 | } 192 | } 193 | 194 | fn updateLoop(self: *Self) void { 195 | while (!self.closing) { 196 | self.updateTick(true); 197 | } 198 | } 199 | 200 | /// Start the game loop, that is doing rendering. 201 | /// It is also ensuring game updates and updating the window. 202 | pub fn loop(self: *Self) !void { 203 | var thread: std.Thread = undefined; 204 | if (!single_threaded) { 205 | if (std.io.is_async) { 206 | _ = async self.updateLoop(); 207 | } else { 208 | thread = try std.Thread.spawn(.{}, updateLoop, .{ self }); 209 | try thread.setName("Update-Thread"); 210 | } 211 | } 212 | while (self.window.update()) { 213 | if (single_threaded) { 214 | self.updateTick(false); 215 | } 216 | try self.scene.render(self.window); 217 | } 218 | self.closing = true; 219 | if (!single_threaded and !std.io.is_async) { 220 | thread.join(); // thread must be closed before scene is de-init (to avoid use-after-free) 221 | } 222 | self.closing = false; 223 | self.deinit(); 224 | } 225 | 226 | pub fn deinit(self: *Self) void { 227 | self.window.deinit(); 228 | self.scene.deinitAll(); 229 | } 230 | 231 | /// Helper method to call both init() and loop(). 232 | pub fn run(self: *Self, allocator: *Allocator, scene: *Scene) !void { 233 | try self.init(allocator, scene); 234 | try self.loop(); 235 | } 236 | 237 | }; 238 | return App; 239 | } 240 | 241 | test "app" { 242 | comptime var systems = Systems {}; 243 | var app = Application(systems) {}; 244 | try app.init(std.testing.allocator, try Scene.create(std.testing.allocator, null)); 245 | app.window.setShouldClose(true); 246 | try app.loop(); 247 | } -------------------------------------------------------------------------------- /didot-glfw/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const EngineConfig = @import("../build.zig").EngineConfig; 3 | 4 | pub fn build(step: *std.build.LibExeObjStep, comptime config: EngineConfig) !?std.build.Pkg { 5 | _ = config; 6 | 7 | step.linkSystemLibrary("glfw"); 8 | step.linkLibC(); 9 | return null; 10 | } 11 | -------------------------------------------------------------------------------- /didot-glfw/window.zig: -------------------------------------------------------------------------------- 1 | const c = @cImport({ 2 | @cInclude("GLFW/glfw3.h"); 3 | }); 4 | 5 | pub const WindowError = error { 6 | InitializationError 7 | }; 8 | 9 | const std = @import("std"); 10 | const zalgebra = @import("zalgebra"); 11 | const Vec2 = zalgebra.Vec2; 12 | 13 | var mainWindow: ?*Window = null; 14 | 15 | // TODO: more inputs and a more efficient way to do them 16 | 17 | pub const Input = struct { 18 | nativeId: *c.GLFWwindow, 19 | lastMousePos: Vec2 = Vec2.zero(), 20 | mouseDelta: Vec2 = Vec2.zero(), 21 | firstFrame: bool = true, 22 | 23 | pub const KEY_A = c.GLFW_KEY_A; 24 | pub const KEY_D = c.GLFW_KEY_D; 25 | pub const KEY_S = c.GLFW_KEY_S; 26 | pub const KEY_W = c.GLFW_KEY_W; 27 | 28 | pub const KEY_ESCAPE = c.GLFW_KEY_ESCAPE; 29 | pub const KEY_SPACE = c.GLFW_KEY_SPACE; 30 | 31 | pub const KEY_UP = c.GLFW_KEY_UP; 32 | pub const KEY_LEFT = c.GLFW_KEY_LEFT; 33 | pub const KEY_RIGHT = c.GLFW_KEY_RIGHT; 34 | pub const KEY_DOWN = c.GLFW_KEY_DOWN; 35 | 36 | pub const MouseInputMode = enum { 37 | Normal, 38 | Hidden, 39 | Grabbed 40 | }; 41 | 42 | pub const MouseButton = enum { 43 | Left, 44 | Middle, 45 | Right 46 | }; 47 | 48 | pub const Joystick = struct { 49 | id: u4, 50 | name: []const u8, 51 | /// This doesn't necessarily means the joystick *IS* a gamepad, this means it is registered in the DB. 52 | isGamepad: bool, 53 | 54 | pub const ButtonType = enum { 55 | A, 56 | B, 57 | X, 58 | Y, 59 | LeftBumper, 60 | RightBumper, 61 | Back, 62 | Start, 63 | Guide, 64 | LeftThumb, 65 | RightThumb, 66 | DPad_Up, 67 | DPad_Right, 68 | DPad_Down, 69 | DPad_Left 70 | }; 71 | 72 | pub fn getRawAxes(self: *const Joystick) []const f32 { 73 | var count: c_int = 0; 74 | const axes = c.glfwGetJoystickAxes(self.id, &count); 75 | return axes[0..@intCast(usize, count)]; 76 | } 77 | 78 | pub fn getRawButtons(self: *const Joystick) []bool { 79 | var count: c_int = 0; 80 | const cButtons = c.glfwGetJoystickButtons(self.id, &count); 81 | var cButtonsBool: [15]bool = undefined; 82 | 83 | var i: usize = 0; 84 | while (i < count) { 85 | cButtonsBool[i] = cButtons[i] == c.GLFW_PRESS; 86 | i += 1; 87 | } 88 | 89 | return cButtonsBool[0..@intCast(usize, count)]; 90 | } 91 | 92 | pub fn getAxes(self: *const Joystick) []const f32 { 93 | if (self.isGamepad) { 94 | var state: c.GLFWgamepadstate = undefined; 95 | _ = c.glfwGetGamepadState(self.id, &state); 96 | return state.axes[0..6]; 97 | } else { 98 | return self.getRawAxes(); 99 | } 100 | } 101 | 102 | pub fn isButtonDown(self: *const Joystick, btn: ButtonType) bool { 103 | const buttons = self.getButtons(); 104 | return buttons[@enumToInt(btn)]; 105 | } 106 | 107 | pub fn getButtons(self: *const Joystick) []bool { 108 | if (self.isGamepad) { 109 | var state: c.GLFWgamepadstate = undefined; 110 | _ = c.glfwGetGamepadState(self.id, &state); 111 | var buttons: [15]bool = undefined; 112 | for (state.buttons[0..15]) |value, i| { 113 | buttons[i] = value == c.GLFW_PRESS; 114 | } 115 | return buttons[0..]; 116 | } else { 117 | return self.getRawButtons(); 118 | } 119 | } 120 | }; 121 | 122 | fn init(self: *const Input) void { 123 | c.glfwSetInputMode(self.nativeId, c.GLFW_STICKY_MOUSE_BUTTONS, c.GLFW_TRUE); 124 | } 125 | 126 | /// Returns true if the key is currently being pressed. 127 | pub fn isKeyDown(self: *const Input, key: u32) bool { 128 | return c.glfwGetKey(self.nativeId, @intCast(c_int, key)) == c.GLFW_PRESS; 129 | } 130 | 131 | pub fn getJoystick(self: *const Input, id: u4) ?Joystick { 132 | _ = self; 133 | if (c.glfwJoystickPresent(@intCast(c_int, id)) == c.GLFW_FALSE) { 134 | return null; 135 | } else { 136 | const gamepad = c.glfwJoystickIsGamepad(id) == c.GLFW_TRUE; 137 | const cName = if (gamepad) c.glfwGetJoystickName(id) else c.glfwGetGamepadName(id); 138 | var i: usize = 0; 139 | while (true) { 140 | if (cName[i] == 0) break; 141 | i += 1; 142 | } 143 | const name = cName[0..i]; 144 | 145 | return Joystick { 146 | .id = id, 147 | .name = name, 148 | .isGamepad = gamepad 149 | }; 150 | } 151 | } 152 | 153 | pub fn isMouseButtonDown(self: *const Input, button: MouseButton) bool { 154 | var glfwButton: c_int = 0; 155 | switch (button) { 156 | .Left => glfwButton = c.GLFW_MOUSE_BUTTON_LEFT, 157 | .Middle => glfwButton = c.GLFW_MOUSE_BUTTON_MIDDLE, 158 | .Right => glfwButton = c.GLFW_MOUSE_BUTTON_RIGHT 159 | } 160 | return c.glfwGetMouseButton(self.nativeId, glfwButton) == c.GLFW_PRESS; 161 | } 162 | 163 | pub fn getMousePosition(self: *const Input) Vec2 { 164 | var xpos: f64 = 0; 165 | var ypos: f64 = 0; 166 | c.glfwGetCursorPos(self.nativeId, &xpos, &ypos); 167 | return Vec2.new(@floatCast(f32, xpos), @floatCast(f32, ypos)); 168 | } 169 | 170 | /// Set the input mode of the mouse. 171 | /// This allows to grab, hide or reset to normal the cursor. 172 | pub fn setMouseInputMode(self: *const Input, mode: MouseInputMode) void { 173 | var glfwMode: c_int = 0; 174 | switch (mode) { 175 | .Normal => glfwMode = c.GLFW_CURSOR_NORMAL, 176 | .Hidden => glfwMode = c.GLFW_CURSOR_HIDDEN, 177 | .Grabbed => glfwMode = c.GLFW_CURSOR_DISABLED 178 | } 179 | c.glfwSetInputMode(self.nativeId, c.GLFW_CURSOR, glfwMode); 180 | } 181 | 182 | pub fn getMouseInputMode(self: *const Input) MouseInputMode { 183 | var mode: c_int = c.glfwGetInputMode(self.nativeId, c.GLFW_CURSOR); 184 | switch (mode) { 185 | c.GLFW_CURSOR_NORMAL => return .Normal, 186 | c.GLFW_CURSOR_HIDDEN => return .Hidden, 187 | c.GLFW_CURSOR_DISABLED => return .Grabbed, 188 | else => { 189 | // this cannot happen 190 | return .Normal; 191 | } 192 | } 193 | } 194 | 195 | pub fn update(self: *Input) void { 196 | const pos = self.getMousePosition(); 197 | if (self.firstFrame) { 198 | self.lastMousePos = pos; 199 | self.firstFrame = false; 200 | } 201 | self.mouseDelta = pos.sub(self.lastMousePos); 202 | self.lastMousePos = pos; 203 | } 204 | }; 205 | 206 | pub const Window = struct { 207 | nativeId: *c.GLFWwindow, 208 | /// The input context of the window 209 | input: Input, 210 | 211 | /// Create a new window 212 | /// By default, the window will be resizable, with empty title and a size of 800x600. 213 | pub fn create() !Window { 214 | if (c.glfwInit() != 1) { 215 | std.debug.warn("Could not init GLFW!\n", .{}); 216 | return WindowError.InitializationError; 217 | } 218 | c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MAJOR, 3); 219 | c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MINOR, 2); 220 | c.glfwWindowHint(c.GLFW_OPENGL_PROFILE, c.GLFW_OPENGL_CORE_PROFILE); 221 | c.glfwWindowHint(c.GLFW_RESIZABLE, c.GLFW_TRUE); 222 | 223 | const window = c.glfwCreateWindow(800, 600, "", null, null) orelse { 224 | std.debug.warn("Could not create GLFW window!\n", .{}); 225 | return WindowError.InitializationError; 226 | }; 227 | c.glfwMakeContextCurrent(window); 228 | c.glfwSwapInterval(1); 229 | return Window { 230 | .nativeId = window, 231 | .input = .{ 232 | .nativeId = window 233 | } 234 | }; 235 | } 236 | 237 | pub fn setSize(self: *const Window, width: u32, height: u32) void { 238 | c.glfwSetWindowSize(self.nativeId, @intCast(c_int, width), @intCast(c_int, height)); 239 | } 240 | 241 | pub fn setPosition(self: *const Window, x: i32, y: i32) void { 242 | c.glfwSetWindowPos(self.nativeId, @intCast(c_int, x), @intCast(c_int, y)); 243 | } 244 | 245 | pub fn setTitle(self: *const Window, title: [:0]const u8) void { 246 | c.glfwSetWindowTitle(self.nativeId, title); 247 | } 248 | 249 | pub fn getPosition(self: *const Window) Vec2 { 250 | var x: i32 = 0; 251 | var y: i32 = 0; 252 | c.glfwGetWindowPos(self.nativeId, &y, &x); 253 | return Vec2.new(@intToFloat(f32, x), @intToFloat(f32, y)); 254 | } 255 | 256 | pub fn getSize(self: *const Window) Vec2 { 257 | var width: i32 = 0; 258 | var height: i32 = 0; 259 | c.glfwGetWindowSize(self.nativeId, &width, &height); 260 | return Vec2.new(@intToFloat(f32, width), @intToFloat(f32, height)); 261 | } 262 | 263 | pub fn getFramebufferSize(self: *const Window) Vec2 { 264 | var width: i32 = 0; 265 | var height: i32 = 0; 266 | c.glfwGetFramebufferSize(self.nativeId, &width, &height); 267 | return Vec2.new(@intToFloat(f32, width), @intToFloat(f32, height)); 268 | } 269 | 270 | pub fn makeContextCurrent(self: *Window) void { 271 | c.glfwMakeContextCurrent(self.nativeId); 272 | } 273 | 274 | pub fn setMain(self: *Window) void { 275 | mainWindow = self; 276 | } 277 | 278 | pub fn setShouldClose(self: *Window, value: bool) void { 279 | c.glfwSetWindowShouldClose(self.nativeId, if (value) 1 else 0); 280 | } 281 | 282 | /// Poll events, swap buffer and update input. 283 | /// Returns false if the window should be closed and true otherwises. 284 | pub fn update(self: *Window) bool { 285 | self.makeContextCurrent(); 286 | c.glfwSwapBuffers(self.nativeId); 287 | c.glfwPollEvents(); 288 | self.input.update(); 289 | return c.glfwWindowShouldClose(self.nativeId) == 0; 290 | } 291 | 292 | pub fn deinit(self: *Window) void { 293 | _ = self; 294 | c.glfwTerminate(); 295 | } 296 | 297 | }; 298 | 299 | var ctxLock: std.Thread.Mutex = .{}; 300 | 301 | /// Ensure the GL (or any other API) context is correctly set. 302 | /// This method should be called after and inside anything that is async. 303 | pub fn windowContextLock() @TypeOf(ctxLock.impl).Held { 304 | if (mainWindow) |win| { 305 | win.makeContextCurrent(); 306 | } else { 307 | std.log.scoped(.didot).warn("Could not switch context, no main window!", .{}); 308 | } 309 | return ctxLock.acquire(); 310 | } 311 | 312 | comptime { 313 | std.testing.refAllDecls(Window); 314 | std.testing.refAllDecls(Input); 315 | } 316 | -------------------------------------------------------------------------------- /didot-image/bmp.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Image = @import("image.zig").Image; 3 | const Allocator = std.mem.Allocator; 4 | 5 | const BmpError = error { 6 | InvalidHeader, 7 | InvalidCompression, 8 | UnsupportedFormat 9 | }; 10 | 11 | pub fn read(allocator: *Allocator, reader: anytype, seekable: anytype) !Image { 12 | const signature = try reader.readBytesNoEof(2); 13 | if (!std.mem.eql(u8, &signature, "BM")) { 14 | return BmpError.UnsupportedFormat; 15 | } 16 | 17 | const size = try reader.readIntLittle(u32); 18 | _ = size; 19 | _ = try reader.readBytesNoEof(4); // skip the reserved bytes 20 | const offset = try reader.readIntLittle(u32); 21 | const dibSize = try reader.readIntLittle(u32); 22 | 23 | if (dibSize == 40 or dibSize == 108) { // BITMAPV4HEADER 24 | const width = @intCast(usize, try reader.readIntLittle(i32)); 25 | const height = @intCast(usize, try reader.readIntLittle(i32)); 26 | const colorPlanes = try reader.readIntLittle(u16); 27 | const bpp = try reader.readIntLittle(u16); 28 | _ = colorPlanes; 29 | 30 | const compression = try reader.readIntLittle(u32); 31 | const imageSize = try reader.readIntLittle(u32); 32 | const horzRes = try reader.readIntLittle(i32); 33 | const vertRes = try reader.readIntLittle(i32); 34 | const colorsNum = try reader.readIntLittle(u32); 35 | const importantColors = try reader.readIntLittle(u32); 36 | _ = compression; _ = imageSize; _ = horzRes; _ = vertRes; _ = colorsNum; _ = importantColors; 37 | 38 | try seekable.seekTo(offset); 39 | const imgReader = (std.io.BufferedReader(16*1024, @TypeOf(reader)) { 40 | .unbuffered_reader = reader 41 | }).reader(); // the input is only buffered now as when seeking the file, the buffer isn't emptied 42 | const bytesPerPixel = @intCast(usize, bpp/8); 43 | var data = try allocator.alloc(u8, @intCast(usize, width*height*bytesPerPixel)); 44 | 45 | var i: usize = height-1; 46 | var j: usize = 0; 47 | const bytesPerLine = width * bytesPerPixel; 48 | 49 | if (bytesPerPixel == 1) { 50 | const skipAhead: usize = @mod(width, 4); 51 | while (i >= 0) { 52 | j = 0; 53 | while (j < width) { 54 | const pos = j + i*bytesPerLine; 55 | data[pos] = try imgReader.readByte(); 56 | j += 1; 57 | } 58 | try imgReader.skipBytes(skipAhead, .{}); 59 | if (i == 0) break; 60 | i -= 1; 61 | } 62 | return Image { 63 | .allocator = allocator, .data = data, 64 | .width = width, .height = height, 65 | .format = @import("image.zig").ImageFormat.GRAY8 66 | }; 67 | } else if (bytesPerPixel == 3) { 68 | const skipAhead: usize = @mod(width, 4); 69 | while (i >= 0) { 70 | const pos = i * bytesPerLine; 71 | _ = try imgReader.readAll(data[pos..(pos+bytesPerLine)]); 72 | try imgReader.skipBytes(skipAhead, .{}); 73 | if (i == 0) break; 74 | i -= 1; 75 | } 76 | return Image { 77 | .allocator = allocator, .data = data, 78 | .width = width, .height = height, 79 | .format = @import("image.zig").ImageFormat.BGR24 80 | }; 81 | } else { 82 | return BmpError.UnsupportedFormat; 83 | } 84 | } else { 85 | return BmpError.InvalidHeader; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /didot-image/image.zig: -------------------------------------------------------------------------------- 1 | pub const bmp = @import("bmp.zig"); 2 | pub const png = @import("png.zig"); 3 | const std = @import("std"); 4 | const Allocator = std.mem.Allocator; 5 | 6 | pub const ColorChannel = enum { 7 | Red, 8 | Green, 9 | Blue, 10 | Alpha 11 | }; 12 | 13 | //// A pixel format. Note that it only represents the sRGB color space. 14 | pub const ImageFormat = struct { 15 | /// Bit mask for the red color channel. 16 | /// Example: 0xFF000000 for RGBA. 17 | redMask: u32, 18 | /// Bit mask for the green color channel. 19 | /// Example: 0x00FF0000 for RGBA. 20 | greenMask: u32, 21 | /// Bit mask for the blue color channel. 22 | /// Example: 0x0000FF00 for RGBA. 23 | blueMask: u32, 24 | /// Bit mask for the alpha transparency channel. 25 | /// Example: 0x000000FF for RGBA. 26 | alphaMask: u32 = 0, 27 | /// The size, in bits, of one pixel. 28 | bitsSize: u8, 29 | 30 | /// 8-bit red, green and blue samples in that order. 31 | pub const RGB24 = ImageFormat {.redMask=0xFF0000, .greenMask=0x00FF00, .blueMask=0x0000FF, .bitsSize=24}; 32 | /// 8-bit blue, green and red samples in that order. 33 | pub const BGR24 = ImageFormat {.redMask=0x0000FF, .greenMask=0x00FF00, .blueMask=0xFF0000, .bitsSize=24}; 34 | /// 8-bit red, green, blue and alpha samples in that order. 35 | pub const RGBA32 = ImageFormat{.redMask=0xFF000000,.greenMask=0x00FF0000,.blueMask=0x0000FF00,.alphaMask=0x000000FF,.bitsSize=32}; 36 | /// 8-bit gray sample. 37 | pub const GRAY8 = ImageFormat {.redMask=0xFF, .greenMask=0xFF, .blueMask=0xFF, .bitsSize=8}; 38 | 39 | /// Get the bit size of the image format. 40 | pub fn getBitSize(self: *ImageFormat) u8 { 41 | return self.bitsSize; 42 | } 43 | 44 | /// Get the bit mask of the specified color channel. 45 | pub fn getBitMask(self: *ImageFormat, channel: ColorChannel) u32 { 46 | return switch (channel) { 47 | .Red => self.redMask, .Green => self.greenMask, 48 | .Blue => self.blueMask, .Alpha => self.alphaMask 49 | }; 50 | } 51 | 52 | pub const ShiftError = error { 53 | /// If the mask of the color channel is 0, 54 | /// no shift can be found and this error is returned. 55 | NullMask 56 | }; 57 | 58 | /// Returns how many bits must be shifted to the right in order to get the specified color channel value. 59 | /// Example: for ColorChannel.Red, this functions returns 24 if the image format is RGBA as you must 60 | /// shift a pixel 24 bits to the right to get the red color. 61 | pub fn getShift(self: *ImageFormat, channel: ColorChannel) ShiftError!u5 { 62 | // Example: 63 | // The mask of the red color is 0b111000 64 | // We shift right one time and get 0b011100, last bit is 0 so continue 65 | // We shift right another time and get 0b001110, last bit is also 0 so continue 66 | // We shift right a 3rd time and get 0b000111, the last bit is 1 and our shift is correct. 67 | // So we now know that a color value has to be shifted 3 times to get the red color. 68 | 69 | const mask = self.getBitMask(channel); 70 | var shift: u8 = 0; 71 | while (shift < self.bitsSize) : (shift += 1) { 72 | const num = mask >> @intCast(u5, shift); 73 | if ((num & 1) == 1) { // if we hit the first 1 bit of the mask 74 | return @intCast(u5, shift); 75 | } 76 | } 77 | return ShiftError.NullMask; 78 | } 79 | 80 | /// Using this image format, get the value corresponding to the color channel from a pixel. 81 | /// Example: Assuming RGBA image format and a pixel with value 0x11223344, if we use this function 82 | /// with the Red color channel, it will return 0x11. 83 | pub fn getValue(self: *ImageFormat, channel: ColorChannel, value: u32) !u32 { 84 | const mask = self.getBitMask(channel); 85 | const shift = try self.getShift(channel); 86 | return (value & mask) >> shift; 87 | } 88 | }; 89 | 90 | pub const Image = struct { 91 | allocator: ?*Allocator = null, 92 | /// The image data, in linear 8-bit RGB format. 93 | data: []u8, 94 | /// The width of the image in pixels. 95 | width: usize, 96 | /// The height of the image in pixels. 97 | height: usize, 98 | /// The pixel format of the image. 99 | format: ImageFormat, 100 | 101 | pub fn deinit(self: *const Image) void { 102 | if (self.allocator) |allocator| { 103 | allocator.free(self.data); 104 | } 105 | } 106 | }; 107 | 108 | comptime { 109 | @import("std").testing.refAllDecls(bmp); 110 | @import("std").testing.refAllDecls(png); 111 | @import("std").testing.refAllDecls(Image); 112 | @import("std").testing.refAllDecls(ImageFormat); 113 | } 114 | -------------------------------------------------------------------------------- /didot-image/png.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Image = @import("image.zig").Image; 3 | const Allocator = std.mem.Allocator; 4 | 5 | pub const PngError = error { 6 | InvalidHeader, 7 | InvalidFilter, 8 | UnsupportedFormat 9 | }; 10 | 11 | const ChunkStream = std.io.FixedBufferStream([]u8); 12 | 13 | // PNG files are made of chunks which have this structure: 14 | const Chunk = struct { 15 | length: u32, 16 | type: []const u8, 17 | data: []const u8, 18 | stream: ChunkStream, 19 | crc: u32, 20 | allocator: *Allocator, 21 | 22 | pub fn deinit(self: *const Chunk) void { 23 | self.allocator.free(self.type); 24 | self.allocator.free(self.data); 25 | } 26 | 27 | // fancy Zig reflection for basically loading the chunk into a struct 28 | // for the experienced: this method is necessary instead of a simple @bitCast because of endianess, as 29 | // PNG uses big-endian. 30 | pub fn toStruct(self: *Chunk, comptime T: type) T { 31 | var result: T = undefined; 32 | var reader = self.stream.reader(); 33 | inline for (@typeInfo(T).Struct.fields) |field| { 34 | const fieldInfo = @typeInfo(field.field_type); 35 | switch (fieldInfo) { 36 | .Int => { 37 | const f = reader.readIntBig(field.field_type) catch unreachable; 38 | @field(result, field.name) = f; 39 | }, 40 | .Enum => |e| { 41 | const id = reader.readIntBig(e.tag_type) catch unreachable; 42 | @field(result, field.name) = @intToEnum(field.field_type, id); 43 | }, 44 | else => unreachable 45 | } 46 | } 47 | return result; 48 | } 49 | }; 50 | 51 | const ColorType = enum(u8) { 52 | Greyscale = 0, 53 | Truecolor = 2, 54 | IndexedColor = 3, 55 | GreyscaleAlpha = 4, 56 | TruecolorAlpha = 6 57 | }; 58 | 59 | const CompressionMethod = enum(u8) { 60 | Deflate = 0, 61 | }; 62 | 63 | // Struct for the IHDR chunk, which contains most of metadata about the image. 64 | const IHDR = struct { 65 | width: u32, 66 | height: u32, 67 | bitDepth: u8, 68 | colorType: ColorType, 69 | compressionMethod: CompressionMethod, 70 | filterMethod: u8, 71 | interlaceMethod: u8 72 | }; 73 | 74 | fn filterNone(_: []const u8, _: []u8, _: u32, _: usize, _: u8) callconv(.Inline) void { 75 | // line is already pre-filled with original data, so nothing to do 76 | } 77 | 78 | fn filterSub(_: []const u8, line: []u8, _: u32, _: usize, bytes: u8) callconv(.Inline) void { 79 | var pos: usize = bytes; 80 | while (pos < line.len) : (pos += 1) { 81 | line[pos] = line[pos] +% line[pos-bytes]; 82 | } 83 | } 84 | 85 | fn filterUp(image: []const u8, line: []u8, y: u32, start: usize, _: u8) callconv(.Inline) void { 86 | const width = line.len; 87 | if (y != 0) { 88 | var pos: usize = 0; 89 | while (pos < line.len) : (pos += 1) { 90 | line[pos] = line[pos] +% image[start+pos-width]; 91 | } 92 | } 93 | } 94 | 95 | fn filterAverage(image: []const u8, line: []u8, y: u32, start: usize, bytes: u8) callconv(.Inline) void { 96 | const width = line.len; 97 | var pos: usize = 0; 98 | while (pos < line.len) : (pos += 1) { 99 | var val: u9 = if (pos >= bytes) line[pos-bytes] else 0; 100 | if (y > 0) { 101 | val += image[pos+start-width]; // val = a + b 102 | } 103 | line[pos] = line[pos] +% @truncate(u8, val / 2); 104 | } 105 | } 106 | 107 | fn filterPaeth(image: []const u8, line: []u8, y: u32, start: usize, bytes: u8) callconv(.Inline) void { 108 | const width = line.len; 109 | var pos: usize = 0; 110 | while (pos < line.len) : (pos += 1) { 111 | const a: isize = if (pos >= bytes) line[pos-bytes] else 0; 112 | const b: isize = if (y > 0) image[pos+start-width] else 0; 113 | const c: isize = if (pos >= bytes and y > 0) image[pos+start-width-bytes] else 0; 114 | const p: isize = a + b - c; 115 | // the minimum value of p is -255, minus the maximum value of a/b/c, the minimum result is -510, so using unreachable is safe 116 | const pa = std.math.absInt(p - a) catch unreachable; 117 | const pb = std.math.absInt(p - b) catch unreachable; 118 | const pc = std.math.absInt(p - c) catch unreachable; 119 | 120 | if (pa <= pb and pa <= pc) { 121 | line[pos] = line[pos] +% @truncate(u8, @bitCast(usize, a)); 122 | } else if (pb <= pc) { 123 | line[pos] = line[pos] +% @truncate(u8, @bitCast(usize, b)); 124 | } else { 125 | line[pos] = line[pos] +% @truncate(u8, @bitCast(usize, c)); 126 | } 127 | } 128 | } 129 | 130 | fn readChunk(allocator: *Allocator, reader: anytype) !Chunk { 131 | const length = try reader.readIntBig(u32); 132 | var chunkType = try allocator.alloc(u8, 4); 133 | _ = try reader.readAll(chunkType); 134 | var data = try allocator.alloc(u8, length); 135 | _ = try reader.readAll(data); 136 | 137 | const crc = try reader.readIntBig(u32); 138 | var stream = ChunkStream { 139 | .buffer = data, 140 | .pos = 0 141 | }; 142 | 143 | return Chunk { 144 | .length = length, 145 | .type = chunkType, 146 | .data = data, 147 | .stream = stream, 148 | .crc = crc, 149 | .allocator = allocator 150 | }; 151 | } 152 | 153 | pub fn read(allocator: *Allocator, unbufferedReader: anytype) !Image { 154 | var bufferedReader = std.io.BufferedReader(16*1024, @TypeOf(unbufferedReader)) { 155 | .unbuffered_reader = unbufferedReader 156 | }; 157 | const reader = bufferedReader.reader(); 158 | 159 | var signature = reader.readBytesNoEof(8) catch return error.UnsupportedFormat; 160 | if (!std.mem.eql(u8, signature[0..], "\x89PNG\r\n\x1A\n")) { 161 | return error.UnsupportedFormat; 162 | } 163 | 164 | var ihdrChunk = try readChunk(allocator, reader); 165 | defer ihdrChunk.deinit(); 166 | if (!std.mem.eql(u8, ihdrChunk.type, "IHDR")) { 167 | return error.InvalidHeader; // first chunk must ALWAYS be IHDR 168 | } 169 | const ihdr = ihdrChunk.toStruct(IHDR); 170 | 171 | if (ihdr.filterMethod != 0) { 172 | // there's only one filter method declared in the PNG specification 173 | // the error falls under InvalidHeader because InvalidFilter is for 174 | // the per-scanline filter type. 175 | return error.InvalidHeader; 176 | } 177 | 178 | var idatData = try allocator.alloc(u8, 0); 179 | defer allocator.free(idatData); 180 | 181 | while (true) { 182 | const chunk = try readChunk(allocator, reader); 183 | defer chunk.deinit(); 184 | 185 | if (std.mem.eql(u8, chunk.type, "IEND")) { 186 | break; 187 | } else if (std.mem.eql(u8, chunk.type, "IDAT")) { // image data 188 | const pos = idatData.len; 189 | // in PNG files, there can be multiple IDAT chunks, and their data must all be concatenated. 190 | idatData = try allocator.realloc(idatData, idatData.len + chunk.data.len); 191 | std.mem.copy(u8, idatData[pos..], chunk.data); 192 | } 193 | } 194 | 195 | // the following lines create a zlib stream over our concatenated data from IDAT chunks. 196 | var idatStream = std.io.fixedBufferStream(idatData); 197 | var zlibStream = try std.compress.zlib.zlibStream(allocator, idatStream.reader()); 198 | defer zlibStream.deinit(); 199 | var zlibReader = zlibStream.reader(); 200 | var idatBuffer = (std.io.BufferedReader(64*1024, @TypeOf(zlibReader)) { 201 | .unbuffered_reader = zlibReader 202 | }); 203 | const idatReader = idatBuffer.reader(); 204 | 205 | // allocate image data (TODO: support more than RGB) 206 | var bpp: u32 = 3; 207 | if (ihdr.colorType == .TruecolorAlpha) { 208 | bpp = 4; 209 | } 210 | const imageData = try allocator.alloc(u8, ihdr.width*ihdr.height*bpp); 211 | var y: u32 = 0; 212 | 213 | const Filter = fn(image: []const u8, line: []u8, y: u32, start: usize, bytes: u8) callconv(.Inline) void; 214 | const filters = [_]Filter {filterNone, filterSub, filterUp, filterAverage, filterPaeth}; 215 | 216 | if (ihdr.colorType == .Truecolor) { 217 | const bytesPerLine = ihdr.width * bpp; 218 | 219 | while (y < ihdr.height) { 220 | // in PNG files, each scanlines have a filter, it is used to have more efficient compression. 221 | const filterType = try idatReader.readByte(); 222 | const offset = y*bytesPerLine; 223 | var line = imageData[offset..offset+bytesPerLine]; 224 | _ = try idatReader.readAll(line); 225 | 226 | if (filterType >= filters.len) { 227 | return error.InvalidFilter; 228 | } 229 | 230 | inline for (filters) |filter, i| { 231 | if (filterType == i) { 232 | filter(imageData, line, y, offset, 3); 233 | } 234 | } 235 | 236 | y += 1; 237 | } 238 | 239 | return Image { 240 | .allocator = allocator, 241 | .data = imageData, 242 | .width = ihdr.width, 243 | .height = ihdr.height, 244 | .format = @import("image.zig").ImageFormat.RGB24 245 | }; 246 | } else if (ihdr.colorType == .TruecolorAlpha and false) { 247 | const bytesPerLine = ihdr.width * bpp; 248 | var line = try allocator.alloc(u8, bytesPerLine); 249 | defer allocator.free(line); 250 | while (y < ihdr.height) { 251 | const filterType = try idatReader.readByte(); 252 | const offset = y*bytesPerLine; 253 | _ = try idatReader.readAll(line); 254 | 255 | if (filterType >= filters.len) { 256 | return error.InvalidFilter; 257 | } 258 | inline for (filters) |filter, i| { 259 | if (filterType == i) { 260 | filter(imageData, line, y, offset, 4); 261 | std.mem.copy(u8, imageData[offset..offset+bytesPerLine], line); 262 | } 263 | } 264 | y += 1; 265 | } 266 | 267 | return Image { 268 | .allocator = allocator, 269 | .data = imageData, 270 | .width = ihdr.width, 271 | .height = ihdr.height, 272 | .format = @import("image.zig").ImageFormat.RGBA32 273 | }; 274 | } else { 275 | std.log.scoped(.didot).err("Unsupported PNG format: {}", .{ihdr.colorType}); 276 | return PngError.UnsupportedFormat; 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /didot-image/ppm.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Image = @import("image.zig").Image; 3 | const Allocator = std.mem.Allocator; 4 | 5 | pub fn read(allocator: *Allocator, unbufferedReader: anytype) !Image { 6 | var bufferedReader = std.io.BufferedReader(16*1024, @TypeOf(unbufferedReader)) { 7 | .unbuffered_reader = unbufferedReader 8 | }; 9 | const reader = bufferedReader.reader(); 10 | 11 | const signature = reader.readBytesNoEof(2) catch return error.UnsupportedFormat; 12 | const sizeLine = try reader.readUntilDelimiterAlloc(allocator, '\n', std.math.maxInt(usize)); 13 | const size = std.mem.split(sizeLine, " "); 14 | const width = std.fmt.parseUnsigned(usize, if (size.next()) |n| n else return error.UnsupportedFormat, 10); 15 | const height = std.fmt.parseUnsigned(usize, if (size.next()) |n| n else return error.UnsupportedFormat, 10); 16 | if (std.mem.eql(u8, signature, "P3")) { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /didot-models/models.zig: -------------------------------------------------------------------------------- 1 | pub const obj = @import("obj.zig"); 2 | const graphics = @import("didot-graphics"); 3 | const objects = @import("../didot-objects/assets.zig"); 4 | const std = @import("std"); 5 | const Allocator = std.mem.Allocator; 6 | const AssetStream = objects.AssetStream; 7 | const Asset = objects.Asset; 8 | 9 | pub const MeshAsset = struct { 10 | /// Memory is caller owned 11 | pub fn init(allocator: *Allocator, format: []const u8) !Asset { 12 | var data = try allocator.create(MeshAssetLoaderData); 13 | data.format = format; 14 | return Asset { 15 | .loader = meshAssetLoader, 16 | .loaderData = @ptrToInt(data), 17 | .objectType = .Mesh, 18 | .allocator = allocator 19 | }; 20 | } 21 | }; 22 | 23 | pub const MeshAssetLoaderData = struct { 24 | format: []const u8 25 | }; 26 | 27 | pub const MeshAssetLoaderError = error { 28 | InvalidFormat 29 | }; 30 | 31 | pub fn meshAssetLoader(allocator: *Allocator, dataPtr: usize, stream: *AssetStream) !usize { 32 | const data = @intToPtr(*MeshAssetLoaderData, dataPtr); 33 | if (std.mem.eql(u8, data.format, "obj")) { 34 | const mesh = try obj.read_obj(allocator, stream.reader()); 35 | var m = try allocator.create(graphics.Mesh); 36 | m.* = mesh; 37 | return @ptrToInt(m); 38 | } else { 39 | return MeshAssetLoaderError.InvalidFormat; 40 | } 41 | } 42 | 43 | comptime { 44 | @import("std").testing.refAllDecls(obj); 45 | } 46 | -------------------------------------------------------------------------------- /didot-models/obj.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const zalgebra = @import("zalgebra"); 3 | const graphics = @import("didot-graphics"); 4 | const Mesh = graphics.Mesh; 5 | const Allocator = std.mem.Allocator; 6 | const ArrayList = std.ArrayList; 7 | 8 | const Vec2 = zalgebra.Vec2; 9 | const Vec3 = zalgebra.Vec3; 10 | 11 | const OBJError = error { 12 | }; 13 | 14 | const Element = struct { 15 | posIdx: usize, 16 | texIdx: usize, 17 | normalIdx: usize 18 | }; 19 | 20 | pub fn read_obj(allocator: *Allocator, unbufferedReader: anytype) !Mesh { 21 | const reader = std.io.bufferedReader(unbufferedReader).reader(); 22 | 23 | var vertices = ArrayList(Vec3).init(allocator); 24 | var normals = ArrayList(Vec3).init(allocator); 25 | var texCoords = ArrayList(Vec2).init(allocator); 26 | var elements = ArrayList(Element).init(allocator); 27 | 28 | defer vertices.deinit(); 29 | defer elements.deinit(); 30 | defer normals.deinit(); 31 | defer texCoords.deinit(); 32 | 33 | const text = try reader.readAllAlloc(allocator, std.math.maxInt(u64)); 34 | defer allocator.free(text); 35 | var linesSplit = std.mem.split(u8, text, "\n"); 36 | 37 | while (true) { 38 | const line = if (linesSplit.next()) |s| s else break; 39 | var split = std.mem.split(u8, line, " "); 40 | const command = split.next().?; 41 | if (std.mem.eql(u8, command, "v")) { // vertex (position) 42 | const xStr = split.next().?; 43 | const yStr = split.next().?; 44 | const zStr = split.next().?; 45 | //var wStr = "1.0"; 46 | //if (split.next()) |w| { 47 | // wStr = w; 48 | //} 49 | 50 | const x = try std.fmt.parseFloat(f32, xStr); 51 | const y = try std.fmt.parseFloat(f32, yStr); 52 | const z = try std.fmt.parseFloat(f32, zStr); 53 | try vertices.append(Vec3.new(x, y, z)); 54 | } else if (std.mem.eql(u8, command, "vt")) { // vertex (texture coordinate) 55 | const uStr = split.next().?; 56 | const vStr = split.next().?; 57 | //const wStr = split.next().?; 58 | 59 | const u = try std.fmt.parseFloat(f32, uStr); 60 | const v = try std.fmt.parseFloat(f32, vStr); 61 | //const w = try std.fmt.parseFloat(f32, wStr); 62 | try texCoords.append(Vec2.new(u, v)); 63 | } else if (std.mem.eql(u8, command, "vn")) { // vertex (normal) 64 | const xStr = split.next().?; 65 | const yStr = split.next().?; 66 | const zStr = split.next().?; 67 | 68 | const x = try std.fmt.parseFloat(f32, xStr); 69 | const y = try std.fmt.parseFloat(f32, yStr); 70 | const z = try std.fmt.parseFloat(f32, zStr); 71 | try normals.append(Vec3.new(x, y, z)); 72 | } else if (std.mem.eql(u8, command, "f")) { // face 73 | while (true) { 74 | if (split.next()) |vertex| { 75 | var faceSplit = std.mem.split(u8, vertex, "/"); 76 | var posIdx = try std.fmt.parseInt(i32, faceSplit.next().?, 10); 77 | const texIdxStr = faceSplit.next().?; 78 | var texIdx = if (texIdxStr.len == 0) 0 else try std.fmt.parseInt(i32, texIdxStr, 10); 79 | const normalIdxStr = faceSplit.next(); 80 | var normalIdx = if (normalIdxStr) |str| try std.fmt.parseInt(i32, str, 10) else 0; 81 | if (normalIdx < 1) { 82 | normalIdx = 1; // TODO 83 | } 84 | if (texIdx < 1) { 85 | texIdx = 1; // TODO 86 | } 87 | if (posIdx < 1) { 88 | posIdx = 1; // TODO 89 | } 90 | try elements.append(.{ 91 | .posIdx = @intCast(usize, posIdx-1), 92 | .texIdx = @intCast(usize, texIdx-1), 93 | .normalIdx = @intCast(usize, normalIdx-1), 94 | }); 95 | } else { 96 | break; 97 | } 98 | } 99 | } else { 100 | //std.debug.warn("Unknown OBJ command: {}\n", .{command}); 101 | } 102 | } 103 | 104 | var final = try allocator.alloc(f32, elements.items.len*8); 105 | defer allocator.free(final); 106 | var i: usize = 0; 107 | for (elements.items) |f| { 108 | const v = vertices.items[f.posIdx]; 109 | const t = if (texCoords.items.len == 0) Vec2.zero() else texCoords.items[f.texIdx]; 110 | const n = if (normals.items.len == 0) Vec3.zero() else normals.items[f.normalIdx]; 111 | // position 112 | final[i] = v.x; 113 | final[i+1] = v.y; 114 | final[i+2] = v.z; 115 | // normal 116 | final[i+3] = n.x; 117 | final[i+4] = n.y; 118 | final[i+5] = n.z; 119 | // texture coordinate 120 | final[i+6] = t.x; 121 | final[i+7] = t.y; 122 | i = i + 8; 123 | } 124 | 125 | return Mesh.create(final, null); // TODO simplify 126 | } 127 | -------------------------------------------------------------------------------- /didot-objects/components.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | const objects = @import("objects.zig"); 4 | const GameObject = objects.GameObject; 5 | 6 | pub const Component = struct { 7 | data: usize, 8 | allocator: *Allocator, 9 | name: []const u8, 10 | gameObject: *GameObject = undefined, 11 | // TODO: index into a scene array of objects 12 | 13 | pub fn from(allocator: *Allocator, x: anytype) !Component { 14 | const T = @TypeOf(x); 15 | var copy = try allocator.create(T); 16 | copy.* = x; 17 | return Component { 18 | .data = if (@sizeOf(T) == 0) 0 else @ptrToInt(copy), 19 | .allocator = allocator, 20 | .name = @typeName(T), 21 | }; 22 | } 23 | 24 | /// Get the type name of this component. 25 | pub fn getName(self: *const Component) []const u8 { 26 | return self.name.*; 27 | } 28 | 29 | /// Get the data, as type T, holded in this component. 30 | pub fn getData(self: *const Component, comptime T: type) *T { 31 | if (@sizeOf(T) == 0) { 32 | return undefined; 33 | } else { 34 | return @intToPtr(*T, self.data); 35 | } 36 | } 37 | 38 | pub fn deinit(self: *Component) void { 39 | if (self.data != 0) { 40 | self.allocator.destroy(@intToPtr(*u8, self.data)); 41 | } 42 | } 43 | }; 44 | 45 | pub const ComponentOptions = struct { 46 | /// Functions called regularly depending on the updateTarget value of the Application. 47 | updateFn: ?fn(allocator: *Allocator, component: *Component, delta: f32) anyerror!void = null 48 | }; 49 | 50 | /// System selector to select objects where component T has just been created. 51 | pub fn Created(comptime T: type) type { 52 | _ = T; 53 | } 54 | 55 | /// System selector to select objects without component T, you should feed a struct type instead of a pointer to struct type as the 56 | /// pointer info (const or not const) is not used for a Without query. 57 | pub fn Without(comptime T: type) type { 58 | _ = T; 59 | } 60 | 61 | /// System selector to select objects with component T, you should feed a pointer to struct type as the 62 | /// pointer info (const or not const) is used for a With query. 63 | /// This is also the default system selector. 64 | pub fn With(comptime T: type) type { 65 | _ = T; 66 | return struct { 67 | const is_condition = true; 68 | 69 | pub fn include(go: GameObject) bool { 70 | _ = go; 71 | return true; 72 | } 73 | }; 74 | } 75 | 76 | fn getComponents(comptime parameters: anytype) []type { 77 | const info = @typeInfo(@TypeOf(parameters)).Struct; 78 | var types: [info.fields.len]type = undefined; 79 | 80 | for (info.fields) |field, i| { 81 | types[i] = @field(parameters, field.name); 82 | if (!std.meta.trait.isSingleItemPtr(types[i])) { 83 | @compileError("Invalid type " ++ @typeName(types[i]) ++ ", must be *" ++ @typeName(types[i]) ++ " or *const " ++ @typeName(types[i])); 84 | } 85 | } 86 | 87 | return &types; 88 | } 89 | 90 | // fn testSystem(pos: *Position, vel: *const Velocity); is used for single item systems 91 | // fn testSystem(ent: Query(.{*Mob}), obs: Query(.{*const Obstacle})) 92 | // fn testSystem(x: Query(.{Without(Velocity), With(*Position)})) without doesn't need a pointer 93 | 94 | // Example: Query(.{*Position, *const Velocity}) 95 | // You have to specify something like Query(.{*Position, *const Velocity, .sync=true}) to avoid this 96 | /// Queries are to be used for processing multiple components at a time 97 | pub fn Query(comptime parameters: anytype) type { 98 | const SystemQuery = struct { 99 | const Self = @This(); 100 | 101 | scene: *objects.Scene, 102 | 103 | const Result = blk: { 104 | const StructField = std.builtin.TypeInfo.StructField; 105 | 106 | var fields: []const StructField = &[0]StructField {}; 107 | const comps = getComponents(parameters); 108 | for (comps) |comp| { 109 | const name = @typeName(std.meta.Child(comp)); 110 | var newName: [name.len]u8 = undefined; 111 | newName = name.*; 112 | newName[0] = std.ascii.toLower(newName[0]); 113 | const field = StructField { 114 | .name = &newName, 115 | .field_type = comp, 116 | .default_value = null, 117 | .is_comptime = false, 118 | .alignment = @alignOf(comp) 119 | }; 120 | fields = fields ++ &[_]StructField {field}; 121 | } 122 | 123 | const info = std.builtin.TypeInfo { 124 | .Struct = .{ 125 | .layout = .Auto, 126 | .fields = fields, 127 | .decls = &[0]std.builtin.TypeInfo.Declaration {}, 128 | .is_tuple = false 129 | } 130 | }; 131 | break :blk @Type(info); 132 | }; 133 | 134 | const Iterator = struct { 135 | pos: usize = 0, 136 | query: *const Self, 137 | 138 | pub fn next(self: *Iterator) ?Result { 139 | const scene = self.query.scene; 140 | while (self.pos < scene.objects.items.len) : (self.pos += 1) { 141 | const obj = scene.objects.items[self.pos]; 142 | const comps = getComponents(parameters); 143 | var result: Result = undefined; 144 | 145 | // TODO: use filters 146 | var ok: bool = true; 147 | inline for (comps) |comp| { 148 | const name = @typeName(std.meta.Child(comp)); 149 | comptime var newName: [name.len]u8 = undefined; 150 | newName = name.*; 151 | newName[0] = comptime std.ascii.toLower(newName[0]); 152 | if (obj.getComponent(std.meta.Child(comp))) |cp| { 153 | @field(result, &newName) = cp; 154 | } else { 155 | ok = false; 156 | } 157 | } 158 | if (ok) { 159 | self.pos += 1; 160 | return result; 161 | } 162 | } 163 | 164 | return null; 165 | } 166 | }; 167 | 168 | pub fn iterator(self: *const @This()) Iterator { 169 | return Iterator { .query = self }; 170 | } 171 | 172 | pub fn parallelIterator(self: *const @This(), divides: usize) Iterator { 173 | _ = self; 174 | _ = divides; 175 | @compileError("TODO"); 176 | } 177 | 178 | }; 179 | return SystemQuery; 180 | } 181 | 182 | comptime { 183 | std.testing.refAllDecls(@This()); 184 | } -------------------------------------------------------------------------------- /didot-objects/main.zig: -------------------------------------------------------------------------------- 1 | pub usingnamespace @import("assets.zig"); 2 | pub usingnamespace @import("components.zig"); 3 | pub usingnamespace @import("objects.zig"); 4 | -------------------------------------------------------------------------------- /didot-ode/README.md: -------------------------------------------------------------------------------- 1 | ## Compiling 2 | 3 | ODE must be compiled with shared libraries enabled! 4 | -------------------------------------------------------------------------------- /didot-ode/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const ODE = "didot-ode/ode-0.16.2"; 4 | 5 | pub fn build(step: *std.build.LibExeObjStep) !void { 6 | // const b = step.builder; 7 | // const cwd = try std.process.getCwdAlloc(b.allocator); 8 | // const odePWD = try std.mem.concat(b.allocator, u8, &([_][]const u8{cwd, "/" ++ ODE})); 9 | // const odeBuild1 = b.addSystemCommand( 10 | // &[_][]const u8{"sh", ODE ++ "/configure", "--disable-demos", "--srcdir=" ++ ODE}); 11 | // odeBuild1.setEnvironmentVariable("PWD", odePWD); 12 | // odeBuild1.addPathDir(ODE); 13 | // // const odeBuild2 = b.addSystemCommand(&[_][]const u8{"make"}); 14 | // // odeBuild2.setEnvironmentVariable("PWD", odePWD); 15 | // step.step.dependOn(&odeBuild1.step); 16 | // step.step.dependOn(&odeBuild2.step); 17 | // const flags = [_][]const u8 {"-iquote", ODE}; 18 | // step.addCSourceFile(ODE ++ "/ode/src/array.cpp", &flags); 19 | step.addIncludeDir(ODE ++ "/include"); 20 | //step.addLibPath(ODE ++ "/ode/src/.libs/"); 21 | step.linkSystemLibraryName("ode"); 22 | //step.linkSystemLibraryName("stdc++"); 23 | } 24 | -------------------------------------------------------------------------------- /didot-ode/physics.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const zalgebra = @import("zalgebra"); 3 | const objects = @import("didot-objects"); 4 | const c = @cImport({ 5 | @cDefine("dSINGLE", "1"); 6 | @cInclude("ode/ode.h"); 7 | }); 8 | 9 | const Allocator = std.mem.Allocator; 10 | const Component = objects.Component; 11 | const GameObject = objects.GameObject; 12 | const Transform = objects.Transform; 13 | 14 | const Vec3 = zalgebra.Vec3; 15 | 16 | var isOdeInit: bool = false; 17 | const logger = std.log.scoped(.didot); 18 | 19 | var odeThread: ?std.Thread.Id = null; 20 | 21 | fn ensureInit() void { 22 | if (!isOdeInit) { 23 | _ = c.dInitODE2(0); 24 | const conf = @ptrCast([*:0]const u8, c.dGetConfiguration()); 25 | logger.debug("Initialized ODE.", .{}); 26 | logger.debug("ODE configuration: {s}", .{conf}); 27 | isOdeInit = true; 28 | } 29 | } 30 | 31 | /// A physical world in which the physics simulation happen. 32 | pub const World = struct { 33 | /// Internal value (ODE dWorldID) 34 | id: c.dWorldID, 35 | /// Internal value (ODE dSpaceID) 36 | space: c.dSpaceID, 37 | /// Internal value (ODE dJointGroupID) 38 | /// Used internally as a group for contact joints. 39 | contactGroup: c.dJointGroupID, 40 | /// The time, in milliseconds, when the last step was executed 41 | lastStep: i64, 42 | /// The lag-behind of the physics simulation measured in seconds. 43 | /// The maximum value is set to 0.1 seconds. 44 | accumulatedStep: f64 = 0.0, 45 | 46 | pub fn create() World { 47 | ensureInit(); 48 | return World { 49 | .id = c.dWorldCreate(), 50 | .space = c.dHashSpaceCreate(null), 51 | .contactGroup = c.dJointGroupCreate(0), 52 | .lastStep = std.time.milliTimestamp() 53 | }; 54 | } 55 | 56 | export fn nearCallback(data: ?*c_void, o1: c.dGeomID, o2: c.dGeomID) void { 57 | const b1 = c.dGeomGetBody(o1); 58 | const b2 = c.dGeomGetBody(o2); 59 | const self = @ptrCast(*World, @alignCast(@alignOf(World), data.?)); 60 | 61 | const b1data = @ptrCast(*Rigidbody, @alignCast(@alignOf(Rigidbody), c.dBodyGetData(b1).?)); 62 | const b2data = @ptrCast(*Rigidbody, @alignCast(@alignOf(Rigidbody), c.dBodyGetData(b2).?)); 63 | 64 | const bounce: f32 = std.math.max(b1data.material.bounciness, b2data.material.bounciness); 65 | 66 | var contact: c.dContact = undefined; 67 | contact.surface.mode = c.dContactBounce | c.dContactMu2; 68 | contact.surface.mu = b1data.material.friction; 69 | contact.surface.mu2 = b2data.material.friction; 70 | contact.surface.rho = b1data.material.friction/10; 71 | contact.surface.rho2 = b2data.material.friction/10; 72 | contact.surface.bounce = bounce; 73 | contact.surface.bounce_vel = 1; 74 | contact.surface.soft_cfm = 0.1; 75 | const numc = c.dCollide(o1, o2, 1, &contact.geom, @sizeOf(c.dContact)); 76 | if (numc != 0) { 77 | const joint = c.dJointCreateContact(self.id, self.contactGroup, &contact); 78 | c.dJointAttach(joint, b1, b2); 79 | } 80 | } 81 | 82 | pub fn setGravity(self: *World, gravity: Vec3) void { 83 | c.dWorldSetGravity(self.id, gravity.x, gravity.y, gravity.z); 84 | c.dWorldSetAutoDisableFlag(self.id, 1); 85 | c.dWorldSetAutoDisableLinearThreshold(self.id, 0.1); 86 | c.dWorldSetAutoDisableAngularThreshold(self.id, 0.1); 87 | //c.dWorldSetERP(self.id, 0.1); 88 | //c.dWorldSetCFM(self.id, 0.0000); 89 | } 90 | 91 | pub fn update(self: *World) void { 92 | self.accumulatedStep += @intToFloat(f64, std.time.milliTimestamp())/1000 - @intToFloat(f64, self.lastStep)/1000; 93 | if (self.accumulatedStep > 0.1) self.accumulatedStep = 0.1; 94 | const timeStep = 0.01; 95 | while (self.accumulatedStep > timeStep) { 96 | c.dSpaceCollide(self.space, self, nearCallback); 97 | _ = c.dWorldStep(self.id, timeStep); 98 | c.dJointGroupEmpty(self.contactGroup); 99 | self.accumulatedStep -= timeStep; 100 | } 101 | self.lastStep = std.time.milliTimestamp(); 102 | } 103 | 104 | pub fn deinit(self: *const World) void { 105 | c.dWorldDestroy(self.id); 106 | } 107 | }; 108 | 109 | pub const KinematicState = enum { 110 | Dynamic, 111 | Kinematic 112 | }; 113 | 114 | pub const PhysicsMaterial = struct { 115 | bounciness: f32 = 0.0, 116 | friction: f32 = 10.0 117 | }; 118 | 119 | pub const SphereCollider = struct { 120 | radius: f32 = 0.5 121 | }; 122 | 123 | pub const BoxCollider = struct { 124 | size: Vec3 = Vec3.new(1, 1, 1) 125 | }; 126 | 127 | pub const Collider = union(enum) { 128 | Box: BoxCollider, 129 | Sphere: SphereCollider 130 | }; 131 | 132 | /// Rigidbody component. 133 | /// Add it to a GameObject for it to have physics with other rigidbodies. 134 | pub const Rigidbody = struct { 135 | /// Set by the Rigidbody component allowing it to know when to initialize internal values. 136 | inited: bool = false, 137 | /// The pointer to the World **MUST** be set. 138 | world: *World, 139 | kinematic: KinematicState = .Dynamic, 140 | transform: *Transform = undefined, 141 | material: PhysicsMaterial = .{}, 142 | collider: Collider = .{ .Box = .{} }, 143 | /// Internal value (ODE dBodyID) 144 | _body: c.dBodyID = undefined, 145 | /// Internal value (ODE dGeomID) 146 | _geom: c.dGeomID = undefined, 147 | /// Internal value (ODE dMass) 148 | _mass: c.dMass = undefined, 149 | 150 | pub fn addForce(self: *Rigidbody, force: Vec3) void { 151 | c.dBodyEnable(self._body); 152 | c.dBodyAddForce(self._body, force.x, force.y, force.z); 153 | } 154 | 155 | pub fn setPosition(self: *Rigidbody, position: Vec3) void { 156 | c.dBodySetPosition(self._body, position.x, position.y, position.z); 157 | self.transform.position = position; 158 | } 159 | }; 160 | 161 | pub fn rigidbodySystem(query: objects.Query(.{*Rigidbody, *Transform})) !void { 162 | var iterator = query.iterator(); 163 | while (iterator.next()) |o| { 164 | const data = o.rigidbody; 165 | const transform = o.transform; 166 | if (!data.inited) { // TODO: move to a system that uses the Created() filter 167 | data._body = c.dBodyCreate(data.world.id); 168 | 169 | // const scale = transform.scale; // not used? 170 | data._geom = switch (data.collider) { 171 | .Box => |box| c.dCreateBox(data.world.space, box.size.x, box.size.y, box.size.z), 172 | .Sphere => |sphere| c.dCreateSphere(data.world.space, sphere.radius) 173 | }; 174 | data.transform = transform; 175 | c.dMassSetBox(&data._mass, 1.0, 1.0, 1.0, 1.0); 176 | c.dGeomSetBody(data._geom, data._body); 177 | data.setPosition(transform.position); 178 | c.dBodySetData(data._body, data); 179 | c.dBodySetMass(data._body, &data._mass); 180 | //c.dBodySetDamping(data._body, 0.005, 0.005); 181 | data.inited = true; 182 | } 183 | if (c.dBodyIsEnabled(data._body) != 0) { 184 | switch (data.kinematic) { 185 | .Dynamic => c.dBodySetDynamic(data._body), 186 | .Kinematic => c.dBodySetKinematic(data._body) 187 | } 188 | const scale = transform.scale; 189 | switch (data.collider) { 190 | .Box => |box| { 191 | c.dGeomBoxSetLengths(data._geom, box.size.x * scale.x, box.size.y * scale.y, box.size.z * scale.z); 192 | }, 193 | .Sphere => |sphere| { 194 | // TODO: handle that 195 | _ = sphere; 196 | } 197 | } 198 | const pos = c.dBodyGetPosition(data._body); 199 | const raw = c.dBodyGetQuaternion(data._body); 200 | transform.rotation = zalgebra.Quat.new(raw[0], raw[1], raw[2], raw[3]); 201 | transform.position = Vec3.new(pos[0], pos[1], pos[2]); 202 | } 203 | } 204 | } 205 | 206 | comptime { 207 | std.testing.refAllDecls(@This()); 208 | std.testing.refAllDecls(Rigidbody); 209 | std.testing.refAllDecls(World); 210 | } 211 | -------------------------------------------------------------------------------- /didot-opengl/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(step: *std.build.LibExeObjStep) !void { 4 | step.linkSystemLibrary("GL"); 5 | } 6 | -------------------------------------------------------------------------------- /didot-opengl/c.zig: -------------------------------------------------------------------------------- 1 | const c = @cImport({ 2 | @cInclude("GL/gl.h"); 3 | }); 4 | pub usingnamespace c; 5 | 6 | pub extern fn glGenBuffers(n: c.GLsizei, buffers: [*c]c.GLuint) void; 7 | pub extern fn glGenVertexArrays(n: c.GLsizei, arrays: [*c]c.GLuint) void; 8 | pub extern fn glBindBuffer(target: c.GLenum, buffer: c.GLuint) void; 9 | pub extern fn glBufferData(target: c.GLenum, size: c.GLsizeiptr, data: ?*c_void, usage: c.GLenum) void; 10 | pub extern fn glCreateShader(shader: c.GLenum) c.GLuint; 11 | pub extern fn glShaderSource(shader: c.GLuint, count: c.GLsizei, string: *[:0]const c.GLchar, length: ?*c_int) void; 12 | pub extern fn glCompileShader(shader: c.GLuint) void; 13 | pub extern fn glCreateProgram() c.GLuint; 14 | pub extern fn glAttachShader(program: c.GLuint, shader: c.GLuint) void; 15 | pub extern fn glLinkProgram(program: c.GLuint) void; 16 | pub extern fn glUseProgram(program: c.GLuint) void; 17 | pub extern fn glGetAttribLocation(program: c.GLuint, name: [*:0]const c.GLchar) c.GLint; 18 | pub extern fn glBindFragDataLocation(program: c.GLuint, colorNumber: c.GLuint, name: [*:0]const c.GLchar) void; 19 | pub extern fn glVertexAttribPointer(index: c.GLuint, size: c.GLint, type: c.GLenum, normalized: c.GLboolean, stride: c.GLsizei, offset: c.GLint) void; 20 | pub extern fn glBindVertexArray(array: c.GLuint) void; 21 | pub extern fn glGetShaderiv(shader: c.GLuint, pname: c.GLenum, params: *c.GLint) void; 22 | pub extern fn glEnableVertexAttribArray(index: c.GLuint) void; 23 | pub extern fn glGetShaderInfoLog(shader: c.GLuint, maxLength: c.GLsizei, length: *c.GLsizei, infoLog: [*]c.GLchar) void; 24 | pub extern fn glGetUniformLocation(shader: c.GLuint, name: [*:0]const c.GLchar) c.GLint; 25 | pub extern fn glUniform1i(location: c.GLint, v0: c.GLint) void; 26 | pub extern fn glUniform1f(location: c.GLint, v0: c.GLfloat) void; 27 | pub extern fn glUniform3f(location: c.GLint, v0: c.GLfloat, v1: c.GLfloat, v2: c.GLfloat) void; 28 | pub extern fn glUniformMatrix4fv(location: c.GLint, count: c.GLsizei, transpose: c.GLboolean, value: *const c.GLfloat) void; 29 | -------------------------------------------------------------------------------- /didot-opengl/main.zig: -------------------------------------------------------------------------------- 1 | pub usingnamespace @import("graphics.zig"); 2 | pub usingnamespace @import("didot-window"); 3 | -------------------------------------------------------------------------------- /didot-webgl/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(step: *std.build.LibExeObjStep) !void { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /didot-webgl/js.zig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenith391/didot/a096869f934bb1005c1122c40029eafa51e76e58/didot-webgl/js.zig -------------------------------------------------------------------------------- /didot-x11/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const EngineConfig = @import("../build.zig").EngineConfig; 3 | 4 | pub fn build(step: *std.build.LibExeObjStep, comptime config: EngineConfig) !?std.build.Pkg { 5 | step.linkSystemLibrary("X11"); 6 | step.linkLibC(); 7 | return null; 8 | } 9 | -------------------------------------------------------------------------------- /didot-x11/c.zig: -------------------------------------------------------------------------------- 1 | //! Manually redefined bindings for X11 (as the generated one are bugged) 2 | const c = @cImport({ 3 | @cInclude("X11/Xlib.h"); 4 | @cInclude("GL/glx.h"); 5 | }); 6 | const std = @import("std"); 7 | 8 | pub const Screen = c.Screen; 9 | pub const _XPrivDisplay = c._XPrivDisplay; 10 | pub const XSetWindowAttributes = c.XSetWindowAttributes; 11 | pub const XWindowAttributes = c.XWindowAttributes; 12 | pub const Window = c.Window; 13 | pub const Display = c.Display; 14 | pub const XEvent = c.XEvent; 15 | pub const XKeyEvent = c.XKeyEvent; 16 | pub const Colormap = c.Colormap; 17 | pub const Visual = c.Visual; 18 | pub const KeySym = c.KeySym; 19 | 20 | pub const CWBackPixmap = @as(c_long, 1) << 0; 21 | pub const CWBackPixel = @as(c_long, 1) << 1; 22 | pub const CWBorderPixmap = @as(c_long, 1) << 2; 23 | pub const CWBorderPixel = @as(c_long, 1) << 3; 24 | pub const CWBitGravity = @as(c_long, 1) << 4; 25 | pub const CWWinGravity = @as(c_long, 1) << 5; 26 | pub const CWBackingStore = @as(c_long, 1) << 6; 27 | pub const CWBackingPlanes = @as(c_long, 1) << 7; 28 | pub const CWBackingPixel = @as(c_long, 1) << 8; 29 | pub const CWOverrideRedirect = @as(c_long, 1) << 9; 30 | pub const CWSaveUnder = @as(c_long, 1) << 10; 31 | pub const CWEventMask = @as(c_long, 1) << 11; 32 | pub const CWDontPropagate = @as(c_long, 1) << 12; 33 | pub const CWColormap = @as(c_long, 1) << 13; 34 | pub const CWCursor = @as(c_long, 1) << 14; 35 | 36 | pub const KeyPressMask = @as(c_long, 1) << 0; 37 | pub const KeyReleaseMask = @as(c_long, 1) << 1; 38 | pub const ButtonPressMask = @as(c_long, 1) << 2; 39 | pub const ButtonReleaseMask = @as(c_long, 1) << 3; 40 | pub const EnterWindowMask = @as(c_long, 1) << 4; 41 | pub const LeaveWindowMask = @as(c_long, 1) << 5; 42 | pub const PointerMotionMask = @as(c_long, 1) << 6; 43 | pub const PointerMotionHintMask = @as(c_long, 1) << 7; 44 | pub const Button1MotionMask = @as(c_long, 1) << 8; 45 | pub const Button2MotionMask = @as(c_long, 1) << 9; 46 | pub const Button3MotionMask = @as(c_long, 1) << 10; 47 | pub const Button4MotionMask = @as(c_long, 1) << 11; 48 | pub const Button5MotionMask = @as(c_long, 1) << 12; 49 | pub const ButtonMotionMask = @as(c_long, 1) << 13; 50 | pub const KeymapStateMask = @as(c_long, 1) << 14; 51 | pub const ExposureMask = @as(c_long, 1) << 15; 52 | pub const VisibilityChangeMask = @as(c_long, 1) << 16; 53 | pub const StructureNotifyMask = @as(c_long, 1) << 17; 54 | pub const ResizeRedirectMask = @as(c_long, 1) << 18; 55 | pub const SubstructureNotifyMask = @as(c_long, 1) << 19; 56 | pub const SubstructureRedirectMask = @as(c_long, 1) << 20; 57 | pub const FocusChangeMask = @as(c_long, 1) << 21; 58 | pub const PropertyChangeMask = @as(c_long, 1) << 22; 59 | pub const ColormapChangeMask = @as(c_long, 1) << 23; 60 | pub const OwnerGrabButtonMask = @as(c_long, 1) << 24; 61 | 62 | pub const MotionNotify = c.MotionNotify; 63 | pub const Expose = c.Expose; 64 | pub const KeyPress = c.KeyPress; 65 | pub const KeyRelease = c.KeyRelease; 66 | 67 | pub const None = c.None; 68 | pub const CopyFromParent = c.CopyFromParent; 69 | pub const AllocNone = c.AllocNone; 70 | pub const NotUseful = c.NotUseful; 71 | pub const WhenMapped = c.WhenMapped; 72 | pub const Always = c.Always; 73 | pub const ForgetGravity = c.ForgetGravity; 74 | pub const NorthWestGravity = c.NorthWestGravity; 75 | pub const InputOutput = c.InputOutput; 76 | 77 | // GLX 78 | pub const GLint = c.GLint; 79 | pub const GLXContext = c.GLXContext; 80 | pub const GLXDrawable = c.GLXDrawable; 81 | pub const XVisualInfo = c.XVisualInfo; 82 | pub const GL_TRUE = c.GL_TRUE; 83 | pub const GLX_RGBA = c.GLX_RGBA; 84 | pub const GLX_DEPTH_SIZE = c.GLX_DEPTH_SIZE; 85 | pub const GLX_DOUBLEBUFFER = c.GLX_DOUBLEBUFFER; 86 | 87 | pub extern fn XOpenDisplay([*c]const u8) ?*Display; 88 | pub extern fn XCreateSimpleWindow(?*Display, Window, c_int, c_int, c_uint, c_uint, c_uint, c_ulong, c_ulong) Window; 89 | pub extern fn XMapWindow(?*Display, Window) c_int; 90 | pub extern fn XMoveWindow(?*Display, Window, c_int, c_int) c_int; 91 | pub extern fn XFlush(?*Display) c_int; 92 | pub extern fn XInitThreads() c_int; 93 | pub extern fn XCreateColormap(?*Display, Window, [*c]Visual, c_int) Colormap; 94 | pub extern fn XCreateWindow(?*Display, Window, c_int, c_int, c_uint, c_uint, c_uint, c_int, c_uint, [*c]Visual, c_ulong, [*c]XSetWindowAttributes) Window; 95 | pub extern fn XStoreName(?*Display, Window, [*c]const u8) c_int; 96 | pub extern fn XGetWindowAttributes(?*Display, Window, [*c]XWindowAttributes) c_int; 97 | pub extern fn XNextEvent(?*Display, [*c]XEvent) c_int; 98 | pub extern fn XPending(?*Display) c_int; 99 | pub extern fn XDestroyWindow(?*Display, Window) c_int; 100 | pub extern fn XLookupKeysym(?*XKeyEvent, c_int) KeySym; 101 | pub extern fn XKeysymToString(KeySym) [*:0]const u8; 102 | 103 | 104 | pub extern fn glXChooseVisual(dpy: ?*Display, screen: c_int, attribList: [*c]c_int) [*c]XVisualInfo; 105 | pub extern fn glXCreateContext(dpy: ?*Display, vis: [*c]XVisualInfo, shareList: GLXContext, direct: c_int) GLXContext; 106 | pub extern fn glXMakeCurrent(dpy: ?*Display, drawable: GLXDrawable, ctx: GLXContext) c_int; 107 | pub extern fn glXSwapBuffers(dpy: ?*Display, drawable: GLXDrawable) void; 108 | 109 | pub inline fn ScreenOfDisplay(dpy: anytype, scr: c_int) [*c]Screen { 110 | return &(std.meta.cast(_XPrivDisplay, dpy)).*.screens[@intCast(usize, scr)]; 111 | } 112 | 113 | pub inline fn DefaultRootWindow(dpy: anytype) Window { 114 | return ScreenOfDisplay(dpy, DefaultScreen(dpy)).*.root; 115 | } 116 | 117 | pub inline fn WhitePixel(dpy: anytype, scr: anytype) c_ulong { 118 | return ScreenOfDisplay(dpy, scr).*.white_pixel; 119 | } 120 | 121 | pub inline fn BlackPixel(dpy: anytype, scr: anytype) c_ulong { 122 | return ScreenOfDisplay(dpy, scr).*.black_pixel; 123 | } 124 | 125 | pub inline fn DefaultScreen(dpy: anytype) c_int { 126 | return (std.meta.cast(_XPrivDisplay, dpy)).*.default_screen; 127 | } 128 | -------------------------------------------------------------------------------- /didot-x11/window.zig: -------------------------------------------------------------------------------- 1 | // Deprecated: to be replaced with ZWL 2 | const c = @import("c.zig"); 3 | 4 | pub const WindowError = error { 5 | InitializationError 6 | }; 7 | 8 | const std = @import("std"); 9 | const zlm = @import("zlm"); 10 | const Vec2 = zlm.Vec2; 11 | 12 | // TODO: more inputs and a more efficient way to do them 13 | 14 | pub const Input = struct { 15 | mousePosition: Vec2 = Vec2.zero, 16 | mouseDelta: Vec2 = Vec2.zero, 17 | firstFrame: bool = true, 18 | 19 | pub const KEY_A = 0; 20 | pub const KEY_D = 0; 21 | pub const KEY_S = 0; 22 | pub const KEY_W = 0; 23 | 24 | pub const KEY_ESCAPE = 0; 25 | 26 | pub const KEY_UP = 0; 27 | pub const KEY_LEFT = 0; 28 | pub const KEY_RIGHT = 0; 29 | pub const KEY_DOWN = 0; 30 | 31 | pub const MouseInputMode = enum { 32 | Normal, 33 | Hidden, 34 | Grabbed 35 | }; 36 | 37 | pub const MouseButton = enum { 38 | Left, 39 | Middle, 40 | Right 41 | }; 42 | 43 | pub const Joystick = struct { 44 | id: u4, 45 | name: []const u8, 46 | /// This doesn't necessarily means the joystick *IS* a gamepad, this means it is registered in the DB. 47 | isGamepad: bool, 48 | 49 | pub const ButtonType = enum { 50 | A, 51 | B, 52 | X, 53 | Y, 54 | LeftBumper, 55 | RightBumper, 56 | Back, 57 | Start, 58 | Guide, 59 | LeftThumb, 60 | RightThumb, 61 | DPad_Up, 62 | DPad_Right, 63 | DPad_Down, 64 | DPad_Left 65 | }; 66 | 67 | pub fn getRawAxes(self: *const Joystick) []const f32 { 68 | unreachable; 69 | } 70 | 71 | pub fn getRawButtons(self: *const Joystick) []bool { 72 | unreachable; 73 | } 74 | 75 | pub fn getAxes(self: *const Joystick) []const f32 { 76 | unreachable; 77 | } 78 | 79 | pub fn isButtonDown(self: *const Joystick, btn: ButtonType) bool { 80 | unreachable; 81 | } 82 | 83 | pub fn getButtons(self: *const Joystick) []bool { 84 | unreachable; 85 | } 86 | }; 87 | 88 | fn init(self: *const Input) void { 89 | 90 | } 91 | 92 | /// Returns true if the key is currently being pressed. 93 | pub fn isKeyDown(self: *const Input, key: u32) bool { 94 | return false; 95 | } 96 | 97 | pub fn getJoystick(self: *const Input, id: u4) ?Joystick { 98 | return null; 99 | } 100 | 101 | pub fn isMouseButtonDown(self: *const Input, button: MouseButton) bool { 102 | return false; 103 | } 104 | 105 | pub fn getMousePosition(self: *const Input) Vec2 { 106 | return undefined; 107 | } 108 | 109 | /// Set the input mode of the mouse. 110 | /// This allows to grab, hide or reset to normal the cursor. 111 | pub fn setMouseInputMode(self: *const Input, mode: MouseInputMode) void { 112 | 113 | } 114 | 115 | pub fn getMouseInputMode(self: *const Input) MouseInputMode { 116 | return .Normal; 117 | } 118 | 119 | pub fn update(self: *Input) void { 120 | 121 | } 122 | }; 123 | 124 | pub const Window = struct { 125 | nativeDisplay: *c.Display, 126 | nativeId: c.Window, 127 | /// The input context of the window 128 | input: Input, 129 | glCtx: c.GLXContext, 130 | 131 | /// Create a new window 132 | /// By default, the window will be resizable, with empty title and a size of 800x600. 133 | pub fn create() !Window { 134 | if (!@import("builtin").single_threaded) { 135 | _ = c.XInitThreads(); 136 | } 137 | const dpy = c.XOpenDisplay(null) orelse return WindowError.InitializationError; 138 | const screen = c.DefaultScreen(dpy); 139 | const root = c.DefaultRootWindow(dpy); 140 | 141 | const window = try initGLX(dpy, root, screen); 142 | _ = c.XFlush(dpy); 143 | 144 | return Window { 145 | .nativeId = window, 146 | .nativeDisplay = dpy, 147 | .glCtx = undefined, 148 | .input = .{ 149 | 150 | } 151 | }; 152 | } 153 | 154 | // TODO: use EGL 155 | fn initGLX(dpy: *c.Display, root: c.Window, screen: c_int) !c.Window { 156 | var att = [_]c.GLint{c.GLX_RGBA, c.GLX_DEPTH_SIZE, 24, c.GLX_DOUBLEBUFFER, c.None}; 157 | const visual = c.glXChooseVisual(dpy, screen, &att[0]) orelse return WindowError.InitializationError; 158 | const colormap = c.XCreateColormap(dpy, root, visual.*.visual, c.AllocNone); 159 | var swa = c.XSetWindowAttributes { 160 | .background_pixmap = c.None, 161 | .background_pixel = 0, 162 | .border_pixmap = c.CopyFromParent, 163 | .border_pixel = 0, 164 | .bit_gravity = c.ForgetGravity, 165 | .win_gravity = c.NorthWestGravity, 166 | .backing_store = c.NotUseful, 167 | .backing_planes = 1, 168 | .backing_pixel = 0, 169 | .save_under = 0, 170 | .event_mask = c.ExposureMask | c.PointerMotionMask | c.KeyPressMask | c.KeyReleaseMask, 171 | .do_not_propagate_mask = 0, 172 | .override_redirect = 0, 173 | .colormap = colormap, 174 | .cursor = c.None 175 | }; 176 | const window = c.XCreateWindow(dpy, root, 0, 0, 177 | 800, 600, 0, visual.*.depth, c.InputOutput, 178 | visual.*.visual, c.CWColormap | c.CWEventMask, &swa); 179 | _ = c.XMapWindow(dpy, window); 180 | const ctx = c.glXCreateContext(dpy, visual, null, c.GL_TRUE); 181 | _ = c.glXMakeCurrent(dpy, window, ctx); 182 | return window; 183 | } 184 | 185 | pub fn setSize(self: *const Window, width: u32, height: u32) void { 186 | 187 | } 188 | 189 | pub fn setPosition(self: *const Window, x: i32, y: i32) void { 190 | _ = c.XMoveWindow(self.nativeDisplay, self.nativeId, @intCast(c_int, x), @intCast(c_int, y)); 191 | } 192 | 193 | pub fn setTitle(self: *const Window, title: [:0]const u8) void { 194 | _ = c.XStoreName(self.nativeDisplay, self.nativeId, title); 195 | } 196 | 197 | pub fn getPosition(self: *const Window) Vec2 { 198 | var wa: c.XWindowAttributes = undefined; 199 | _ = c.XGetWindowAttributes(self.nativeDisplay, self.nativeId, &wa); 200 | return Vec2.new(@intToFloat(f32, wa.x), @intToFloat(f32, wa.y)); 201 | } 202 | 203 | pub fn getSize(self: *const Window) Vec2 { 204 | var wa: c.XWindowAttributes = undefined; 205 | _ = c.XGetWindowAttributes(self.nativeDisplay, self.nativeId, &wa); 206 | return Vec2.new(@intToFloat(f32, wa.width), @intToFloat(f32, wa.height)); 207 | } 208 | 209 | pub fn getFramebufferSize(self: *const Window) Vec2 { 210 | var wa: c.XWindowAttributes = undefined; 211 | _ = c.XGetWindowAttributes(self.nativeDisplay, self.nativeId, &wa); 212 | return Vec2.new(@intToFloat(f32, wa.width), @intToFloat(f32, wa.height)); 213 | } 214 | 215 | /// Poll events, swap buffer and update input. 216 | /// Returns false if the window should be closed and true otherwises. 217 | pub fn update(self: *Window) bool { 218 | std.time.sleep(16*1000000); // TODO: vsync 219 | 220 | var pending = c.XPending(self.nativeDisplay); 221 | var event: c.XEvent = undefined; 222 | var i: c_int = 0; 223 | while (i < pending) { 224 | _ = c.XNextEvent(self.nativeDisplay, &event); 225 | switch (event.type) { 226 | c.Expose => { 227 | std.log.info("'Expose' event from X11.", .{}); 228 | }, 229 | c.MotionNotify => { 230 | //std.log.info("motion", .{}); 231 | }, 232 | c.KeyPress => { 233 | var keyEvent = event.xkey; 234 | const keySym = c.XLookupKeysym(&keyEvent, 0); 235 | const str = c.XKeysymToString(keySym); 236 | 237 | std.log.info("key press, keycode = {}, keysym = {}, state = {}, str = {}", .{keyEvent.keycode, keySym, keyEvent.state, str}); 238 | }, 239 | c.KeyRelease => { 240 | std.log.info("key release", .{}); 241 | }, 242 | else => std.log.scoped(.didot).warn("Got X11 event with type {}", .{event.type}) 243 | } 244 | i += 1; 245 | } 246 | 247 | c.glXSwapBuffers(self.nativeDisplay, self.nativeId); 248 | return true; 249 | } 250 | 251 | pub fn deinit(self: *Window) void { 252 | _ = c.glXMakeCurrent(self.nativeDisplay, c.None, null); 253 | _ = c.XDestroyWindow(self.nativeDisplay, self.nativeId); 254 | } 255 | 256 | }; 257 | 258 | test "" { 259 | comptime { 260 | std.meta.refAllDecls(Window); 261 | std.meta.refAllDecls(Input); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /didot-zwl/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const EngineConfig = @import("../build.zig").EngineConfig; 3 | 4 | pub fn build(step: *std.build.LibExeObjStep, comptime config: EngineConfig) !?std.build.Pkg { 5 | const zlm = std.build.Pkg { 6 | .name = "zlm", 7 | .path = config.prefix ++ "zlm/zlm.zig" 8 | }; 9 | 10 | const zwl = std.build.Pkg { 11 | .name = "zwl", 12 | .path = config.prefix ++ "didot-zwl/zwl/src/zwl.zig" 13 | }; 14 | 15 | step.linkSystemLibrary("X11"); // necessary until zwl's x11 backend support opengl 16 | step.linkSystemLibrary("c"); 17 | 18 | return std.build.Pkg { 19 | .name = "didot-window", 20 | .path = config.prefix ++ "didot-zwl/window.zig", 21 | .dependencies = &[_]std.build.Pkg{zlm, zwl} 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /didot-zwl/window.zig: -------------------------------------------------------------------------------- 1 | const zwl = @import("zwl/src/zwl.zig"); 2 | 3 | pub const WindowError = error { 4 | InitializationError 5 | }; 6 | 7 | const std = @import("std"); 8 | const zlm = @import("zlm"); 9 | const Vec2 = zlm.Vec2; 10 | 11 | // TODO: more inputs and a more efficient way to do them 12 | 13 | pub const Input = struct { 14 | nativeId: u32, 15 | lastMousePos: Vec2 = Vec2.zero, 16 | mouseDelta: Vec2 = Vec2.zero, 17 | firstFrame: bool = true, 18 | 19 | pub const KEY_A = 0; 20 | pub const KEY_D = 0; 21 | pub const KEY_S = 0; 22 | pub const KEY_W = 0; 23 | 24 | pub const KEY_ESCAPE = 0; 25 | pub const KEY_SPACE = 0; 26 | 27 | pub const KEY_UP = 0; 28 | pub const KEY_LEFT = 0; 29 | pub const KEY_RIGHT = 0; 30 | pub const KEY_DOWN = 0; 31 | 32 | pub const MouseInputMode = enum { 33 | Normal, 34 | Hidden, 35 | Grabbed 36 | }; 37 | 38 | pub const MouseButton = enum { 39 | Left, 40 | Middle, 41 | Right 42 | }; 43 | 44 | pub const Joystick = struct { 45 | id: u4, 46 | name: []const u8, 47 | /// This doesn't necessarily means the joystick *IS* a gamepad, this means it is registered in the DB. 48 | isGamepad: bool, 49 | 50 | pub const ButtonType = enum { 51 | A, 52 | B, 53 | X, 54 | Y, 55 | LeftBumper, 56 | RightBumper, 57 | Back, 58 | Start, 59 | Guide, 60 | LeftThumb, 61 | RightThumb, 62 | DPad_Up, 63 | DPad_Right, 64 | DPad_Down, 65 | DPad_Left 66 | }; 67 | 68 | pub fn getRawAxes(self: *const Joystick) []const f32 { 69 | var count: c_int = 0; 70 | const axes = c.glfwGetJoystickAxes(self.id, &count); 71 | return axes[0..@intCast(usize, count)]; 72 | } 73 | 74 | pub fn getRawButtons(self: *const Joystick) []bool { 75 | var count: c_int = 0; 76 | const cButtons = c.glfwGetJoystickButtons(self.id, &count); 77 | var cButtonsBool: [15]bool = undefined; 78 | 79 | var i: usize = 0; 80 | while (i < count) { 81 | cButtonsBool[i] = cButtons[i] == c.GLFW_PRESS; 82 | i += 1; 83 | } 84 | 85 | return cButtonsBool[0..@intCast(usize, count)]; 86 | } 87 | 88 | pub fn getAxes(self: *const Joystick) []const f32 { 89 | if (self.isGamepad) { 90 | var state: c.GLFWgamepadstate = undefined; 91 | _ = c.glfwGetGamepadState(self.id, &state); 92 | return state.axes[0..6]; 93 | } else { 94 | return self.getRawAxes(); 95 | } 96 | } 97 | 98 | pub fn isButtonDown(self: *const Joystick, btn: ButtonType) bool { 99 | const buttons = self.getButtons(); 100 | return buttons[@enumToInt(btn)]; 101 | } 102 | 103 | pub fn getButtons(self: *const Joystick) []bool { 104 | if (self.isGamepad) { 105 | var state: c.GLFWgamepadstate = undefined; 106 | _ = c.glfwGetGamepadState(self.id, &state); 107 | var buttons: [15]bool = undefined; 108 | for (state.buttons[0..15]) |value, i| { 109 | buttons[i] = value == c.GLFW_PRESS; 110 | } 111 | return buttons[0..]; 112 | } else { 113 | return self.getRawButtons(); 114 | } 115 | } 116 | }; 117 | 118 | fn init(self: *const Input) void { 119 | 120 | } 121 | 122 | /// Returns true if the key is currently being pressed. 123 | pub fn isKeyDown(self: *const Input, key: u32) bool { 124 | return false; 125 | } 126 | 127 | pub fn getJoystick(self: *const Input, id: u4) ?Joystick { 128 | return null; 129 | } 130 | 131 | pub fn isMouseButtonDown(self: *const Input, button: MouseButton) bool { 132 | return false; 133 | } 134 | 135 | pub fn getMousePosition(self: *const Input) Vec2 { 136 | return Vec2.new(0, 0); 137 | } 138 | 139 | /// Set the input mode of the mouse. 140 | /// This allows to grab, hide or reset to normal the cursor. 141 | pub fn setMouseInputMode(self: *const Input, mode: MouseInputMode) void { 142 | 143 | } 144 | 145 | pub fn getMouseInputMode(self: *const Input) MouseInputMode { 146 | return MouseInputMode.Normal; 147 | } 148 | 149 | pub fn update(self: *Input) void { 150 | 151 | } 152 | }; 153 | 154 | pub const Window = struct { 155 | const Platform = zwl.Platform(.{ 156 | .single_window = true, 157 | .backends_enabled = .{ 158 | .opengl = true 159 | }, 160 | .platforms_enabled = .{ 161 | .x11 = false, 162 | .xlib = true // temporary, for OpenGL support 163 | } 164 | }); 165 | 166 | /// The input context of the window 167 | input: Input, 168 | platform: *Platform, 169 | nativeId: *Platform.Window, 170 | 171 | /// Create a new window 172 | /// By default, the window will be resizable, with empty title and a size of 800x600. 173 | pub fn create() !Window { 174 | var platform = try Platform.init(std.heap.page_allocator, .{}); 175 | var window = try platform.createWindow(.{ 176 | .resizeable = true, 177 | .decorations = true, 178 | .track_keyboard = true, 179 | .track_mouse = true, 180 | .title = "", 181 | .backend = .{ 182 | .opengl = .{ 183 | .major = 3, .minor = 2 184 | } 185 | } 186 | }); 187 | 188 | return Window { 189 | .nativeId = window, 190 | .platform = platform, 191 | .input = .{ 192 | .nativeId = 0 193 | } 194 | }; 195 | } 196 | 197 | pub fn setSize(self: *const Window, width: u32, height: u32) void { 198 | var w = @floor(width); 199 | var h = @floor(height); 200 | if (w > std.math.maxInt(u16) or h > std.math.maxInt(u16)) { 201 | std.log.warn("Unable to set size to {d}x{d} : ZWL only supports up to a 65535x65535 window size, size will be set to 655535x65535", .{w, h}); 202 | w = @intToFloat(f32, std.math.maxInt(u16)); 203 | h = @intToFloat(f32, std.math.maxInt(u16)); 204 | } 205 | self.nativeId.configure(.{ 206 | .width = @floatToInt(u16, w), 207 | .height = @floatToInt(u16, h) 208 | }); 209 | } 210 | 211 | pub fn setPosition(self: *const Window, x: i32, y: i32) void { 212 | 213 | } 214 | 215 | pub fn setTitle(self: *const Window, title: [:0]const u8) void { 216 | self.nativeId.configure(.{ 217 | .title = title 218 | }) catch unreachable; // TODO: handle error on title change? 219 | } 220 | 221 | pub fn getPosition(self: *const Window) Vec2 { 222 | return Vec2.new(0, 0); 223 | } 224 | 225 | pub fn getSize(self: *const Window) Vec2 { 226 | return self.getFramebufferSize(); 227 | } 228 | 229 | pub fn getFramebufferSize(self: *const Window) Vec2 { 230 | var size = self.nativeId.getSize(); 231 | return Vec2.new(@intToFloat(f32, size[0]), @intToFloat(f32, size[1])); 232 | } 233 | 234 | /// Poll events, swap buffer and update input. 235 | /// Returns false if the window should be closed and true otherwises. 236 | pub fn update(self: *Window) bool { 237 | self.input.update(); 238 | self.nativeId.present() catch unreachable; 239 | return true; 240 | } 241 | 242 | pub fn deinit(self: *Window) void { 243 | self.nativeId.deinit(); 244 | self.platform.deinit(); 245 | } 246 | 247 | }; 248 | 249 | comptime { 250 | std.testing.refAllDecls(Window); 251 | std.testing.refAllDecls(Input); 252 | } 253 | -------------------------------------------------------------------------------- /didot-zwl/zwl/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | schedule: 9 | - cron: "0 5 * * *" # run at 5 AM UTC 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | platform: [ubuntu-latest, windows-latest] 17 | runs-on: ${{ matrix.platform }} 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | with: 22 | submodules: "recursive" 23 | 24 | - name: Setup Zig 25 | uses: goto-bus-stop/setup-zig@v1 26 | with: 27 | version: master 28 | 29 | - name: Compile softlogo 30 | run: zig build 31 | -------------------------------------------------------------------------------- /didot-zwl/zwl/.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | -------------------------------------------------------------------------------- /didot-zwl/zwl/README.md: -------------------------------------------------------------------------------- 1 | # ZWL 2 | A Zig Windowing Library 3 | -------------------------------------------------------------------------------- /didot-zwl/zwl/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Builder = std.build.Builder; 3 | 4 | pub fn build(b: *Builder) void { 5 | const target = b.standardTargetOptions(.{}); 6 | const mode = b.standardReleaseOptions(); 7 | 8 | const softlogo = b.addExecutable("softlogo", "examples/softlogo.zig"); 9 | softlogo.addPackagePath("zwl", "src/zwl.zig"); 10 | softlogo.single_threaded = true; 11 | softlogo.subsystem = .Windows; 12 | softlogo.setTarget(target); 13 | softlogo.setBuildMode(mode); 14 | softlogo.install(); 15 | 16 | const softlogo_run_cmd = softlogo.run(); 17 | const softlogo_run_step = b.step("run-softlogo", "Run the softlogo example"); 18 | softlogo_run_step.dependOn(&softlogo_run_cmd.step); 19 | } 20 | -------------------------------------------------------------------------------- /didot-zwl/zwl/examples/softlogo.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const zwl = @import("zwl"); 3 | 4 | const Platform = zwl.Platform(.{ 5 | .single_window = true, 6 | .backends_enabled = .{ 7 | .software = true, 8 | .opengl = false, 9 | .vulkan = false, 10 | }, 11 | .remote = true, 12 | .platforms_enabled = .{ 13 | .wayland = (std.builtin.os.tag != .windows), 14 | }, 15 | }); 16 | 17 | var stripes: [32][4]u8 = undefined; 18 | var logo: [70][200][4]u8 = undefined; 19 | pub const log_level = .info; 20 | 21 | pub fn main() !void { 22 | var platform = try Platform.init(std.heap.page_allocator, .{}); 23 | defer platform.deinit(); 24 | 25 | // init logo, stripes 26 | var seed_bytes: [@sizeOf(u64)]u8 = undefined; 27 | std.crypto.random.bytes(seed_bytes[0..]); 28 | var rng = std.rand.DefaultPrng.init(std.mem.readIntNative(u64, &seed_bytes)); 29 | for (stripes) |*stripe| { 30 | stripe.* = .{ @as(u8, rng.random.int(u6)) + 191, @as(u8, rng.random.int(u6)) + 191, @as(u8, rng.random.int(u6)) + 191, 0 }; 31 | } 32 | _ = try std.fs.cwd().readFile("logo.bgra", std.mem.asBytes(&logo)); 33 | 34 | var window = try platform.createWindow(.{ 35 | .title = "Softlogo", 36 | .width = 512, 37 | .height = 512, 38 | .resizeable = false, 39 | .visible = true, 40 | .decorations = true, 41 | .track_damage = false, 42 | .backend = .software, 43 | }); 44 | defer window.deinit(); 45 | 46 | { 47 | var pixbuf = try window.mapPixels(); 48 | paint(pixbuf); 49 | const updates = [_]zwl.UpdateArea{.{ .x = 0, .y = 0, .w = 128, .h = 128 }}; 50 | try window.submitPixels(&updates); 51 | } 52 | 53 | while (true) { 54 | const event = try platform.waitForEvent(); 55 | 56 | switch (event) { 57 | .WindowVBlank => |win| { 58 | var pixbuf = try win.mapPixels(); 59 | paint(pixbuf); 60 | const updates = [_]zwl.UpdateArea{.{ .x = 0, .y = 0, .w = pixbuf.width, .h = pixbuf.height }}; 61 | try win.submitPixels(&updates); 62 | }, 63 | .WindowResized => |win| { 64 | const size = win.getSize(); 65 | std.log.info("Window resized: {}x{}", .{ size[0], size[1] }); 66 | }, 67 | .WindowDestroyed => |win| { 68 | std.log.info("Window destroyed", .{}); 69 | return; 70 | }, 71 | .ApplicationTerminated => { // Can only happen on Windows 72 | return; 73 | }, 74 | else => {}, 75 | } 76 | } 77 | } 78 | 79 | fn paint(pixbuf: zwl.PixelBuffer) void { 80 | const ts = std.time.milliTimestamp(); 81 | const tsf = @intToFloat(f32, @intCast(usize, ts) % (60 * 1000000)); 82 | 83 | var y: usize = 0; 84 | while (y < pixbuf.height) : (y += 1) { 85 | var x: usize = 0; 86 | while (x < pixbuf.width) : (x += 1) { 87 | const fp = @intToFloat(f32, x * 2 + y / 2) * 0.01 + tsf * 0.005; 88 | const background = stripes[@floatToInt(u32, fp) % stripes.len]; 89 | 90 | const mid = [2]i32{ pixbuf.width >> 1, pixbuf.height >> 1 }; 91 | if (x < mid[0] - 100 or x >= mid[0] + 100 or y < mid[1] - 35 or y >= mid[1] + 35) { 92 | pixbuf.data[y * pixbuf.width + x] = @bitCast(u32, background); 93 | } else { 94 | const tx = @intCast(usize, @intCast(isize, x) - (mid[0] - 100)); 95 | const ty = @intCast(usize, @intCast(isize, y) - (mid[1] - 35)); 96 | const pix = logo[ty][tx]; 97 | const B = @intCast(u16, pix[0]) * pix[3] + @intCast(u16, background[0]) * (255 - pix[3]); 98 | const G = @intCast(u16, pix[1]) * pix[3] + @intCast(u16, background[1]) * (255 - pix[3]); 99 | const R = @intCast(u16, pix[2]) * pix[3] + @intCast(u16, background[2]) * (255 - pix[3]); 100 | pixbuf.data[y * pixbuf.width + x] = @bitCast(u32, [4]u8{ @intCast(u8, B >> 8), @intCast(u8, G >> 8), @intCast(u8, R >> 8), 0 }); 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /didot-zwl/zwl/logo.bgra: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenith391/didot/a096869f934bb1005c1122c40029eafa51e76e58/didot-zwl/zwl/logo.bgra -------------------------------------------------------------------------------- /didot-zwl/zwl/src/opengl.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const WINAPI = std.os.windows.WINAPI; 4 | 5 | const GLenum = c_uint; 6 | const GLboolean = u8; 7 | const GLbitfield = c_uint; 8 | const GLbyte = i8; 9 | const GLshort = c_short; 10 | const GLint = c_int; 11 | const GLsizei = c_int; 12 | const GLubyte = u8; 13 | const GLushort = c_ushort; 14 | const GLuint = c_uint; 15 | const GLfloat = f32; 16 | const GLclampf = f32; 17 | const GLdouble = f64; 18 | const GLclampd = f64; 19 | const GLvoid = void; 20 | 21 | pub extern "opengl32" fn glGetString( 22 | name: GLenum, 23 | ) callconv(WINAPI) [*:0]const u8; 24 | 25 | pub const GL_VENDOR = 0x1F00; 26 | pub const GL_RENDERER = 0x1F01; 27 | pub const GL_VERSION = 0x1F02; 28 | pub const GL_EXTENSIONS = 0x1F03; 29 | pub const GL_COLOR_BUFFER_BIT = 0x00004000; 30 | 31 | pub const GL_TRUE = 1; 32 | pub const GL_FALSE = 0; 33 | 34 | pub extern "opengl32" fn glClear(mask: GLbitfield) callconv(WINAPI) void; 35 | pub extern "opengl32" fn glClearColor(red: GLclampf, green: GLclampf, blue: GLclampf, alpha: GLclampf) callconv(WINAPI) void; 36 | -------------------------------------------------------------------------------- /didot-zwl/zwl/src/wayland.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const zwl = @import("zwl.zig"); 4 | const Allocator = std.mem.Allocator; 5 | 6 | pub fn Platform(comptime Parent: anytype) type { 7 | return struct { 8 | const Self = @This(); 9 | parent: Parent, 10 | file: std.fs.File, 11 | 12 | pub fn init(allocator: *Allocator, options: zwl.PlatformOptions) !*Parent { 13 | const file = try displayConnect(); 14 | errdefer file.close(); 15 | 16 | var self = try allocator.create(Self); 17 | errdefer allocator.destroy(self); 18 | 19 | self.* = .{ 20 | .parent = .{ 21 | .allocator = allocator, 22 | .type = .Wayland, 23 | .window = undefined, 24 | .windows = if (!Parent.settings.single_window) &[0]*Parent.Window{} else undefined, 25 | }, 26 | .file = file, 27 | }; 28 | 29 | std.log.scoped(.zwl).info("Platform Initialized: Wayland", .{}); 30 | return @ptrCast(*Parent, self); 31 | } 32 | 33 | pub fn deinit(self: *Self) void { 34 | self.file.close(); 35 | self.parent.allocator.destroy(self); 36 | } 37 | 38 | pub fn waitForEvent(self: *Self) !Parent.Event { 39 | return error.Unimplemented; 40 | } 41 | 42 | pub fn createWindow(self: *Self, options: zwl.WindowOptions) !*Parent.Window { 43 | var window = try self.parent.allocator.create(Window); 44 | errdefer self.parent.allocator.destroy(window); 45 | 46 | var wbuf = std.io.bufferedWriter(self.file.writer()); 47 | var writer = wbuf.writer(); 48 | try window.init(self, options, writer); 49 | // Extra settings and shit 50 | try wbuf.flush(); 51 | 52 | return @ptrCast(*Parent.Window, window); 53 | } 54 | 55 | const MessageId = enum(u16) { 56 | 57 | }; 58 | 59 | const Message = union(MessageId) { 60 | 61 | }; 62 | 63 | fn sendMessage(writer: anytype, objectId: u32, message: Message) !void { 64 | const T = @TypeOf(message); 65 | const tag = std.meta.activeTag(message); 66 | const size = @sizeOf(std.meta.TagPayloadType(Message, tag)); 67 | const opcode = @enumToInt(message); 68 | 69 | try writer.writeIntNative(u32, objectId); 70 | try writer.writeIntNative(u32, ((size + 8) << 16) | opcode); 71 | try writer.writeAll(std.mem.asBytes(&message)); 72 | } 73 | 74 | pub const Window = struct { 75 | parent: Parent.Window, 76 | width: u16, 77 | height: u16, 78 | 79 | pub fn init(self: *Window, parent: *Self, options: zwl.WindowOptions, writer: anytype) !void { 80 | self.* = .{ 81 | .parent = .{ 82 | .platform = @ptrCast(*Parent, parent), 83 | }, 84 | .width = options.width orelse 800, 85 | .height = options.height orelse 600, 86 | }; 87 | } 88 | pub fn deinit(self: *Window) void { 89 | // Do 90 | } 91 | 92 | pub fn configure(self: *Window, options: zwl.WindowOptions) !void { 93 | // Do 94 | } 95 | 96 | pub fn getSize(self: *Window) [2]u16 { 97 | return [2]u16{ self.width, self.height }; 98 | } 99 | 100 | pub fn mapPixels(self: *Window) !zwl.PixelBuffer { 101 | return error.Unimplemented; 102 | } 103 | 104 | pub fn submitPixels(self: *Window, pdates: []const zwl.UpdateArea) !void { 105 | return error.Unimplemented; 106 | } 107 | }; 108 | }; 109 | } 110 | 111 | fn displayConnect() !std.fs.File { 112 | const XDG_RUNTIME_DIR = if (std.os.getenv("XDG_RUNTIME_DIR")) |val| val else return error.NoXDGRuntimeDirSpecified; 113 | const WAYLAND_DISPLAY = if (std.os.getenv("WAYLAND_DISPLAY")) |val| val else "wayland-0"; 114 | 115 | var membuf: [256]u8 = undefined; 116 | var allocator = std.heap.FixedBufferAllocator.init(&membuf); 117 | const path = try std.mem.join(&allocator.allocator, "/", &[_][]const u8{ XDG_RUNTIME_DIR, WAYLAND_DISPLAY }); 118 | 119 | const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0; 120 | var socket = try std.os.socket(std.os.AF_UNIX, std.os.SOCK_STREAM | std.os.SOCK_CLOEXEC | opt_non_block, 0); 121 | errdefer std.os.close(socket); 122 | 123 | var addr = std.os.sockaddr_un{ .path = [_]u8{0} ** 108 }; 124 | std.mem.copy(u8, addr.path[0..], path); 125 | try std.os.connect(socket, @ptrCast(*const std.os.sockaddr, &addr), @sizeOf(std.os.sockaddr_un) - @intCast(u32, addr.path.len - path.len)); 126 | return std.fs.File{ .handle = socket }; 127 | } 128 | -------------------------------------------------------------------------------- /didot-zwl/zwl/src/windows/gdi32.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const windows = @import("../windows.zig").windows; 4 | usingnamespace @import("bits.zig"); 5 | 6 | pub const HBITMAP = *opaque { 7 | pub fn toGdiObject(self: HBITMAP) HGDIOBJ { 8 | return @ptrCast(HGDIOBJ, self); 9 | } 10 | }; 11 | pub const HGDIOBJ = *opaque {}; 12 | 13 | pub const Compression = extern enum { 14 | BI_RGB = 0x0000, 15 | BI_RLE8 = 0x0001, 16 | BI_RLE4 = 0x0002, 17 | BI_BITFIELDS = 0x0003, 18 | BI_JPEG = 0x0004, 19 | BI_PNG = 0x0005, 20 | BI_CMYK = 0x000B, 21 | BI_CMYKRLE8 = 0x000C, 22 | BI_CMYKRLE4 = 0x000D, 23 | }; 24 | 25 | pub const DIBColors = extern enum { 26 | DIB_RGB_COLORS = 0x00, 27 | DIB_PAL_COLORS = 0x01, 28 | DIB_PAL_INDICES = 0x02, 29 | }; 30 | 31 | pub const BITMAPINFOHEADER = extern struct { 32 | biSize: DWORD = @sizeOf(@This()), 33 | biWidth: LONG, 34 | biHeight: LONG, 35 | biPlanes: WORD, 36 | biBitCount: WORD, 37 | biCompression: DWORD, 38 | biSizeImage: DWORD, 39 | biXPelsPerMeter: LONG, 40 | biYPelsPerMeter: LONG, 41 | biClrUsed: DWORD, 42 | biClrImportant: DWORD, 43 | }; 44 | 45 | pub const RGBQUAD = extern struct { 46 | rgbBlue: BYTE, 47 | rgbGreen: BYTE, 48 | rgbRed: BYTE, 49 | rgbReserved: BYTE, 50 | }; 51 | 52 | pub const BITMAPINFO = extern struct { 53 | bmiHeader: BITMAPINFOHEADER, 54 | bmiColors: [1]RGBQUAD, // dynamic size... 55 | }; 56 | 57 | pub const TernaryRasterOperation = extern enum { 58 | SRCCOPY = 0x00CC0020, // dest = source 59 | SRCPAINT = 0x00EE0086, // dest = source OR dest 60 | SRCAND = 0x008800C6, // dest = source AND dest 61 | SRCINVERT = 0x00660046, // dest = source XOR dest 62 | SRCERASE = 0x00440328, // dest = source AND (NOT dest ) 63 | NOTSRCCOPY = 0x00330008, // dest = (NOT source) 64 | NOTSRCERASE = 0x001100A6, // dest = (NOT src) AND (NOT dest) 65 | MERGECOPY = 0x00C000CA, // dest = (source AND pattern) 66 | MERGEPAINT = 0x00BB0226, // dest = (NOT source) OR dest 67 | PATCOPY = 0x00F00021, // dest = pattern 68 | PATPAINT = 0x00FB0A09, // dest = DPSnoo 69 | PATINVERT = 0x005A0049, // dest = pattern XOR dest 70 | DSTINVERT = 0x00550009, // dest = (NOT dest) 71 | BLACKNESS = 0x00000042, // dest = BLACK 72 | WHITENESS = 0x00FF0062, // dest = WHITE 73 | }; 74 | 75 | pub const PIXELFORMATDESCRIPTOR = extern struct { 76 | nSize: WORD = @sizeOf(@This()), 77 | nVersion: WORD, 78 | dwFlags: DWORD, 79 | iPixelType: BYTE, 80 | cColorBits: BYTE, 81 | cRedBits: BYTE, 82 | cRedShift: BYTE, 83 | cGreenBits: BYTE, 84 | cGreenShift: BYTE, 85 | cBlueBits: BYTE, 86 | cBlueShift: BYTE, 87 | cAlphaBits: BYTE, 88 | cAlphaShift: BYTE, 89 | cAccumBits: BYTE, 90 | cAccumRedBits: BYTE, 91 | cAccumGreenBits: BYTE, 92 | cAccumBlueBits: BYTE, 93 | cAccumAlphaBits: BYTE, 94 | cDepthBits: BYTE, 95 | cStencilBits: BYTE, 96 | cAuxBuffers: BYTE, 97 | iLayerType: BYTE, 98 | bReserved: BYTE, 99 | dwLayerMask: DWORD, 100 | dwVisibleMask: DWORD, 101 | dwDamageMask: DWORD, 102 | }; 103 | 104 | pub const PFD_TYPE_RGBA = 0; 105 | pub const PFD_TYPE_COLORINDEX = 1; 106 | 107 | pub const PFD_MAIN_PLANE = 0; 108 | pub const PFD_OVERLAY_PLANE = 1; 109 | pub const PFD_UNDERLAY_PLANE = -1; 110 | 111 | pub const PFD_DOUBLEBUFFER = 0x00000001; 112 | pub const PFD_STEREO = 0x00000002; 113 | pub const PFD_DRAW_TO_WINDOW = 0x00000004; 114 | pub const PFD_DRAW_TO_BITMAP = 0x00000008; 115 | pub const PFD_SUPPORT_GDI = 0x00000010; 116 | pub const PFD_SUPPORT_OPENGL = 0x00000020; 117 | pub const PFD_GENERIC_FORMAT = 0x00000040; 118 | pub const PFD_NEED_PALETTE = 0x00000080; 119 | pub const PFD_NEED_SYSTEM_PALETTE = 0x00000100; 120 | pub const PFD_SWAP_EXCHANGE = 0x00000200; 121 | pub const PFD_SWAP_COPY = 0x00000400; 122 | pub const PFD_SWAP_LAYER_BUFFERS = 0x00000800; 123 | pub const PFD_GENERIC_ACCELERATED = 0x00001000; 124 | pub const PFD_SUPPORT_DIRECTDRAW = 0x00002000; 125 | pub const PFD_DIRECT3D_ACCELERATED = 0x00004000; 126 | pub const PFD_SUPPORT_COMPOSITION = 0x00008000; 127 | 128 | pub extern "gdi32" fn CreateDIBSection( 129 | hdc: HDC, 130 | pbmi: *const BITMAPINFO, 131 | usage: UINT, 132 | ppvBits: **c_void, 133 | hSection: ?HANDLE, 134 | offset: DWORD, 135 | ) callconv(WINAPI) ?HBITMAP; 136 | 137 | pub extern "gdi32" fn DeleteObject( 138 | bitmap: HGDIOBJ, 139 | ) callconv(WINAPI) BOOL; 140 | 141 | pub extern "gdi32" fn CreateCompatibleDC( 142 | hdc: ?HDC, 143 | ) callconv(WINAPI) ?HDC; 144 | 145 | pub extern "gdi32" fn SelectObject( 146 | hdc: HDC, 147 | h: HGDIOBJ, 148 | ) callconv(WINAPI) HGDIOBJ; 149 | 150 | pub extern "gdi32" fn BitBlt( 151 | hdc: HDC, 152 | x: c_int, 153 | y: c_int, 154 | cx: c_int, 155 | cy: c_int, 156 | hdcSrc: HDC, 157 | x1: c_int, 158 | y1: c_int, 159 | rop: DWORD, 160 | ) callconv(WINAPI) BOOL; 161 | 162 | pub extern "gdi32" fn DeleteDC( 163 | hdc: HDC, 164 | ) callconv(WINAPI) BOOL; 165 | 166 | pub extern "gdi32" fn TextOutA( 167 | hdc: HDC, 168 | x: c_int, 169 | y: c_int, 170 | lpString: [*:0]const u8, 171 | c: c_int, 172 | ) callconv(WINAPI) BOOL; 173 | 174 | pub extern "gdi32" fn wglCreateContext(hDC: HDC) callconv(WINAPI) ?HGLRC; 175 | 176 | pub extern "gdi32" fn wglDeleteContext(context: HGLRC) callconv(WINAPI) BOOL; 177 | 178 | pub extern "gdi32" fn wglMakeCurrent( 179 | hDC: HDC, 180 | gl_context: ?HGLRC, 181 | ) callconv(WINAPI) BOOL; 182 | 183 | pub extern "gdi32" fn SwapBuffers(hDC: HDC) callconv(WINAPI) BOOL; 184 | 185 | pub extern "gdi32" fn SetPixelFormat( 186 | hdc: HDC, 187 | format: c_int, 188 | ppfd: *const PIXELFORMATDESCRIPTOR, 189 | ) callconv(WINAPI) BOOL; 190 | 191 | pub extern "gdi32" fn ChoosePixelFormat( 192 | hdc: HDC, 193 | ppfd: *const PIXELFORMATDESCRIPTOR, 194 | ) c_int; 195 | 196 | pub extern "gdi32" fn wglGetProcAddress(entry_point: [*:0]const u8) callconv(WINAPI) ?*c_void; 197 | 198 | pub extern "gdi32" fn wglGetCurrentContext() callconv(WINAPI) ?HGLRC; 199 | 200 | pub const WGL_NUMBER_PIXEL_FORMATS_ARB = 0x2000; 201 | pub const WGL_DRAW_TO_WINDOW_ARB = 0x2001; 202 | pub const WGL_DRAW_TO_BITMAP_ARB = 0x2002; 203 | pub const WGL_ACCELERATION_ARB = 0x2003; 204 | pub const WGL_NEED_PALETTE_ARB = 0x2004; 205 | pub const WGL_NEED_SYSTEM_PALETTE_ARB = 0x2005; 206 | pub const WGL_SWAP_LAYER_BUFFERS_ARB = 0x2006; 207 | pub const WGL_SWAP_METHOD_ARB = 0x2007; 208 | pub const WGL_NUMBER_OVERLAYS_ARB = 0x2008; 209 | pub const WGL_NUMBER_UNDERLAYS_ARB = 0x2009; 210 | pub const WGL_TRANSPARENT_ARB = 0x200A; 211 | pub const WGL_TRANSPARENT_RED_VALUE_ARB = 0x2037; 212 | pub const WGL_TRANSPARENT_GREEN_VALUE_ARB = 0x2038; 213 | pub const WGL_TRANSPARENT_BLUE_VALUE_ARB = 0x2039; 214 | pub const WGL_TRANSPARENT_ALPHA_VALUE_ARB = 0x203A; 215 | pub const WGL_TRANSPARENT_INDEX_VALUE_ARB = 0x203B; 216 | pub const WGL_SHARE_DEPTH_ARB = 0x200C; 217 | pub const WGL_SHARE_STENCIL_ARB = 0x200D; 218 | pub const WGL_SHARE_ACCUM_ARB = 0x200E; 219 | pub const WGL_SUPPORT_GDI_ARB = 0x200F; 220 | pub const WGL_SUPPORT_OPENGL_ARB = 0x2010; 221 | pub const WGL_DOUBLE_BUFFER_ARB = 0x2011; 222 | pub const WGL_STEREO_ARB = 0x2012; 223 | pub const WGL_PIXEL_TYPE_ARB = 0x2013; 224 | pub const WGL_COLOR_BITS_ARB = 0x2014; 225 | pub const WGL_RED_BITS_ARB = 0x2015; 226 | pub const WGL_RED_SHIFT_ARB = 0x2016; 227 | pub const WGL_GREEN_BITS_ARB = 0x2017; 228 | pub const WGL_GREEN_SHIFT_ARB = 0x2018; 229 | pub const WGL_BLUE_BITS_ARB = 0x2019; 230 | pub const WGL_BLUE_SHIFT_ARB = 0x201A; 231 | pub const WGL_ALPHA_BITS_ARB = 0x201B; 232 | pub const WGL_ALPHA_SHIFT_ARB = 0x201C; 233 | pub const WGL_ACCUM_BITS_ARB = 0x201D; 234 | pub const WGL_ACCUM_RED_BITS_ARB = 0x201E; 235 | pub const WGL_ACCUM_GREEN_BITS_ARB = 0x201F; 236 | pub const WGL_ACCUM_BLUE_BITS_ARB = 0x2020; 237 | pub const WGL_ACCUM_ALPHA_BITS_ARB = 0x2021; 238 | pub const WGL_DEPTH_BITS_ARB = 0x2022; 239 | pub const WGL_STENCIL_BITS_ARB = 0x2023; 240 | pub const WGL_AUX_BUFFERS_ARB = 0x2024; 241 | pub const WGL_NO_ACCELERATION_ARB = 0x2025; 242 | pub const WGL_GENERIC_ACCELERATION_ARB = 0x2026; 243 | pub const WGL_FULL_ACCELERATION_ARB = 0x2027; 244 | pub const WGL_SWAP_EXCHANGE_ARB = 0x2028; 245 | pub const WGL_SWAP_COPY_ARB = 0x2029; 246 | pub const WGL_SWAP_UNDEFINED_ARB = 0x202A; 247 | pub const WGL_TYPE_RGBA_ARB = 0x202B; 248 | pub const WGL_TYPE_COLORINDEX_ARB = 0x202C; 249 | 250 | pub const WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; 251 | pub const WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; 252 | pub const WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; 253 | pub const WGL_CONTEXT_FLAGS_ARB = 0x2094; 254 | pub const WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; 255 | 256 | pub const WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; 257 | pub const WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; 258 | 259 | pub const WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; 260 | pub const WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; 261 | 262 | pub const ERROR_INVALID_VERSION_ARB = 0x2095; 263 | pub const ERROR_INVALID_PROFILE_ARB = 0x2096; 264 | -------------------------------------------------------------------------------- /didot-zwl/zwl/src/x11/auth.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | 4 | pub fn getCookie(path: ?[]const u8) ![16]u8 { 5 | const xauth_file = blk: { 6 | if (path) |p| { 7 | break :blk try std.fs.openFileAbsolute(p, .{ .read = true, .write = false }); 8 | } else if (builtin.os.tag == .windows) { 9 | const xauthority = std.os.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("XAUTHORITY")) orelse return error.XAuthorityNotSpecified; 10 | break :blk try std.fs.openFileAbsoluteW(xauthority, .{ .read = true, .write = false }); 11 | } else { 12 | if (std.os.getenv("XAUTHORITY")) |xafn| { 13 | break :blk try std.fs.openFileAbsolute(xafn, .{ .read = true, .write = false }); 14 | } 15 | const home = std.os.getenv("HOME") orelse return error.HomeDirectoryNotFound; 16 | var membuf: [256]u8 = undefined; 17 | var allocator = std.heap.FixedBufferAllocator.init(&membuf); 18 | const fpath = try std.mem.joinZ(&allocator.allocator, "/", &[_][]const u8{ home, ".Xauthority" }); 19 | break :blk try std.fs.openFileAbsoluteZ(fpath, .{ .read = true, .write = false }); 20 | } 21 | }; 22 | defer xauth_file.close(); 23 | 24 | var rbuf = std.io.bufferedReader(xauth_file.reader()); 25 | var reader = rbuf.reader(); 26 | 27 | while (true) { 28 | const family = reader.readIntBig(u16) catch break; 29 | 30 | const addr_len = try reader.readIntBig(u16); 31 | try reader.skipBytes(addr_len, .{ .buf_size = 64 }); 32 | 33 | const num_len = try reader.readIntBig(u16); 34 | try reader.skipBytes(num_len, .{ .buf_size = 64 }); 35 | 36 | const name_len = try reader.readIntBig(u16); 37 | if (name_len != 18) { 38 | try reader.skipBytes(name_len, .{ .buf_size = 64 }); 39 | const data_len = try reader.readIntBig(u16); 40 | try reader.skipBytes(data_len, .{ .buf_size = 64 }); 41 | continue; 42 | } 43 | 44 | var nbuf: [18]u8 = undefined; 45 | _ = try reader.readAll(nbuf[0..]); 46 | if (!std.mem.eql(u8, nbuf[0..], "MIT-MAGIC-COOKIE-1")) { 47 | const data_len = try reader.readIntBig(u16); 48 | try reader.skipBytes(data_len, .{ .buf_size = 64 }); 49 | continue; 50 | } 51 | 52 | const data_len = try reader.readIntBig(u16); 53 | if (data_len != 16) break; 54 | 55 | var xauth_data: [16]u8 = undefined; 56 | _ = try reader.readAll(xauth_data[0..]); 57 | return xauth_data; 58 | } 59 | return error.XauthorityCookieNotFound; 60 | } 61 | -------------------------------------------------------------------------------- /didot-zwl/zwl/src/x11/display_info.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | pub const DisplayInfo = struct { 6 | unix: bool, 7 | host: []const u8, 8 | display: u6, 9 | screen: u8, 10 | 11 | pub fn init(allocator: *Allocator, host_override: ?[]const u8, display_override: ?u6, screen_override: ?u8) !DisplayInfo { 12 | 13 | // If everything is fixed, just return it verbatim without bothering with reading DISPLAY 14 | if (host_override != null and display_override != null and screen_override != null) { 15 | return DisplayInfo{ 16 | .unix = if (@hasDecl(std.os, "sockaddr_un") and (std.mem.eql(u8, "localhost", host_override.?) or host_override.?.len == 0)) true else false, 17 | .host = host_override.?, 18 | .display = display_override.?, 19 | .screen = screen_override.?, 20 | }; 21 | } 22 | 23 | // Get the DISPLAY env variable 24 | const DISPLAY = blk: { 25 | if (builtin.os.tag == .windows) { 26 | const display_w = std.os.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("DISPLAY")) orelse break :blk ":0"; 27 | break :blk std.unicode.utf16leToUtf8Alloc(allocator, display_w[0..]) catch break :blk ":0"; 28 | } else { 29 | if (std.os.getenv("DISPLAY")) |d| 30 | break :blk d; 31 | } 32 | break :blk ":0"; 33 | }; 34 | 35 | // Parse the host, display, and screen 36 | const colon = std.mem.indexOfScalar(u8, DISPLAY, ':') orelse return error.MalformedDisplay; 37 | const host = if (host_override != null) host_override.? else DISPLAY[0..colon]; 38 | const unix = if (builtin.os.tag != .windows and @hasDecl(std.os, "sockaddr_un") and (std.mem.eql(u8, "localhost", host) or host.len == 0)) true else false; 39 | const dot = std.mem.indexOfScalar(u8, DISPLAY[colon..], '.'); 40 | if (dot != null and dot.? == 1) return error.MalformedDisplay; 41 | const display = if (display_override != null) display_override.? else if (dot != null) try std.fmt.parseUnsigned(u6, DISPLAY[colon + 1 .. colon + dot.?], 10) else try std.fmt.parseUnsigned(u6, DISPLAY[colon + 1 ..], 10); 42 | const screen = if (screen_override != null) screen_override.? else if (dot != null) try std.fmt.parseUnsigned(u8, DISPLAY[colon + dot.? + 1 ..], 10) else 0; 43 | 44 | var self = DisplayInfo{ 45 | .unix = unix, 46 | .host = host, 47 | .display = display, 48 | .screen = screen, 49 | }; 50 | return self; 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /didot.zig: -------------------------------------------------------------------------------- 1 | comptime { 2 | @import("std").testing.refAllDecls(@This()); 3 | } 4 | 5 | // this is very dirty code but is the only way for things to work correctly (i'll post issue on ziglang/zig later) 6 | pub const graphics = @import("didot-opengl/graphics.zig"); 7 | pub const app = @import("didot-app/app.zig"); 8 | pub const image = @import("didot-image/image.zig"); 9 | pub const models = @import("didot-models/models.zig"); 10 | pub const objects = @import("didot-objects/objects.zig"); 11 | pub const physics = @import("didot-ode/physics.zig"); 12 | 13 | comptime { 14 | const testing = @import("std").testing; 15 | testing.refAllDecls(graphics); 16 | testing.refAllDecls(objects); 17 | testing.refAllDecls(app); 18 | testing.refAllDecls(models); 19 | testing.refAllDecls(image); 20 | } 21 | -------------------------------------------------------------------------------- /examples/kart-and-cubes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenith391/didot/a096869f934bb1005c1122c40029eafa51e76e58/examples/kart-and-cubes.png -------------------------------------------------------------------------------- /examples/kart-and-cubes/example-scene.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const zlm = @import("zlm"); 3 | const Vec3 = zlm.Vec3; 4 | const Allocator = std.mem.Allocator; 5 | 6 | usingnamespace @import("didot-graphics"); 7 | const objects = @import("didot-objects"); 8 | const models = @import("didot-models"); 9 | const image = @import("didot-image"); 10 | const physics = @import("didot-physics"); 11 | const bmp = image.bmp; 12 | const obj = models.obj; 13 | const Application = @import("didot-app").Application; 14 | 15 | const GameObject = objects.GameObject; 16 | const Scene = objects.Scene; 17 | const Camera = objects.Camera; 18 | const PointLight = objects.PointLight; 19 | 20 | var world: physics.World = undefined; 21 | var simPaused: bool = false; 22 | var cube2: GameObject = undefined; 23 | 24 | const Component = objects.Component; 25 | const CameraControllerData = struct { input: *Input }; 26 | const CameraController = objects.ComponentType(.CameraController, CameraControllerData, .{ .updateFn = cameraInput }) {}; 27 | fn cameraInput(allocator: *Allocator, component: *Component, delta: f32) !void { 28 | const data = component.getData(CameraControllerData); 29 | const input = data.input; 30 | const gameObject = component.gameObject; 31 | 32 | const speed: f32 = 0.1 * delta; 33 | const forward = gameObject.getForward(); 34 | const left = gameObject.getLeft(); 35 | 36 | if (input.isKeyDown(Input.KEY_W)) { 37 | gameObject.position = gameObject.position.add(forward.scale(speed)); 38 | } 39 | if (input.isKeyDown(Input.KEY_S)) { 40 | gameObject.position = gameObject.position.add(forward.scale(-speed)); 41 | } 42 | if (input.isKeyDown(Input.KEY_A)) { 43 | gameObject.position = gameObject.position.add(left.scale(speed)); 44 | } 45 | if (input.isKeyDown(Input.KEY_D)) { 46 | gameObject.position = gameObject.position.add(left.scale(-speed)); 47 | } 48 | 49 | if (input.isKeyDown(Input.KEY_UP)) { 50 | if (cube2.findComponent(.Rigidbody)) |rb| { 51 | rb.getData(physics.RigidbodyData).addForce(zlm.Vec3.new(0, 20, 0)); 52 | } 53 | } 54 | if (input.isKeyDown(Input.KEY_LEFT)) { 55 | if (cube2.findComponent(.Rigidbody)) |rb| { 56 | rb.getData(physics.RigidbodyData).addForce(zlm.Vec3.new(5, 0, 0)); 57 | } 58 | } 59 | if (input.isKeyDown(Input.KEY_RIGHT)) { 60 | if (cube2.findComponent(.Rigidbody)) |rb| { 61 | rb.getData(physics.RigidbodyData).addForce(zlm.Vec3.new(0, 0, 5)); 62 | } 63 | } 64 | 65 | if (input.isMouseButtonDown(.Left)) { 66 | input.setMouseInputMode(.Grabbed); 67 | simPaused = false; 68 | } else if (input.isMouseButtonDown(.Right)) { 69 | input.setMouseInputMode(.Normal); 70 | } else if (input.isKeyDown(Input.KEY_ESCAPE)) { 71 | input.setMouseInputMode(.Normal); 72 | } 73 | 74 | if (input.getMouseInputMode() == .Grabbed) { 75 | gameObject.rotation.x -= (input.mouseDelta.x / 300.0) * delta; 76 | gameObject.rotation.y -= (input.mouseDelta.y / 300.0) * delta; 77 | } 78 | 79 | if (input.getJoystick(0)) |joystick| { 80 | const axes = joystick.getRawAxes(); 81 | var r = axes[0]; // right 82 | var fw = axes[1]; // forward 83 | const thrust = (axes[3] - 1.0) * -0.5; 84 | const threshold = 0.2; 85 | 86 | if (r < threshold and r > 0) r = 0; if (r > -threshold and r < 0) r = 0; 87 | if (fw < threshold and fw > 0) fw = 0; if (fw > -threshold and fw < 0) fw = 0; 88 | 89 | gameObject.position = gameObject.position.add(forward.scale(thrust * speed)); 90 | gameObject.rotation.x -= (r / 50.0) * delta; 91 | gameObject.rotation.y -= (fw / 50.0) * delta; 92 | 93 | // std.debug.warn("A: {}, B: {}, X: {}, Y: {}\n", .{ 94 | // joystick.isButtonDown(.A), 95 | // joystick.isButtonDown(.B), 96 | // joystick.isButtonDown(.X), 97 | // joystick.isButtonDown(.Y), 98 | // }); 99 | } 100 | } 101 | 102 | const TestLight = objects.ComponentType(.TestLight, struct {}, .{ .updateFn = testLight }) {}; 103 | fn testLight(allocator: *Allocator, component: *Component, delta: f32) !void { 104 | const time = @intToFloat(f64, std.time.milliTimestamp()); 105 | const rad = @floatCast(f32, @mod((time / 1000.0), std.math.pi * 2.0)); 106 | component.gameObject.position = Vec3.new(std.math.sin(rad) * 20 + 10, 3, std.math.cos(rad) * 10 - 10); 107 | } 108 | 109 | fn loadSkybox(allocator: *Allocator, camera: *Camera, scene: *Scene) !GameObject { 110 | var skyboxShader = try ShaderProgram.createFromFile(allocator, "assets/shaders/skybox-vert.glsl", "assets/shaders/skybox-frag.glsl"); 111 | camera.*.skyboxShader = skyboxShader; 112 | 113 | try scene.assetManager.put("Texture/Skybox", try TextureAsset.initCubemap(allocator, .{ 114 | .front = "assets/textures/skybox/front.bmp", 115 | .back = "assets/textures/skybox/back.bmp", 116 | .left = "assets/textures/skybox/left.bmp", 117 | .right = "assets/textures/skybox/right.bmp", 118 | .top = "assets/textures/skybox/top.bmp", 119 | .bottom = "assets/textures/skybox/bottom.bmp" 120 | }, "bmp")); 121 | 122 | var skyboxMaterial = Material{ .texturePath = "Texture/Skybox" }; 123 | var skybox = try objects.createSkybox(allocator); 124 | skybox.meshPath = "Mesh/Cube"; 125 | skybox.material = skyboxMaterial; 126 | return skybox; 127 | } 128 | 129 | fn initFromFile(allocator: *Allocator, app: *Application) !void { 130 | input = &app.window.input; 131 | var scene = Scene.loadFromFile(allocator, "res/example-scene.json"); 132 | scene.findChild("Camera").?.updateFn = cameraInput; 133 | scene.findChild("Light").?.updateFn = testLight; 134 | app.scene = scene; 135 | 136 | //var skybox = try loadSkybox(allocator, camera); 137 | //try scene.add(skybox); 138 | 139 | // var i: f32 = 0; 140 | // while (i < 5) { 141 | // var j: f32 = 0; 142 | // while (j < 5) { 143 | // var kart2 = GameObject.createObject(allocator, "Mesh/Kart"); 144 | // kart2.position = Vec3.new(0.7 + (j*8), 0.75, -8 - (i*3)); 145 | // try scene.add(kart2); 146 | // j += 1; 147 | // } 148 | // i += 1; 149 | // } 150 | } 151 | 152 | fn update(allocator: *Allocator, app: *Application, delta: f32) !void { 153 | if (!simPaused) 154 | world.update(); 155 | } 156 | 157 | fn init(allocator: *Allocator, app: *Application) !void { 158 | world = physics.World.create(); 159 | world.setGravity(zlm.Vec3.new(0, -9.8, 0)); 160 | 161 | var shader = try ShaderProgram.createFromFile(allocator, "assets/shaders/vert.glsl", "assets/shaders/frag.glsl"); 162 | const scene = app.scene; 163 | 164 | try scene.assetManager.put("Texture/Grass", try TextureAsset.init(allocator, .{ 165 | .path = "assets/textures/grass.png", .format = "png", 166 | .tiling = zlm.Vec2.new(2, 2) 167 | })); 168 | var grassMaterial = Material{ .texturePath = "Texture/Grass" }; 169 | 170 | var camera = try Camera.create(allocator, shader); 171 | camera.gameObject.position = Vec3.new(1.5, 1.5, -0.5); 172 | camera.gameObject.rotation = Vec3.new(-120.0, -15.0, 0).toRadians(); 173 | const controller = try CameraController.newWithData(allocator, .{ .input = &app.window.input }); 174 | try camera.gameObject.addComponent(controller); 175 | try scene.add(camera.gameObject); 176 | 177 | const skybox = try loadSkybox(allocator, camera, scene); 178 | try scene.add(skybox); 179 | 180 | var cube = GameObject.createObject(allocator, "Mesh/Cube"); 181 | cube.position = Vec3.new(5, -0.75, -10); 182 | cube.scale = Vec3.new(50, 1, 50); 183 | cube.material = grassMaterial; 184 | try cube.addComponent(try physics.Rigidbody.newWithData(allocator, .{ .world=&world, .kinematic=.Kinematic, .collider = .{ 185 | .Box = .{ .size = Vec3.new(50, 1, 50) } 186 | } })); 187 | try scene.add(cube); 188 | 189 | cube2 = GameObject.createObject(allocator, "Mesh/Cube"); 190 | cube2.position = Vec3.new(-1.2, 5.75, -3); 191 | cube2.material.ambient = Vec3.new(0.2, 0.1, 0.1); 192 | cube2.material.diffuse = Vec3.new(0.8, 0.8, 0.8); 193 | try cube2.addComponent(try physics.Rigidbody.newWithData(allocator, .{ .world = &world })); 194 | try scene.add(cube2); 195 | 196 | try scene.assetManager.put("Mesh/Kart", .{ 197 | .loader = models.meshAssetLoader, 198 | .loaderData = try models.MeshAssetLoaderData.init(allocator, "assets/kart.obj", "obj"), 199 | .objectType = .Mesh, 200 | }); 201 | var kart = GameObject.createObject(allocator, "Mesh/Kart"); 202 | kart.position = Vec3.new(0.7, 0.75, -5); 203 | kart.name = "Kart"; 204 | try kart.addComponent(try physics.Rigidbody.newWithData(allocator, .{ .world = &world })); 205 | try scene.add(kart); 206 | 207 | var i: f32 = 0; 208 | while (i < 7) { // rows front to back 209 | var j: f32 = 0; 210 | while (j < 5) { // cols left to right 211 | var kart2 = GameObject.createObject(allocator, "Mesh/Kart"); 212 | kart2.position = Vec3.new(0.7 + (j * 8), 0.75, -8 - (i * 3)); 213 | 214 | // lets decorate the kart based on its location 215 | kart2.material.ambient = Vec3.new(0.0, j * 0.001, 0.002); 216 | kart2.material.diffuse = Vec3.new(i * 0.1, j * 0.1, 0.6); 217 | 218 | // dull on the left, getting shinier to the right 219 | kart2.material.specular = Vec3.new(1.0 - (j * 0.2), 1.0 - (j * 0.2), 0.2); 220 | kart2.material.shininess = 110.0 - (j * 20.0); 221 | //try kart2.addComponent(try physics.Rigidbody.newWithData(allocator,.{.world=&world})); 222 | try scene.add(kart2); 223 | j += 1; 224 | } 225 | i += 1; 226 | } 227 | 228 | var light = GameObject.createObject(allocator, "Mesh/Cube"); 229 | light.position = Vec3.new(1, 5, -5); 230 | light.material.ambient = Vec3.one; 231 | try light.addComponent(try TestLight.new(allocator)); 232 | try light.addComponent(try PointLight.newWithData(allocator, .{})); 233 | try scene.add(light); 234 | } 235 | 236 | var gp: std.heap.GeneralPurposeAllocator(.{}) = .{}; 237 | 238 | pub fn main() !void { 239 | defer _ = gp.deinit(); 240 | const allocator = &gp.allocator; 241 | var scene = try Scene.create(allocator, null); 242 | var app = Application{ 243 | .title = "Test Cubes", 244 | .initFn = init 245 | }; 246 | try app.run(allocator, scene); 247 | } 248 | -------------------------------------------------------------------------------- /examples/planet-test/example-scene.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const zalgebra = @import("zalgebra"); 3 | usingnamespace @import("didot-graphics"); 4 | usingnamespace @import("didot-objects"); 5 | usingnamespace @import("didot-app"); 6 | 7 | const Vec3 = zalgebra.vec3; 8 | const Quat = zalgebra.quat; 9 | const rad = zalgebra.to_radians; 10 | const Allocator = std.mem.Allocator; 11 | 12 | var scene: *Scene = undefined; 13 | 14 | const App = comptime blk: { 15 | comptime var systems = Systems {}; 16 | systems.addSystem(cameraSystem); 17 | break :blk Application(systems); 18 | }; 19 | 20 | const CameraController = struct {}; 21 | 22 | pub fn cameraSystem(controller: *Camera, transform: *Transform) !void { 23 | if (scene.findChild("Planet")) |planet| { 24 | const planetPosition = planet.getComponent(Transform).?.position; 25 | transform.lookAt(planetPosition); 26 | } 27 | } 28 | 29 | pub fn init(allocator: *Allocator, app: *App) !void { 30 | scene = app.scene; 31 | const asset = &scene.assetManager; 32 | 33 | try asset.autoLoad(allocator); 34 | 35 | var shader = try ShaderProgram.createFromFile(allocator, "assets/shaders/vert.glsl", "assets/shaders/frag.glsl"); 36 | 37 | var camera = try GameObject.createObject(allocator, null); 38 | camera.getComponent(Transform).?.position = Vec3.new(1.5, 1.5, -0.5); 39 | camera.getComponent(Transform).?.rotation = Quat.from_euler_angle(Vec3.new(300, -15, 0)); 40 | try camera.addComponent(Camera { .shader = shader }); 41 | try camera.addComponent(CameraController {}); 42 | try app.scene.add(camera); 43 | 44 | var sphere = try GameObject.createObject(allocator, asset.get("sphere.obj")); 45 | sphere.name = "Planet"; 46 | sphere.getComponent(Transform).?.position = Vec3.new(5, 0.75, -5); 47 | try app.scene.add(sphere); 48 | 49 | var light = try GameObject.createObject(allocator, null); 50 | light.getComponent(Transform).?.position = Vec3.new(1, 5, -5); 51 | light.material.ambient = Vec3.one(); 52 | try light.addComponent(PointLight {}); 53 | try scene.add(light); 54 | } 55 | 56 | pub fn main() !void { 57 | var gp = std.heap.GeneralPurposeAllocator(.{}) {}; 58 | const allocator = &gp.allocator; 59 | 60 | var app = App { 61 | .title = "Planets", 62 | .initFn = init 63 | }; 64 | try app.run(allocator, try Scene.create(allocator, null)); 65 | } -------------------------------------------------------------------------------- /examples/test-portal/example-scene.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const zalgebra = @import("zalgebra"); 3 | const physics = @import("didot-physics"); 4 | const Vec3 = zalgebra.Vec3; 5 | const Quat = zalgebra.Quat; 6 | const Allocator = std.mem.Allocator; 7 | const rad = zalgebra.to_radians; 8 | 9 | const graphics = @import("didot-graphics"); 10 | const objects = @import("didot-objects"); 11 | const Scene = objects.Scene; 12 | const Input = graphics.Input; 13 | const Transform = objects.Transform; 14 | const Camera = objects.Camera; 15 | const Query = objects.Query; 16 | const ShaderProgram = graphics.ShaderProgram; 17 | const GameObject = objects.GameObject; 18 | const TextureAsset = graphics.TextureAsset; 19 | const Material = graphics.Material; 20 | const Skybox = objects.Skybox; 21 | const PointLight = objects.PointLight; 22 | 23 | pub const log = @import("didot-app").log; 24 | const Systems = @import("didot-app").Systems; 25 | const Application = @import("didot-app").Application; 26 | 27 | var world: physics.World = undefined; 28 | var scene: *Scene = undefined; 29 | 30 | //pub const io_mode = .evented; 31 | 32 | const App = blk: { 33 | comptime var systems = Systems {}; 34 | systems.addSystem(update); 35 | systems.addSystem(testSystem); 36 | systems.addSystem(playerSystem); 37 | systems.addSystem(cameraSystem); 38 | 39 | systems.addSystem(physics.rigidbodySystem); 40 | break :blk Application(systems); 41 | }; 42 | 43 | const CameraController = struct { input: *Input, player: *GameObject }; 44 | fn cameraSystem(controller: *CameraController, transform: *Transform) !void { 45 | const input = controller.input; 46 | transform.position = controller.player.getComponent(Transform).?.position; 47 | 48 | if (input.isMouseButtonDown(.Left)) { 49 | input.setMouseInputMode(.Grabbed); 50 | } else if (input.isMouseButtonDown(.Right) or input.isKeyDown(Input.KEY_ESCAPE)) { 51 | input.setMouseInputMode(.Normal); 52 | } 53 | 54 | if (input.getMouseInputMode() == .Grabbed) { 55 | const delta = 1.0; // TODO: put delta in Application struct 56 | var euler = transform.rotation.extractRotation(); 57 | euler.x += (input.mouseDelta.x / 3.0) * delta; 58 | euler.y -= (input.mouseDelta.y / 3.0) * delta; 59 | if (euler.y > 89) euler.y = 89; 60 | if (euler.y < -89) euler.y = -89; 61 | 62 | transform.rotation = Quat.fromEulerAngle(euler); 63 | } 64 | } 65 | 66 | const PlayerController = struct { input: *Input }; 67 | fn playerSystem(controller: *PlayerController, rb: *physics.Rigidbody) !void { 68 | const input = controller.input; 69 | 70 | const delta = 1.0; // TODO: put delta in Application struct 71 | const speed: f32 = 40 / delta; 72 | 73 | const camera = scene.findChild("Camera").?.getComponent(Transform).?; 74 | var forward = camera.getForward(); 75 | const right = camera.getRight(); 76 | forward.y = 0; 77 | 78 | if (input.isKeyDown(Input.KEY_W)) { 79 | rb.addForce(forward.scale(speed)); 80 | } 81 | if (input.isKeyDown(Input.KEY_S)) { 82 | rb.addForce(forward.scale(-speed)); 83 | } 84 | if (input.isKeyDown(Input.KEY_A)) { 85 | rb.addForce(right.scale(-speed)); 86 | } 87 | if (input.isKeyDown(Input.KEY_D)) { 88 | rb.addForce(right.scale(speed)); 89 | } 90 | if (input.isKeyDown(Input.KEY_SPACE)) { 91 | rb.addForce(Vec3.new(0, speed, 0)); 92 | } 93 | } 94 | 95 | fn loadSkybox(allocator: *Allocator, camera: *Camera) !void { 96 | const asset = &scene.assetManager; 97 | try asset.put("textures/skybox", try TextureAsset.initCubemap(allocator, .{ 98 | .front = asset.get("textures/skybox/front.png").?, 99 | .back = asset.get("textures/skybox/back.png").?, 100 | .left = asset.get("textures/skybox/left.png").?, 101 | .right = asset.get("textures/skybox/right.png").?, 102 | .top = asset.get("textures/skybox/top.png").?, 103 | .bottom = asset.get("textures/skybox/bottom.png").?, 104 | })); 105 | 106 | var skyboxShader = try ShaderProgram.createFromFile(allocator, "assets/shaders/skybox-vert.glsl", "assets/shaders/skybox-frag.glsl"); 107 | camera.skybox = Skybox { 108 | .mesh = asset.get("Mesh/Cube").?, 109 | .cubemap = asset.get("textures/skybox").?, 110 | .shader = skyboxShader 111 | }; 112 | } 113 | 114 | fn testSystem(query: Query(.{})) !void { 115 | var iter = query.iterator(); 116 | while (iter.next()) |go| { 117 | _ = go; 118 | } 119 | } 120 | 121 | fn update() !void { 122 | world.update(); 123 | } 124 | 125 | fn init(allocator: *Allocator, app: *App) !void { 126 | world = physics.World.create(); 127 | world.setGravity(Vec3.new(0, -9.8, 0)); 128 | 129 | var shader = try ShaderProgram.createFromFile(allocator, "assets/shaders/vert.glsl", "assets/shaders/frag.glsl"); 130 | scene = app.scene; 131 | const asset = &scene.assetManager; 132 | 133 | try asset.autoLoad(allocator); 134 | //try asset.comptimeAutoLoad(allocator); 135 | 136 | var concreteMaterial = Material { .texture = asset.get("textures/grass.bmp") }; 137 | 138 | var player = try GameObject.createObject(allocator, null); 139 | player.getComponent(Transform).?.* = .{ 140 | .position = Vec3.new(1.5, 3.5, -0.5), 141 | .scale = Vec3.new(2, 2, 2) 142 | }; 143 | player.name = "Player"; 144 | try player.addComponent(PlayerController { .input = &app.window.input }); 145 | try player.addComponent(physics.Rigidbody { 146 | .world = &world, 147 | .collider = .{ 148 | .Sphere = .{ .radius = 1.0 } 149 | } 150 | }); 151 | try scene.add(player); 152 | 153 | var camera = try GameObject.createObject(allocator, null); 154 | try camera.addComponent(Camera { .shader = shader }); 155 | camera.getComponent(Transform).?.* = .{ 156 | .position = Vec3.new(1.5, 3.5, -0.5), 157 | .rotation = Quat.fromEulerAngle(Vec3.new(-120, -15, 0)) 158 | }; 159 | camera.name = "Camera"; 160 | try camera.addComponent(CameraController { 161 | .input = &app.window.input, 162 | .player = scene.findChild("Player").? 163 | }); 164 | try scene.add(camera); 165 | 166 | try loadSkybox(allocator, camera.getComponent(Camera).?); 167 | 168 | var cube = try GameObject.createObject(allocator, asset.get("Mesh/Cube")); 169 | cube.getComponent(Transform).?.* = .{ 170 | .position = Vec3.new(5, -0.75, -10), 171 | .scale = Vec3.new(250, 0.1, 250) 172 | }; 173 | cube.material = concreteMaterial; 174 | try cube.addComponent(physics.Rigidbody { 175 | .world=&world, 176 | .kinematic=.Kinematic, 177 | .collider = .{ 178 | .Box = .{} 179 | }, 180 | .material = .{ 181 | .friction = 10 182 | } 183 | }); 184 | try scene.add(cube); 185 | 186 | var i: usize = 0; 187 | 188 | var rand = std.rand.DefaultPrng.init(145115126); 189 | var random = rand.random; 190 | while (i < 50) : (i += 1) { 191 | var domino = try GameObject.createObject(allocator, asset.get("Mesh/Cube")); 192 | domino.getComponent(Transform).?.* = .{ 193 | .position = Vec3.new(-1.2, 0.75, -3 - (1.3 * @intToFloat(f32, i))), 194 | .scale = Vec3.new(1, 2, 0.1) 195 | }; 196 | domino.material.ambient = Vec3.new(random.float(f32) * 0.1, random.float(f32) * 0.1, random.float(f32) * 0.1); 197 | domino.material.diffuse = Vec3.new(random.float(f32), random.float(f32), random.float(f32)); 198 | try domino.addComponent(physics.Rigidbody { .world = &world, 199 | .collider = .{ .Box = .{} }}); 200 | try scene.add(domino); 201 | } 202 | 203 | var light = try GameObject.createObject(allocator, asset.get("Mesh/Cube")); 204 | light.getComponent(Transform).?.position = Vec3.new(1, 5, -5); 205 | light.material.ambient = Vec3.one(); 206 | try light.addComponent(PointLight {}); 207 | try scene.add(light); 208 | } 209 | 210 | var gp: std.heap.GeneralPurposeAllocator(.{}) = .{}; 211 | 212 | pub fn main() !void { 213 | defer _ = gp.deinit(); 214 | const gpa_allocator = &gp.allocator; 215 | const allocator = gpa_allocator; 216 | 217 | var app = App { 218 | .title = "Test Room", 219 | .initFn = init 220 | }; 221 | try app.run(allocator, try Scene.create(allocator, null)); 222 | } 223 | -------------------------------------------------------------------------------- /zalgebra/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: push 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | os: [ubuntu-latest, macos-latest, windows-latest] 8 | runs-on: ${{matrix.os}} 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: goto-bus-stop/setup-zig@v1 12 | with: 13 | version: 0.8.0 14 | - run: zig build test 15 | lint: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: goto-bus-stop/setup-zig@v1 20 | with: 21 | version: 0.8.0 22 | - run: zig fmt --check src/*.zig 23 | -------------------------------------------------------------------------------- /zalgebra/.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | build_runner.zig 3 | deps.zig 4 | gyro.lock 5 | -------------------------------------------------------------------------------- /zalgebra/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alexandre Chêne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /zalgebra/README.md: -------------------------------------------------------------------------------- 1 | # zalgebra 2 | ![CI](https://github.com/kooparse/zalgebra/workflows/CI/badge.svg) 3 |
4 |
5 | Linear algebra library for games and computer graphics. 6 | 7 | The goal is to become as complete and useful as the Unity one. I'm currently using it for my projects and will continue to update it as new needs are coming. 8 | 9 | If you would like to contribute, don't hesitate! ;) 10 | 11 | ## Examples 12 | 13 | ```zig 14 | const za = @import("zalgebra"); 15 | const Vec3 = za.Vec3; 16 | const Mat4 = za.Mat4; 17 | 18 | pub fn main () void { 19 | var projection = za.perspective(45.0, 800.0 / 600.0, 0.1, 100.0); 20 | var view = za.lookAt(Vec3.new(0., 0., -3.), Vec3.zero(), Vec3.up()); 21 | var model = Mat4.fromTranslate(Vec3.new(0.2, 0.5, 0.0)); 22 | 23 | var mvp = Mat4.mult(projection, view.mult(model)); 24 | } 25 | ``` 26 | 27 | ## Quick reference 28 | 29 | ### Aliases 30 | 31 | Type | Description 32 | ------------ | ------------- 33 | Vec2 | Two dimensional vector for `f32` 34 | Vec2_f64 | Two dimensional vector for `f64` 35 | Vec2_i32 | Two dimensional vector for `i32` 36 | Vec3 | Three dimensional vector for `f32` 37 | Vec3_f64 | Three dimensional vector for `f64` 38 | Vec3_i32 | Three dimensional vector for `i32` 39 | Vec4 | Four dimensional vector for `f32` 40 | Vec4_f64 | Four dimensional vector for `f64` 41 | Vec4_i32 | Four dimensional vector for `i32` 42 | Mat4 | 4x4 matrix for `f32` 43 | Mat4_f64 | 4x4 matrix for `f64` 44 | Quat | Quaternion for `f32` 45 | Quat_f64 | Quaternion for `f64` 46 | perspective | Perspective function for `f32` 4x4 mat4 47 | orthographic | Orthographic function for `f32` 4x4 mat4 48 | lookAt | LookAt function for `f32` 4x4 mat4 49 | 50 | ### Vectors 51 | 52 | Methods | Description 53 | ------------ | ------------- 54 | new | Construct a vector from 2 to 4 components 55 | at | Return component from given index 56 | set | Set all components to the same given value 57 | cast | Cast a type to another type 58 | fromSlice | Construct new vectors from slice 59 | zero | Shorthand for `(0, 0, 0)` 60 | one | Shorthand for `(1, 1, 1)` 61 | up | Shorthand for `(0, 1, 0)` (only for vec3) 62 | down | Shorthand for `(0, -1, 0)` (only for vec3) 63 | right | Shorthand for `(1, 0, 0)` (only for vec3) 64 | left | Shorthand for `(-1, 0, 0)` (only for vec3) 65 | forward | Shorthand for `(0, 0, 1)` (only for vec3) 66 | back | Shorthand for `(0, 0, -1)` (only for vec3) 67 | toArray | Return an array of same size. 68 | getAngle | Return angle in degrees between two vectors (only for vec2 and vec3) 69 | length | Return the magnitude of the current vector 70 | distance | Return the distance between two points 71 | norm | Construct a new normalized vector based on the given one 72 | eql | Return `true` if two vectors are equals 73 | sub | Construct new vector resulting from the substraction between two vectors 74 | add | Construct new vector resulting from the addition between two vectors 75 | scale | Construct new vector after multiplying each components by the given scalar 76 | cross | Construct the cross product (as vector) from two vectors (only for vec3) 77 | dot | Return the dot product between two vectors 78 | lerp | Linear interpolation between two vectors 79 | min | Construct vector from the min components between two vectors 80 | max | Construct vector from the min components between two vectors 81 | 82 | ### Matrices 83 | Note: All matrices are column-major. 84 | 85 | Methods | Description 86 | ------------ | ------------- 87 | identity | Construct an identity matrix 88 | fromSlice | Construct new matrix from given slice of data 89 | getData | Return a pointer to the inner data 90 | eql | Return `true` if two matrices are equals 91 | multByVec4 | Multiply a given vec4 by matrix (only for mat4) 92 | fromTranslate | Construct a translation matrix 93 | translate | Construct a translation from the given matrix according to given axis (vec3) 94 | fromRotation | Construct a rotation matrix 95 | fromEulerAngle | Construct a rotation matrix from pitch/yaw/roll in degrees (X * Y * Z) 96 | rotate | Construct a rotation from the given matrix according to given axis (vec3) 97 | fromScale | Construct a scale matrix 98 | scale | Construct a scale from the given matrix according to given axis (vec3) 99 | extractTranslation | Return a vector with proper translation 100 | orthoNormalize | Ortho normalize the given matrix. 101 | extractRotation | Return a vector with proper pitch/yaw/roll 102 | extractScale | Return a vector with proper scale 103 | perspective | Construct a perspective matrix from given fovy, aspect ratio, near/far inputs 104 | orthographic| Construct an orthographic matrix from given left, right, bottom, top, near/far inputs 105 | lookAt | Construct a right-handed lookAt matrix from given position (eye) and target 106 | mult | Multiply two matrices 107 | inv | Inverse the given matrix 108 | recompose | Return matrix from given `translation`, `rotation` and `scale` components 109 | decompose | Return components `translation`, `rotation` and `scale` from given matrix. 110 | debugPrint | Print the matrix data for debug purpose 111 | 112 | ### Quaternions 113 | Methods | Description 114 | ------------ | ------------- 115 | new| Construct new quat from given floats 116 | zero| Construct quat as `(1, 0, 0, 0)` 117 | fromSlice | Construct new quaternion from slice 118 | fromVec3 | Construct quaternion from vec3 119 | eql | Return `true` if two quaternions are equal 120 | norm | Normalize given quaternion 121 | length | Return the magniture of the given quaternion 122 | sub | Construct quaternion resulting from the subtraction of two given ones 123 | add | Construct quaternion resulting from the addition of two given ones 124 | mult | Construct quaternion resulting from the multiplication of two given ones 125 | scale | Construct quaternion resulting from the multiplication of all components of the given quat 126 | dot | Return the dot product between two quaternions 127 | toMat4 | Convert given quat to rotation 4x4 matrix 128 | fromEulerAngle | Construct quaternion from Euler angle 129 | fromAxis | Construct quat from angle around specified axis 130 | extractRotation | Get euler angles from given quaternion 131 | rotateVec | Rotate given vector 132 | 133 | 134 | ### Utilities 135 | 136 | Methods | Description 137 | ------------ | ------------- 138 | toRadians | Convert degrees to radians 139 | toDegrees | Convert radians to degrees 140 | lerp | Linear interpolation between two floats 141 | 142 | 143 | ## Contributing to the project 144 | 145 | Don’t be shy about shooting any questions you may have. If you are a beginner/junior, don’t hesitate, I will always encourage you. It’s a safe place here. Also, I would be very happy to receive any kind of pull requests, you will have (at least) some feedback/guidance rapidly. 146 | 147 | Behind screens, there are human beings, living any sort of story. So be always kind and respectful, because we all sheer to learn new things. 148 | 149 | 150 | ## Thanks 151 | This project is inspired by [Handmade Math](https://github.com/HandmadeMath/Handmade-Math), [nalgebra](https://nalgebra.org/) and [Unity](https://unity.com/). 152 | -------------------------------------------------------------------------------- /zalgebra/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Builder = std.build.Builder; 3 | 4 | pub fn build(b: *Builder) void { 5 | const mode = b.standardReleaseOptions(); 6 | 7 | var tests = b.addTest("src/main.zig"); 8 | tests.setBuildMode(mode); 9 | 10 | const test_step = b.step("test", "Run tests"); 11 | test_step.dependOn(&tests.step); 12 | } 13 | -------------------------------------------------------------------------------- /zalgebra/gyro.zzz: -------------------------------------------------------------------------------- 1 | pkgs: 2 | zalgebra: 3 | version: 0.1.0 4 | description: "Linear algebra library for games and real-time computer graphics." 5 | license: MIT 6 | source_url: "https://github.com/kooparse/zalgebra" 7 | tags: 8 | math 9 | linear-algebra 10 | graphics 11 | gamedev 12 | matrix 13 | quaternion 14 | 15 | root: src/main.zig 16 | files: 17 | README.md 18 | LICENSE 19 | build.zig 20 | src/*.zig 21 | -------------------------------------------------------------------------------- /zalgebra/src/main.zig: -------------------------------------------------------------------------------- 1 | //! Math utilities for graphics. 2 | //! 3 | const std = @import("std"); 4 | const testing = std.testing; 5 | const math = std.math; 6 | 7 | pub usingnamespace @import("vec2.zig"); 8 | pub usingnamespace @import("vec3.zig"); 9 | pub usingnamespace @import("vec4.zig"); 10 | pub usingnamespace @import("mat4.zig"); 11 | pub usingnamespace @import("quaternion.zig"); 12 | 13 | /// Convert degrees to radians. 14 | pub fn toRadians(degrees: anytype) @TypeOf(degrees) { 15 | const T = @TypeOf(degrees); 16 | 17 | return switch (@typeInfo(T)) { 18 | .Float => degrees * (math.pi / 180.0), 19 | else => @compileError("Radians not implemented for " ++ @typeName(T)), 20 | }; 21 | } 22 | 23 | /// Convert radians to degrees. 24 | pub fn toDegrees(radians: anytype) @TypeOf(radians) { 25 | const T = @TypeOf(radians); 26 | 27 | return switch (@typeInfo(T)) { 28 | .Float => radians * (180.0 / math.pi), 29 | else => @compileError("Degrees not implemented for " ++ @typeName(T)), 30 | }; 31 | } 32 | 33 | /// Linear interpolation between two floats. 34 | /// `t` is used to interpolate between `from` and `to`. 35 | pub fn lerp(comptime T: type, from: T, to: T, t: T) T { 36 | return switch (@typeInfo(T)) { 37 | .Float => (1 - t) * from + t * to, 38 | else => @compileError("Lerp not implemented for " ++ @typeName(T)), 39 | }; 40 | } 41 | 42 | test "zalgebra.toRadians" { 43 | try testing.expectEqual(toRadians(@as(f32, 90)), 1.57079637); 44 | try testing.expectEqual(toRadians(@as(f32, 45)), 0.785398185); 45 | try testing.expectEqual(toRadians(@as(f32, 360)), 6.28318548); 46 | try testing.expectEqual(toRadians(@as(f32, 0)), 0.0); 47 | } 48 | 49 | test "zalgebra.toDegrees" { 50 | try testing.expectEqual(toDegrees(@as(f32, 0.5)), 28.6478900); 51 | try testing.expectEqual(toDegrees(@as(f32, 1.0)), 57.2957801); 52 | try testing.expectEqual(toDegrees(@as(f32, 0.0)), 0.0); 53 | } 54 | 55 | test "zalgebra.lerp" { 56 | const from: f32 = 0; 57 | const to: f32 = 10; 58 | 59 | try testing.expectEqual(lerp(f32, from, to, 0), 0); 60 | try testing.expectEqual(lerp(f32, from, to, 0.5), 5); 61 | try testing.expectEqual(lerp(f32, from, to, 1), 10); 62 | } 63 | -------------------------------------------------------------------------------- /zalgebra/src/vec2.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const root = @import("main.zig"); 3 | const math = std.math; 4 | const testing = std.testing; 5 | const panic = std.debug.panic; 6 | 7 | pub const Vec2 = Vector2(f32); 8 | pub const Vec2_f64 = Vector2(f64); 9 | pub const Vec2_i32 = Vector2(i32); 10 | 11 | /// A 2 dimensional vector. 12 | pub fn Vector2(comptime T: type) type { 13 | if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) { 14 | @compileError("Vector2 not implemented for " ++ @typeName(T)); 15 | } 16 | 17 | return struct { 18 | x: T, 19 | y: T, 20 | 21 | const Self = @This(); 22 | 23 | /// Construct vector from given 2 components. 24 | pub fn new(x: T, y: T) Self { 25 | return .{ .x = x, .y = y }; 26 | } 27 | 28 | /// Set all components to the same given value. 29 | pub fn set(val: T) Self { 30 | return Self.new(val, val); 31 | } 32 | 33 | pub fn zero() Self { 34 | return Self.new(0, 0); 35 | } 36 | 37 | pub fn one() Self { 38 | return Self.new(1, 1); 39 | } 40 | 41 | pub fn up() Self { 42 | return Self.new(0, 1); 43 | } 44 | 45 | /// Cast a type to another type. Only for integers and floats. 46 | /// It's like builtins: @intCast, @floatCast, @intToFloat, @floatToInt. 47 | pub fn cast(self: Self, dest: anytype) Vector2(dest) { 48 | const source_info = @typeInfo(T); 49 | const dest_info = @typeInfo(dest); 50 | 51 | if (source_info == .Float and dest_info == .Int) { 52 | const x = @floatToInt(dest, self.x); 53 | const y = @floatToInt(dest, self.y); 54 | return Vector2(dest).new(x, y); 55 | } 56 | 57 | if (source_info == .Int and dest_info == .Float) { 58 | const x = @intToFloat(dest, self.x); 59 | const y = @intToFloat(dest, self.y); 60 | return Vector2(dest).new(x, y); 61 | } 62 | 63 | return switch (dest_info) { 64 | .Float => { 65 | const x = @floatCast(dest, self.x); 66 | const y = @floatCast(dest, self.y); 67 | return Vector2(dest).new(x, y); 68 | }, 69 | .Int => { 70 | const x = @intCast(dest, self.x); 71 | const y = @intCast(dest, self.y); 72 | return Vector2(dest).new(x, y); 73 | }, 74 | else => panic( 75 | "Error, given type should be integer or float.\n", 76 | .{}, 77 | ), 78 | }; 79 | } 80 | 81 | /// Construct new vector from slice. 82 | pub fn fromSlice(slice: []const T) Self { 83 | return Self.new(slice[0], slice[1]); 84 | } 85 | 86 | /// Transform vector to array. 87 | pub fn toArray(self: Self) [2]T { 88 | return .{ self.x, self.y }; 89 | } 90 | 91 | /// Return the angle in degrees between two vectors. 92 | pub fn getAngle(left: Self, right: Self) T { 93 | const dot_product = Self.dot(left.norm(), right.norm()); 94 | return root.toDegrees(math.acos(dot_product)); 95 | } 96 | 97 | /// Compute the length (magnitude) of given vector |a|. 98 | pub fn length(self: Self) T { 99 | return math.sqrt((self.x * self.x) + (self.y * self.y)); 100 | } 101 | 102 | /// Compute the distance between two points. 103 | pub fn distance(a: Self, b: Self) T { 104 | return math.sqrt( 105 | math.pow(T, b.x - a.x, 2) + math.pow(T, b.y - a.y, 2), 106 | ); 107 | } 108 | 109 | /// Construct new normalized vector from a given vector. 110 | pub fn norm(self: Self) Self { 111 | var l = length(self); 112 | return Self.new(self.x / l, self.y / l); 113 | } 114 | 115 | pub fn eql(left: Self, right: Self) bool { 116 | return left.x == right.x and left.y == right.y; 117 | } 118 | 119 | /// Substraction between two given vector. 120 | pub fn sub(left: Self, right: Self) Self { 121 | return Self.new(left.x - right.x, left.y - right.y); 122 | } 123 | 124 | /// Addition betwen two given vector. 125 | pub fn add(left: Self, right: Self) Self { 126 | return Self.new(left.x + right.x, left.y + right.y); 127 | } 128 | 129 | /// Multiply each components by the given scalar. 130 | pub fn scale(v: Self, scalar: T) Self { 131 | return Self.new(v.x * scalar, v.y * scalar); 132 | } 133 | 134 | /// Return the dot product between two given vector. 135 | pub fn dot(left: Self, right: Self) T { 136 | return (left.x * right.x) + (left.y * right.y); 137 | } 138 | 139 | /// Lerp between two vectors. 140 | pub fn lerp(left: Self, right: Self, t: T) Self { 141 | const x = root.lerp(T, left.x, right.x, t); 142 | const y = root.lerp(T, left.y, right.y, t); 143 | return Self.new(x, y); 144 | } 145 | 146 | /// Construct a new vector from the min components between two vectors. 147 | pub fn min(left: Self, right: Self) Self { 148 | return Self.new( 149 | math.min(left.x, right.x), 150 | math.min(left.y, right.y), 151 | ); 152 | } 153 | 154 | /// Construct a new vector from the max components between two vectors. 155 | pub fn max(left: Self, right: Self) Self { 156 | return Self.new( 157 | math.max(left.x, right.x), 158 | math.max(left.y, right.y), 159 | ); 160 | } 161 | }; 162 | } 163 | 164 | test "zalgebra.Vec2.init" { 165 | var a = Vec2.new(1.5, 2.6); 166 | 167 | try testing.expectEqual(a.x, 1.5); 168 | try testing.expectEqual(a.y, 2.6); 169 | } 170 | 171 | test "zalgebra.Vec2.set" { 172 | var a = Vec2.new(2.5, 2.5); 173 | var b = Vec2.set(2.5); 174 | try testing.expectEqual(Vec2.eql(a, b), true); 175 | } 176 | 177 | test "zalgebra.Vec2.getAngle" { 178 | var a = Vec2.new(1, 0); 179 | var b = Vec2.up(); 180 | var c = Vec2.new(-1, 0); 181 | var d = Vec2.new(1, 1); 182 | 183 | try testing.expectEqual(Vec2.getAngle(a, b), 90); 184 | try testing.expectEqual(Vec2.getAngle(a, c), 180); 185 | try testing.expectEqual(Vec2.getAngle(a, d), 45); 186 | } 187 | 188 | test "zalgebra.Vec2.toArray" { 189 | const a = Vec2.up().toArray(); 190 | const b = [_]f32{ 0, 1 }; 191 | 192 | try testing.expectEqual(std.mem.eql(f32, &a, &b), true); 193 | } 194 | 195 | test "zalgebra.Vec2.eql" { 196 | var a = Vec2.new(1, 2); 197 | var b = Vec2.new(1, 2); 198 | var c = Vec2.new(1.5, 2); 199 | try testing.expectEqual(Vec2.eql(a, b), true); 200 | try testing.expectEqual(Vec2.eql(a, c), false); 201 | } 202 | 203 | test "zalgebra.Vec2.length" { 204 | var a = Vec2.new(1.5, 2.6); 205 | try testing.expectEqual(a.length(), 3.00166606); 206 | } 207 | 208 | test "zalgebra.Vec2.distance" { 209 | var a = Vec2.new(0, 0); 210 | var b = Vec2.new(-1, 0); 211 | var c = Vec2.new(0, 5); 212 | 213 | try testing.expectEqual(Vec2.distance(a, b), 1); 214 | try testing.expectEqual(Vec2.distance(a, c), 5); 215 | } 216 | 217 | test "zalgebra.Vec2.normalize" { 218 | var a = Vec2.new(1.5, 2.6); 219 | try testing.expectEqual(Vec2.eql(a.norm(), Vec2.new(0.499722480, 0.866185605)), true); 220 | } 221 | 222 | test "zalgebra.Vec2.sub" { 223 | var a = Vec2.new(1, 2); 224 | var b = Vec2.new(2, 2); 225 | try testing.expectEqual(Vec2.eql(Vec2.sub(a, b), Vec2.new(-1, 0)), true); 226 | } 227 | 228 | test "zalgebra.Vec2.add" { 229 | var a = Vec2.new(1, 2); 230 | var b = Vec2.new(2, 2); 231 | try testing.expectEqual(Vec2.eql(Vec2.add(a, b), Vec2.new(3, 4)), true); 232 | } 233 | 234 | test "zalgebra.Vec2.scale" { 235 | var a = Vec2.new(1, 2); 236 | try testing.expectEqual(Vec2.eql(Vec2.scale(a, 5), Vec2.new(5, 10)), true); 237 | } 238 | 239 | test "zalgebra.Vec2.dot" { 240 | var a = Vec2.new(1.5, 2.6); 241 | var b = Vec2.new(2.5, 3.45); 242 | 243 | try testing.expectEqual(Vec2.dot(a, b), 12.7200002); 244 | } 245 | 246 | test "zalgebra.Vec2.lerp" { 247 | var a = Vec2.new(-10.0, 0.0); 248 | var b = Vec2.new(10.0, 10.0); 249 | 250 | try testing.expectEqual(Vec2.eql(Vec2.lerp(a, b, 0.5), Vec2.new(0.0, 5.0)), true); 251 | } 252 | 253 | test "zalgebra.Vec2.min" { 254 | var a = Vec2.new(10.0, -2.0); 255 | var b = Vec2.new(-10.0, 5.0); 256 | 257 | try testing.expectEqual(Vec2.eql(Vec2.min(a, b), Vec2.new(-10.0, -2.0)), true); 258 | } 259 | 260 | test "zalgebra.Vec2.max" { 261 | var a = Vec2.new(10.0, -2.0); 262 | var b = Vec2.new(-10.0, 5.0); 263 | 264 | try testing.expectEqual(Vec2.eql(Vec2.max(a, b), Vec2.new(10.0, 5.0)), true); 265 | } 266 | 267 | test "zalgebra.Vec2.fromSlice" { 268 | const array = [2]f32{ 2, 4 }; 269 | try testing.expectEqual(Vec2.eql(Vec2.fromSlice(&array), Vec2.new(2, 4)), true); 270 | } 271 | 272 | test "zalgebra.Vec2.cast" { 273 | const a = Vec2_i32.new(3, 6); 274 | const b = Vector2(usize).new(3, 6); 275 | 276 | try testing.expectEqual( 277 | Vector2(usize).eql(a.cast(usize), b), 278 | true, 279 | ); 280 | 281 | const c = Vec2.new(3.5, 6.5); 282 | const d = Vec2_f64.new(3.5, 6.5); 283 | 284 | try testing.expectEqual( 285 | Vec2_f64.eql(c.cast(f64), d), 286 | true, 287 | ); 288 | 289 | const e = Vec2_i32.new(3, 6); 290 | const f = Vec2.new(3.0, 6.0); 291 | 292 | try testing.expectEqual( 293 | Vec2.eql(e.cast(f32), f), 294 | true, 295 | ); 296 | 297 | const g = Vec2.new(3.0, 6.0); 298 | const h = Vec2_i32.new(3, 6); 299 | 300 | try testing.expectEqual( 301 | Vec2_i32.eql(g.cast(i32), h), 302 | true, 303 | ); 304 | } 305 | -------------------------------------------------------------------------------- /zalgebra/src/vec3.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const root = @import("main.zig"); 3 | const math = std.math; 4 | const assert = std.debug.assert; 5 | const panic = std.debug.panic; 6 | const testing = std.testing; 7 | 8 | pub const Vec3 = Vector3(f32); 9 | pub const Vec3_f64 = Vector3(f64); 10 | pub const Vec3_i32 = Vector3(i32); 11 | 12 | /// A 3 dimensional vector. 13 | pub fn Vector3(comptime T: type) type { 14 | if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) { 15 | @compileError("Vector3 not implemented for " ++ @typeName(T)); 16 | } 17 | 18 | return struct { 19 | x: T, 20 | y: T, 21 | z: T, 22 | 23 | const Self = @This(); 24 | 25 | /// Construct a vector from given 3 components. 26 | pub fn new(x: T, y: T, z: T) Self { 27 | return Self{ .x = x, .y = y, .z = z }; 28 | } 29 | 30 | /// Return component from given index. 31 | pub fn at(self: *const Self, index: i32) T { 32 | assert(index <= 2); 33 | 34 | if (index == 0) { 35 | return self.x; 36 | } else if (index == 1) { 37 | return self.y; 38 | } else { 39 | return self.z; 40 | } 41 | } 42 | 43 | /// Set all components to the same given value. 44 | pub fn set(val: T) Self { 45 | return Self.new(val, val, val); 46 | } 47 | 48 | /// Shorthand for writing vec3.new(0, 0, 0). 49 | pub fn zero() Self { 50 | return Self.new(0, 0, 0); 51 | } 52 | 53 | /// Shorthand for writing vec3.new(1, 1, 1). 54 | pub fn one() Self { 55 | return Self.new(1, 1, 1); 56 | } 57 | 58 | /// Shorthand for writing vec3.new(0, 1, 0). 59 | pub fn up() Self { 60 | return Self.new(0, 1, 0); 61 | } 62 | 63 | /// Shorthand for writing vec3.new(0, -1, 0). 64 | pub fn down() Self { 65 | return Self.new(0, -1, 0); 66 | } 67 | 68 | /// Shorthand for writing vec3.new(1, 0, 0). 69 | pub fn right() Self { 70 | return Self.new(1, 0, 0); 71 | } 72 | 73 | /// Shorthand for writing vec3.new(-1, 0, 0). 74 | pub fn left() Self { 75 | return Self.new(-1, 0, 0); 76 | } 77 | 78 | /// Shorthand for writing vec3.new(0, 0, -1). 79 | pub fn back() Self { 80 | return Self.new(0, 0, -1); 81 | } 82 | 83 | /// Shorthand for writing vec3.new(0, 0, 1). 84 | pub fn forward() Self { 85 | return Self.new(0, 0, 1); 86 | } 87 | 88 | /// Cast a type to another type. Only for integers and floats. 89 | /// It's like builtins: @intCast, @floatCast, @intToFloat, @floatToInt 90 | pub fn cast(self: Self, dest: anytype) Vector3(dest) { 91 | const source_info = @typeInfo(T); 92 | const dest_info = @typeInfo(dest); 93 | 94 | if (source_info == .Float and dest_info == .Int) { 95 | const x = @floatToInt(dest, self.x); 96 | const y = @floatToInt(dest, self.y); 97 | const z = @floatToInt(dest, self.z); 98 | return Vector3(dest).new(x, y, z); 99 | } 100 | 101 | if (source_info == .Int and dest_info == .Float) { 102 | const x = @intToFloat(dest, self.x); 103 | const y = @intToFloat(dest, self.y); 104 | const z = @intToFloat(dest, self.z); 105 | return Vector3(dest).new(x, y, z); 106 | } 107 | 108 | return switch (dest_info) { 109 | .Float => { 110 | const x = @floatCast(dest, self.x); 111 | const y = @floatCast(dest, self.y); 112 | const z = @floatCast(dest, self.z); 113 | return Vector3(dest).new(x, y, z); 114 | }, 115 | .Int => { 116 | const x = @intCast(dest, self.x); 117 | const y = @intCast(dest, self.y); 118 | const z = @intCast(dest, self.z); 119 | return Vector3(dest).new(x, y, z); 120 | }, 121 | else => panic( 122 | "Error, given type should be integers or float.\n", 123 | .{}, 124 | ), 125 | }; 126 | } 127 | 128 | /// Construct new vector from slice. 129 | pub fn fromSlice(slice: []const T) Self { 130 | return Self.new(slice[0], slice[1], slice[2]); 131 | } 132 | 133 | /// Transform vector to array. 134 | pub fn toArray(self: Self) [3]T { 135 | return .{ self.x, self.y, self.z }; 136 | } 137 | 138 | /// Return the angle in degrees between two vectors. 139 | pub fn getAngle(lhs: Self, rhs: Self) T { 140 | const dot_product = Self.dot(lhs.norm(), rhs.norm()); 141 | return root.toDegrees(math.acos(dot_product)); 142 | } 143 | 144 | /// Compute the length (magnitude) of given vector |a|. 145 | pub fn length(self: Self) T { 146 | return math.sqrt( 147 | (self.x * self.x) + (self.y * self.y) + (self.z * self.z), 148 | ); 149 | } 150 | 151 | /// Compute the distance between two points. 152 | pub fn distance(a: Self, b: Self) T { 153 | return math.sqrt( 154 | math.pow(T, b.x - a.x, 2) + 155 | math.pow(T, b.y - a.y, 2) + 156 | math.pow(T, b.z - a.z, 2), 157 | ); 158 | } 159 | 160 | /// Construct new normalized vector from a given vector. 161 | pub fn norm(self: Self) Self { 162 | var l = length(self); 163 | return Self.new(self.x / l, self.y / l, self.z / l); 164 | } 165 | 166 | pub fn eql(lhs: Self, rhs: Self) bool { 167 | return lhs.x == rhs.x and lhs.y == rhs.y and lhs.z == rhs.z; 168 | } 169 | 170 | /// Substraction between two given vector. 171 | pub fn sub(lhs: Self, rhs: Self) Self { 172 | return Self.new(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); 173 | } 174 | 175 | /// Addition betwen two given vector. 176 | pub fn add(lhs: Self, rhs: Self) Self { 177 | return Self.new(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); 178 | } 179 | 180 | /// Multiply each components by the given scalar. 181 | pub fn scale(v: Self, scalar: T) Self { 182 | return Self.new(v.x * scalar, v.y * scalar, v.z * scalar); 183 | } 184 | 185 | /// Compute the cross product from two vector. 186 | pub fn cross(lhs: Self, rhs: Self) Self { 187 | return Self.new( 188 | (lhs.y * rhs.z) - (lhs.z * rhs.y), 189 | (lhs.z * rhs.x) - (lhs.x * rhs.z), 190 | (lhs.x * rhs.y) - (lhs.y * rhs.x), 191 | ); 192 | } 193 | 194 | /// Return the dot product between two given vector. 195 | pub fn dot(lhs: Self, rhs: Self) T { 196 | return (lhs.x * rhs.x) + (lhs.y * rhs.y) + (lhs.z * rhs.z); 197 | } 198 | 199 | /// Lerp between two vectors. 200 | pub fn lerp(lhs: Self, rhs: Self, t: T) Self { 201 | const x = root.lerp(T, lhs.x, rhs.x, t); 202 | const y = root.lerp(T, lhs.y, rhs.y, t); 203 | const z = root.lerp(T, lhs.z, rhs.z, t); 204 | return Self.new(x, y, z); 205 | } 206 | 207 | /// Construct a new vector from the min components between two vectors. 208 | pub fn min(lhs: Self, rhs: Self) Self { 209 | return Self.new( 210 | math.min(lhs.x, rhs.x), 211 | math.min(lhs.y, rhs.y), 212 | math.min(lhs.z, rhs.z), 213 | ); 214 | } 215 | 216 | /// Construct a new vector from the max components between two vectors. 217 | pub fn max(lhs: Self, rhs: Self) Self { 218 | return Self.new( 219 | math.max(lhs.x, rhs.x), 220 | math.max(lhs.y, rhs.y), 221 | math.max(lhs.z, rhs.z), 222 | ); 223 | } 224 | }; 225 | } 226 | 227 | test "zalgebra.Vec3.init" { 228 | var a = Vec3.new(1.5, 2.6, 3.7); 229 | 230 | try testing.expectEqual(a.x, 1.5); 231 | try testing.expectEqual(a.y, 2.6); 232 | try testing.expectEqual(a.z, 3.7); 233 | } 234 | 235 | test "zalgebra.Vec3.set" { 236 | var a = Vec3.new(2.5, 2.5, 2.5); 237 | var b = Vec3.set(2.5); 238 | try testing.expectEqual(Vec3.eql(a, b), true); 239 | } 240 | 241 | test "zalgebra.Vec3.getAngle" { 242 | var a = Vec3.new(1, 0, 0); 243 | var b = Vec3.up(); 244 | var c = Vec3.new(-1, 0, 0); 245 | var d = Vec3.new(1, 1, 0); 246 | 247 | try testing.expectEqual(Vec3.getAngle(a, b), 90); 248 | try testing.expectEqual(Vec3.getAngle(a, c), 180); 249 | try testing.expectEqual(Vec3.getAngle(a, d), 45); 250 | } 251 | 252 | test "zalgebra.Vec3.toArray" { 253 | const a = Vec3.up().toArray(); 254 | const b = [_]f32{ 0, 1, 0 }; 255 | 256 | try testing.expectEqual(std.mem.eql(f32, &a, &b), true); 257 | } 258 | 259 | test "zalgebra.Vec3.eql" { 260 | var a = Vec3.new(1, 2, 3); 261 | var b = Vec3.new(1, 2, 3); 262 | var c = Vec3.new(1.5, 2, 3); 263 | try testing.expectEqual(Vec3.eql(a, b), true); 264 | try testing.expectEqual(Vec3.eql(a, c), false); 265 | } 266 | 267 | test "zalgebra.Vec3.length" { 268 | var a = Vec3.new(1.5, 2.6, 3.7); 269 | try testing.expectEqual(a.length(), 4.7644519); 270 | } 271 | 272 | test "zalgebra.Vec3.distance" { 273 | var a = Vec3.new(0, 0, 0); 274 | var b = Vec3.new(-1, 0, 0); 275 | var c = Vec3.new(0, 5, 0); 276 | 277 | try testing.expectEqual(Vec3.distance(a, b), 1); 278 | try testing.expectEqual(Vec3.distance(a, c), 5); 279 | } 280 | 281 | test "zalgebra.Vec3.normalize" { 282 | var a = Vec3.new(1.5, 2.6, 3.7); 283 | try testing.expectEqual(Vec3.eql(a.norm(), Vec3.new(0.314831584, 0.545708060, 0.776584625)), true); 284 | } 285 | 286 | test "zalgebra.Vec3.sub" { 287 | var a = Vec3.new(1, 2, 3); 288 | var b = Vec3.new(2, 2, 3); 289 | try testing.expectEqual(Vec3.eql(Vec3.sub(a, b), Vec3.new(-1, 0, 0)), true); 290 | } 291 | 292 | test "zalgebra.Vec3.add" { 293 | var a = Vec3.new(1, 2, 3); 294 | var b = Vec3.new(2, 2, 3); 295 | try testing.expectEqual(Vec3.eql(Vec3.add(a, b), Vec3.new(3, 4, 6)), true); 296 | } 297 | 298 | test "zalgebra.Vec3.scale" { 299 | var a = Vec3.new(1, 2, 3); 300 | try testing.expectEqual(Vec3.eql(Vec3.scale(a, 5), Vec3.new(5, 10, 15)), true); 301 | } 302 | 303 | test "zalgebra.Vec3.cross" { 304 | var a = Vec3.new(1.5, 2.6, 3.7); 305 | var b = Vec3.new(2.5, 3.45, 1.0); 306 | var c = Vec3.new(1.5, 2.6, 3.7); 307 | 308 | var result_1 = Vec3.cross(a, c); 309 | var result_2 = Vec3.cross(a, b); 310 | 311 | try testing.expectEqual(Vec3.eql(result_1, Vec3.new(0, 0, 0)), true); 312 | try testing.expectEqual(Vec3.eql( 313 | result_2, 314 | Vec3.new(-10.1650009, 7.75, -1.32499980), 315 | ), true); 316 | } 317 | 318 | test "zalgebra.Vec3.dot" { 319 | var a = Vec3.new(1.5, 2.6, 3.7); 320 | var b = Vec3.new(2.5, 3.45, 1.0); 321 | 322 | try testing.expectEqual(Vec3.dot(a, b), 16.42); 323 | } 324 | 325 | test "zalgebra.Vec3.lerp" { 326 | var a = Vec3.new(-10.0, 0.0, -10.0); 327 | var b = Vec3.new(10.0, 10.0, 10.0); 328 | 329 | try testing.expectEqual(Vec3.eql( 330 | Vec3.lerp(a, b, 0.5), 331 | Vec3.new(0.0, 5.0, 0.0), 332 | ), true); 333 | } 334 | 335 | test "zalgebra.Vec3.min" { 336 | var a = Vec3.new(10.0, -2.0, 0.0); 337 | var b = Vec3.new(-10.0, 5.0, 0.0); 338 | 339 | try testing.expectEqual(Vec3.eql( 340 | Vec3.min(a, b), 341 | Vec3.new(-10.0, -2.0, 0.0), 342 | ), true); 343 | } 344 | 345 | test "zalgebra.Vec3.max" { 346 | var a = Vec3.new(10.0, -2.0, 0.0); 347 | var b = Vec3.new(-10.0, 5.0, 0.0); 348 | 349 | try testing.expectEqual(Vec3.eql( 350 | Vec3.max(a, b), 351 | Vec3.new(10.0, 5.0, 0.0), 352 | ), true); 353 | } 354 | 355 | test "zalgebra.Vec3.at" { 356 | const t = Vec3.new(10.0, -2.0, 0.0); 357 | 358 | try testing.expectEqual(t.at(0), 10.0); 359 | try testing.expectEqual(t.at(1), -2.0); 360 | try testing.expectEqual(t.at(2), 0.0); 361 | } 362 | 363 | test "zalgebra.Vec3.fromSlice" { 364 | const array = [3]f32{ 2, 1, 4 }; 365 | try testing.expectEqual(Vec3.eql( 366 | Vec3.fromSlice(&array), 367 | Vec3.new(2, 1, 4), 368 | ), true); 369 | } 370 | 371 | test "zalgebra.Vec3.cast" { 372 | const a = Vec3_i32.new(3, 6, 2); 373 | const b = Vector3(usize).new(3, 6, 2); 374 | 375 | try testing.expectEqual( 376 | Vector3(usize).eql(a.cast(usize), b), 377 | true, 378 | ); 379 | 380 | const c = Vec3.new(3.5, 6.5, 2.0); 381 | const d = Vec3_f64.new(3.5, 6.5, 2); 382 | 383 | try testing.expectEqual( 384 | Vec3_f64.eql(c.cast(f64), d), 385 | true, 386 | ); 387 | 388 | const e = Vec3_i32.new(3, 6, 2); 389 | const f = Vec3.new(3.0, 6.0, 2.0); 390 | 391 | try testing.expectEqual( 392 | Vec3.eql(e.cast(f32), f), 393 | true, 394 | ); 395 | 396 | const g = Vec3.new(3.0, 6.0, 2.0); 397 | const h = Vec3_i32.new(3, 6, 2); 398 | 399 | try testing.expectEqual( 400 | Vec3_i32.eql(g.cast(i32), h), 401 | true, 402 | ); 403 | } 404 | -------------------------------------------------------------------------------- /zalgebra/src/vec4.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const root = @import("main.zig"); 3 | const math = std.math; 4 | const panic = std.debug.panic; 5 | const testing = std.testing; 6 | 7 | pub const Vec4 = Vector4(f32); 8 | pub const Vec4_f64 = Vector4(f64); 9 | pub const Vec4_i32 = Vector4(i32); 10 | 11 | /// A 4 dimensional vector. 12 | pub fn Vector4(comptime T: type) type { 13 | if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) { 14 | @compileError("Vector4 not implemented for " ++ @typeName(T)); 15 | } 16 | 17 | return struct { 18 | x: T, 19 | y: T, 20 | z: T, 21 | w: T, 22 | 23 | const Self = @This(); 24 | 25 | /// Constract vector from given 3 components. 26 | pub fn new(x: T, y: T, z: T, w: T) Self { 27 | return Self{ 28 | .x = x, 29 | .y = y, 30 | .z = z, 31 | .w = w, 32 | }; 33 | } 34 | 35 | /// Set all components to the same given value. 36 | pub fn set(val: T) Self { 37 | return Self.new(val, val, val, val); 38 | } 39 | 40 | pub fn zero() Self { 41 | return Self.new(0, 0, 0, 0); 42 | } 43 | 44 | pub fn one() Self { 45 | return Self.new(1, 1, 1, 1); 46 | } 47 | 48 | /// Cast a type to another type. Only for integers and floats. 49 | /// It's like builtins: @intCast, @floatCast, @intToFloat, @floatToInt 50 | pub fn cast(self: Self, dest: anytype) Vector4(dest) { 51 | const source_info = @typeInfo(T); 52 | const dest_info = @typeInfo(dest); 53 | 54 | if (source_info == .Float and dest_info == .Int) { 55 | const x = @floatToInt(dest, self.x); 56 | const y = @floatToInt(dest, self.y); 57 | const z = @floatToInt(dest, self.z); 58 | const w = @floatToInt(dest, self.w); 59 | return Vector4(dest).new(x, y, z, w); 60 | } 61 | 62 | if (source_info == .Int and dest_info == .Float) { 63 | const x = @intToFloat(dest, self.x); 64 | const y = @intToFloat(dest, self.y); 65 | const z = @intToFloat(dest, self.z); 66 | const w = @intToFloat(dest, self.w); 67 | return Vector4(dest).new(x, y, z, w); 68 | } 69 | 70 | return switch (dest_info) { 71 | .Float => { 72 | const x = @floatCast(dest, self.x); 73 | const y = @floatCast(dest, self.y); 74 | const z = @floatCast(dest, self.z); 75 | const w = @floatCast(dest, self.w); 76 | return Vector4(dest).new(x, y, z, w); 77 | }, 78 | .Int => { 79 | const x = @intCast(dest, self.x); 80 | const y = @intCast(dest, self.y); 81 | const z = @intCast(dest, self.z); 82 | const w = @intCast(dest, self.w); 83 | return Vector4(dest).new(x, y, z, w); 84 | }, 85 | else => panic( 86 | "Error, given type should be integers or float.\n", 87 | .{}, 88 | ), 89 | }; 90 | } 91 | 92 | /// Construct new vector from slice. 93 | pub fn fromSlice(slice: []const T) Self { 94 | return Self.new(slice[0], slice[1], slice[2], slice[3]); 95 | } 96 | 97 | /// Transform vector to array. 98 | pub fn toArray(self: Self) [4]T { 99 | return .{ self.x, self.y, self.z, self.w }; 100 | } 101 | 102 | /// Compute the length (magnitude) of given vector |a|. 103 | pub fn length(self: Self) T { 104 | return math.sqrt( 105 | (self.x * self.x) + 106 | (self.y * self.y) + 107 | (self.z * self.z) + 108 | (self.w * self.w), 109 | ); 110 | } 111 | 112 | /// Compute the distance between two points. 113 | pub fn distance(a: Self, b: Self) T { 114 | return math.sqrt( 115 | math.pow(T, b.x - a.x, 2) + 116 | math.pow(T, b.y - a.y, 2) + 117 | math.pow(T, b.z - a.z, 2) + 118 | math.pow(T, b.w - a.w, 2), 119 | ); 120 | } 121 | 122 | /// Construct new normalized vector from a given vector. 123 | pub fn norm(self: Self) Self { 124 | var l = length(self); 125 | return Self.new(self.x / l, self.y / l, self.z / l, self.w / l); 126 | } 127 | 128 | pub fn is_eq(left: Self, right: Self) bool { 129 | return left.x == right.x and 130 | left.y == right.y and 131 | left.z == right.z and 132 | left.w == right.w; 133 | } 134 | 135 | /// Substraction between two given vector. 136 | pub fn sub(left: Self, right: Self) Self { 137 | return Self.new( 138 | left.x - right.x, 139 | left.y - right.y, 140 | left.z - right.z, 141 | left.w - right.w, 142 | ); 143 | } 144 | 145 | /// Addition betwen two given vector. 146 | pub fn add(left: Self, right: Self) Self { 147 | return Self.new( 148 | left.x + right.x, 149 | left.y + right.y, 150 | left.z + right.z, 151 | left.w + right.w, 152 | ); 153 | } 154 | 155 | /// Multiply each components by the given scalar. 156 | pub fn scale(v: Self, scalar: T) Self { 157 | return Self.new( 158 | v.x * scalar, 159 | v.y * scalar, 160 | v.z * scalar, 161 | v.w * scalar, 162 | ); 163 | } 164 | 165 | /// Return the dot product between two given vector. 166 | pub fn dot(left: Self, right: Self) T { 167 | return (left.x * right.x) + (left.y * right.y) + (left.z * right.z) + (left.w * right.w); 168 | } 169 | 170 | /// Lerp between two vectors. 171 | pub fn lerp(left: Self, right: Self, t: T) Self { 172 | const x = root.lerp(T, left.x, right.x, t); 173 | const y = root.lerp(T, left.y, right.y, t); 174 | const z = root.lerp(T, left.z, right.z, t); 175 | const w = root.lerp(T, left.w, right.w, t); 176 | return Self.new(x, y, z, w); 177 | } 178 | 179 | /// Construct a new vector from the min components between two vectors. 180 | pub fn min(left: Self, right: Self) Self { 181 | return Self.new( 182 | math.min(left.x, right.x), 183 | math.min(left.y, right.y), 184 | math.min(left.z, right.z), 185 | math.min(left.w, right.w), 186 | ); 187 | } 188 | 189 | /// Construct a new vector from the max components between two vectors. 190 | pub fn max(left: Self, right: Self) Self { 191 | return Self.new( 192 | math.max(left.x, right.x), 193 | math.max(left.y, right.y), 194 | math.max(left.z, right.z), 195 | math.max(left.w, right.w), 196 | ); 197 | } 198 | }; 199 | } 200 | 201 | test "zalgebra.Vec4.init" { 202 | var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 4.7); 203 | 204 | try testing.expectEqual(_vec_0.x, 1.5); 205 | try testing.expectEqual(_vec_0.y, 2.6); 206 | try testing.expectEqual(_vec_0.z, 3.7); 207 | try testing.expectEqual(_vec_0.w, 4.7); 208 | } 209 | 210 | test "zalgebra.Vec4.is_eq" { 211 | var _vec_0 = Vec4.new(1, 2, 3, 4); 212 | var _vec_1 = Vec4.new(1, 2, 3, 4); 213 | var _vec_2 = Vec4.new(1, 2, 3, 5); 214 | try testing.expectEqual(Vec4.is_eq(_vec_0, _vec_1), true); 215 | try testing.expectEqual(Vec4.is_eq(_vec_0, _vec_2), false); 216 | } 217 | 218 | test "zalgebra.Vec4.set" { 219 | var _vec_0 = Vec4.new(2.5, 2.5, 2.5, 2.5); 220 | var _vec_1 = Vec4.set(2.5); 221 | try testing.expectEqual(Vec4.is_eq(_vec_0, _vec_1), true); 222 | } 223 | 224 | test "zalgebra.Vec2.toArray" { 225 | const _vec_0 = Vec4.new(0, 1, 0, 1).toArray(); 226 | const _vec_1 = [_]f32{ 0, 1, 0, 1 }; 227 | 228 | try testing.expectEqual(std.mem.eql(f32, &_vec_0, &_vec_1), true); 229 | } 230 | 231 | test "zalgebra.Vec4.length" { 232 | var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 4.7); 233 | try testing.expectEqual(_vec_0.length(), 6.69253301); 234 | } 235 | 236 | test "zalgebra.Vec4.distance" { 237 | var a = Vec4.new(0, 0, 0, 0); 238 | var b = Vec4.new(-1, 0, 0, 0); 239 | var c = Vec4.new(0, 5, 0, 0); 240 | 241 | try testing.expectEqual(Vec4.distance(a, b), 1); 242 | try testing.expectEqual(Vec4.distance(a, c), 5); 243 | } 244 | 245 | test "zalgebra.Vec4.normalize" { 246 | var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 4.0); 247 | try testing.expectEqual(Vec4.is_eq( 248 | _vec_0.norm(), 249 | Vec4.new(0.241121411, 0.417943745, 0.594766139, 0.642990410), 250 | ), true); 251 | } 252 | 253 | test "zalgebra.Vec4.sub" { 254 | var _vec_0 = Vec4.new(1, 2, 3, 6); 255 | var _vec_1 = Vec4.new(2, 2, 3, 5); 256 | try testing.expectEqual(Vec4.is_eq( 257 | Vec4.sub(_vec_0, _vec_1), 258 | Vec4.new(-1, 0, 0, 1), 259 | ), true); 260 | } 261 | 262 | test "zalgebra.Vec4.add" { 263 | var _vec_0 = Vec4.new(1, 2, 3, 5); 264 | var _vec_1 = Vec4.new(2, 2, 3, 6); 265 | try testing.expectEqual(Vec4.is_eq( 266 | Vec4.add(_vec_0, _vec_1), 267 | Vec4.new(3, 4, 6, 11), 268 | ), true); 269 | } 270 | 271 | test "zalgebra.Vec4.scale" { 272 | var _vec_0 = Vec4.new(1, 2, 3, 4); 273 | try testing.expectEqual(Vec4.is_eq( 274 | Vec4.scale(_vec_0, 5), 275 | Vec4.new(5, 10, 15, 20), 276 | ), true); 277 | } 278 | 279 | test "zalgebra.Vec4.dot" { 280 | var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 5); 281 | var _vec_1 = Vec4.new(2.5, 3.45, 1.0, 1); 282 | 283 | try testing.expectEqual(Vec4.dot(_vec_0, _vec_1), 21.4200000); 284 | } 285 | 286 | test "zalgebra.Vec4.lerp" { 287 | var _vec_0 = Vec4.new(-10.0, 0.0, -10.0, -10.0); 288 | var _vec_1 = Vec4.new(10.0, 10.0, 10.0, 10.0); 289 | 290 | try testing.expectEqual(Vec4.is_eq( 291 | Vec4.lerp(_vec_0, _vec_1, 0.5), 292 | Vec4.new(0.0, 5.0, 0.0, 0.0), 293 | ), true); 294 | } 295 | 296 | test "zalgebra.Vec4.min" { 297 | var _vec_0 = Vec4.new(10.0, -2.0, 0.0, 1.0); 298 | var _vec_1 = Vec4.new(-10.0, 5.0, 0.0, 1.01); 299 | 300 | try testing.expectEqual(Vec4.is_eq( 301 | Vec4.min(_vec_0, _vec_1), 302 | Vec4.new(-10.0, -2.0, 0.0, 1.0), 303 | ), true); 304 | } 305 | 306 | test "zalgebra.Vec4.max" { 307 | var _vec_0 = Vec4.new(10.0, -2.0, 0.0, 1.0); 308 | var _vec_1 = Vec4.new(-10.0, 5.0, 0.0, 1.01); 309 | 310 | try testing.expectEqual(Vec4.is_eq( 311 | Vec4.max(_vec_0, _vec_1), 312 | Vec4.new(10.0, 5.0, 0.0, 1.01), 313 | ), true); 314 | } 315 | 316 | test "zalgebra.Vec2.fromSlice" { 317 | const array = [4]f32{ 2, 4, 3, 6 }; 318 | try testing.expectEqual(Vec4.is_eq( 319 | Vec4.fromSlice(&array), 320 | Vec4.new(2, 4, 3, 6), 321 | ), true); 322 | } 323 | 324 | test "zalgebra.Vec4.cast" { 325 | const a = Vec4_i32.new(3, 6, 2, 0); 326 | const b = Vector4(usize).new(3, 6, 2, 0); 327 | 328 | try testing.expectEqual( 329 | Vector4(usize).is_eq(a.cast(usize), b), 330 | true, 331 | ); 332 | 333 | const c = Vec4.new(3.5, 6.5, 2.0, 0); 334 | const d = Vec4_f64.new(3.5, 6.5, 2, 0.0); 335 | 336 | try testing.expectEqual( 337 | Vec4_f64.is_eq(c.cast(f64), d), 338 | true, 339 | ); 340 | 341 | const e = Vec4_i32.new(3, 6, 2, 0); 342 | const f = Vec4.new(3.0, 6.0, 2.0, 0.0); 343 | 344 | try testing.expectEqual( 345 | Vec4.is_eq(e.cast(f32), f), 346 | true, 347 | ); 348 | 349 | const g = Vec4.new(3.0, 6.0, 2.0, 0.0); 350 | const h = Vec4_i32.new(3, 6, 2, 0); 351 | 352 | try testing.expectEqual( 353 | Vec4_i32.is_eq(g.cast(i32), h), 354 | true, 355 | ); 356 | } 357 | --------------------------------------------------------------------------------