├── .github └── mogwai.png ├── .gitignore ├── .gitmodules ├── README.md ├── build.zig ├── exemples ├── _glfw_opengl.zig ├── assets │ ├── default.frag │ ├── default.vert │ └── grid_debug.png └── common │ ├── c.zig │ ├── camera.zig │ ├── opengl.zig │ ├── primitive.zig │ ├── shader.zig │ └── transform.zig ├── src └── main.zig └── zig-out └── bin └── glfw_opengl /.github/mogwai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kooparse/mogwai/90fbdb659f42bc7bbd9ee8d6612cd4ebb4ea7ea9/.github/mogwai.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | build_runner.zig 3 | 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/libs/zalgebra"] 2 | path = src/libs/zalgebra 3 | url = git@github.com:kooparse/zalgebra.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mogwai 2 | 3 | Graphic utility used to manipulate objects in 3D for scene editing (commonly called Gizmo). 4 | 5 | It's a straightforward and simple Gizmo, all the manipulation is computed in this library 6 | while it gives you all the verticies for your renderer; At the end, 7 | you'll get a decomposed matrix (three vec3 for translation/rotation/scale). 8 | 9 | This library uses only [zalgebra](https://github.com/kooparse/zalgebra) and the `std`! 10 | 11 | preview 12 | 13 | ## Examples 14 | 15 | ```zig 16 | usingnamespace @import("mogwai"); 17 | 18 | pub fn main () void { 19 | var gizmo = Mogwai(.{ 20 | .screen_width = 800, 21 | .screen_height = 600, 22 | .snap_axis = 0.5, 23 | .snap_angle = 5 24 | }); 25 | 26 | // Get all vertices for your renderer. 27 | const xy_panel = gizmo.meshes.move_panels.xy; 28 | const yz_panel = gizmo.meshes.move_panels.yz 29 | // See https://github.com/kooparse/mogwai/blob/master/exemples/_glfw_opengl.zig#L62-L73 30 | // for all meshes. 31 | 32 | // Mode is an enum containing all different modes 33 | // for the gizmo (move, rotate, scale, none). 34 | var gizmo_mode = Mode.Move; 35 | 36 | while(true) { 37 | // After pulling cursor state and positions: 38 | gizmo.setCursor(pos_x, pos_y, is_pressed); 39 | // After updating the view matrix (camera moving...). 40 | gizmo.setCamera(view, proj); 41 | 42 | // You just have to pass a mode and the model matrix from the 43 | // selected object from your scene. 44 | if (gizmo.manipulate(target_model_matrix, gizmo_mode)) |result| { 45 | switch (result.mode) { 46 | Mode.Move => { 47 | target.transform.position = result.position; 48 | }, 49 | Mode.Rotate => { 50 | target.transform.rotation = result.rotation.extractRotation(); 51 | }, 52 | Mode.Scale => { 53 | target.transform.scale = result.scale; 54 | }, 55 | else => {}, 56 | } 57 | } 58 | 59 | // Draw all the meshes 60 | your_renderer.draw(&yz_panel, gizmo.is_hover(GizmoItem.PanelYZ)); 61 | } 62 | } 63 | ``` 64 | See https://github.com/kooparse/mogwai/blob/master/exemples/_glfw_opengl.zig for a real exemple. 65 | 66 | ## Documentation 67 | 68 | ### Config 69 | 70 | Field | Type | Description 71 | ------------ | ------------- | ------------- 72 | screen_width | i32 | Width of the screen (required) 73 | screen_height | i32 | Height of the screen (required) 74 | dpi | i32 | Pixel ratio of your monitor 75 | snap | ?f32 | Snap when dragging gizmo, value is equals to floor factor 76 | arcball_radius | f32 | Radius of the rotation circles 77 | arcball_thickness | f32 | Width of the rotation circle borders 78 | panel_size| f32 | The width/height of panels 79 | panel_offset| f32 | Offset of the panels from the origin position 80 | panel_width| f32 | Plans width for panels 81 | axis_length | f32 | Length of arrows 82 | axis_size | f32 | The width/height of arrows 83 | scale_box_size | f32 | Size of the scaler boxes 84 | 85 | 86 | ## Contributing to the project 87 | 88 | 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. 89 | 90 | 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. 91 | 92 | 93 | ## Thanks 94 | This project is inspired by [tinygizmo](https://github.com/ddiakopoulos/tinygizmo) and [ImGuizmo](https://github.com/CedricGuillemet/ImGuizmo). 95 | 96 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Builder = std.build.Builder; 3 | const Pkg = std.build.Pkg; 4 | const builtin = @import("builtin"); 5 | 6 | pub fn build(b: *Builder) void { 7 | const mode = b.standardReleaseOptions(); 8 | 9 | // Build step for the library 10 | { 11 | var tests = b.addTest("src/main.zig"); 12 | tests.addPackage(.{ 13 | .name = "zalgebra", 14 | .path = "src/libs/zalgebra/src/main.zig", 15 | }); 16 | tests.setBuildMode(mode); 17 | 18 | const test_step = b.step("test", "Run tests"); 19 | test_step.dependOn(&tests.step); 20 | } 21 | 22 | // Build step for exemples. 23 | { 24 | var exe = b.addExecutable("glfw_opengl", "exemples/_glfw_opengl.zig"); 25 | exe.setBuildMode(mode); 26 | 27 | const zalgebra = Pkg{ 28 | .name = "zalgebra", 29 | .path = "src/libs/zalgebra/src/main.zig", 30 | }; 31 | 32 | exe.addPackage(zalgebra); 33 | exe.addPackage(.{ 34 | .name = "mogwai", 35 | .path = "src/main.zig", 36 | .dependencies = &[_]Pkg{zalgebra}, 37 | }); 38 | 39 | switch (builtin.os.tag) { 40 | .macos => { 41 | exe.addFrameworkDir("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"); 42 | exe.linkFramework("OpenGL"); 43 | }, 44 | else => { 45 | @panic("Don't know how to build on your system."); 46 | }, 47 | } 48 | 49 | exe.linkSystemLibrary("glfw"); 50 | exe.linkSystemLibrary("epoxy"); 51 | exe.install(); 52 | 53 | const play = b.step("run", "Run Mogwai exemple"); 54 | const run = exe.run(); 55 | run.step.dependOn(b.getInstallStep()); 56 | 57 | play.dependOn(&run.step); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /exemples/_glfw_opengl.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("common/c.zig"); 3 | const primitive = @import("common/primitive.zig"); 4 | const opengl = @import("common/opengl.zig"); 5 | usingnamespace @import("common/camera.zig"); 6 | usingnamespace @import("common/shader.zig"); 7 | 8 | usingnamespace @import("zalgebra"); 9 | usingnamespace @import("mogwai"); 10 | 11 | const panic = std.debug.panic; 12 | const print = std.debug.print; 13 | 14 | const WINDOW_WIDTH: i32 = 1200; 15 | const WINDOW_HEIGHT: i32 = 800; 16 | const WINDOW_DPI: i32 = 2; 17 | const WINDOW_NAME = "Game"; 18 | 19 | pub fn main() !void { 20 | if (c.glfwInit() == c.GL_FALSE) { 21 | panic("Failed to intialize GLFW.\n", .{}); 22 | } 23 | 24 | c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MAJOR, 3); 25 | c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MINOR, 2); 26 | c.glfwWindowHint(c.GLFW_OPENGL_FORWARD_COMPAT, c.GL_TRUE); 27 | c.glfwWindowHint(c.GLFW_OPENGL_PROFILE, c.GLFW_OPENGL_CORE_PROFILE); 28 | 29 | const window = c.glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_NAME, null, null) orelse { 30 | panic("Unable to create window.\n", .{}); 31 | }; 32 | 33 | c.glfwMakeContextCurrent(window); 34 | c.glEnable(c.GL_DEPTH_TEST); 35 | 36 | c.glfwSwapBuffers(window); 37 | c.glfwPollEvents(); 38 | 39 | defer c.glfwDestroyWindow(window); 40 | defer c.glfwTerminate(); 41 | 42 | const default_vert = @embedFile("assets/default.vert"); 43 | const default_frag = @embedFile("assets/default.frag"); 44 | 45 | const shader = try Shader.create("default_shader", default_vert, default_frag); 46 | 47 | // Our gizmo with some options. 48 | var gizmo = Mogwai.new(.{ 49 | .screen_width = WINDOW_WIDTH, 50 | .screen_height = WINDOW_HEIGHT, 51 | .dpi = WINDOW_DPI, 52 | }); 53 | 54 | var camera = Camera.init(Vec3.new(0.2, 0.8, -3)); 55 | var view = lookAt(camera.position, Vec3.add(camera.position, camera.front), Vec3.up()); 56 | const proj = perspective(45, @intToFloat(f32, WINDOW_WIDTH) / @intToFloat(f32, WINDOW_HEIGHT), 0.1, 100); 57 | 58 | var our_target_object = try opengl.GeometryObject.new(&primitive.CUBE_VERTICES, &primitive.CUBE_INDICES, &primitive.CUBE_UV_COORDS, &primitive.CUBE_COLORS, null); 59 | defer our_target_object.deinit(); 60 | 61 | var x_axis = try opengl.GeometryObject.new(&gizmo.meshes.move_axis.x, &gizmo.meshes.move_axis.indices, null, null, null); 62 | var y_axis = try opengl.GeometryObject.new(&gizmo.meshes.move_axis.y, &gizmo.meshes.move_axis.indices, null, null, null); 63 | var z_axis = try opengl.GeometryObject.new(&gizmo.meshes.move_axis.z, &gizmo.meshes.move_axis.indices, null, null, null); 64 | var yz = try opengl.GeometryObject.new(&gizmo.meshes.move_panels.yz, &gizmo.meshes.move_panels.indices, null, null, null); 65 | var xz = try opengl.GeometryObject.new(&gizmo.meshes.move_panels.xz, &gizmo.meshes.move_panels.indices, null, null, null); 66 | var xy = try opengl.GeometryObject.new(&gizmo.meshes.move_panels.xy, &gizmo.meshes.move_panels.indices, null, null, null); 67 | var scale_x = try opengl.GeometryObject.new(&gizmo.meshes.scale_axis.x, &gizmo.meshes.scale_axis.indices, null, null, null); 68 | var scale_y = try opengl.GeometryObject.new(&gizmo.meshes.scale_axis.y, &gizmo.meshes.scale_axis.indices, null, null, null); 69 | var scale_z = try opengl.GeometryObject.new(&gizmo.meshes.scale_axis.z, &gizmo.meshes.scale_axis.indices, null, null, null); 70 | var rotate_x = try opengl.GeometryObject.new(&gizmo.meshes.rotate_axis.x, null, null, null, null); 71 | var rotate_y = try opengl.GeometryObject.new(&gizmo.meshes.rotate_axis.y, null, null, null, null); 72 | var rotate_z = try opengl.GeometryObject.new(&gizmo.meshes.rotate_axis.z, null, null, null, null); 73 | 74 | defer yz.deinit(); 75 | defer xz.deinit(); 76 | defer xy.deinit(); 77 | defer x_axis.deinit(); 78 | defer y_axis.deinit(); 79 | defer z_axis.deinit(); 80 | defer scale_x.deinit(); 81 | defer scale_y.deinit(); 82 | defer scale_z.deinit(); 83 | defer rotate_x.deinit(); 84 | defer rotate_y.deinit(); 85 | defer rotate_z.deinit(); 86 | 87 | var delta_time: f64 = 0.0; 88 | var last_frame: f64 = 0.0; 89 | 90 | var shouldClose = false; 91 | var gizmo_mode = Mode.Move; 92 | 93 | while (!shouldClose) { 94 | c.glfwPollEvents(); 95 | c.glClear(c.GL_COLOR_BUFFER_BIT | c.GL_DEPTH_BUFFER_BIT); 96 | 97 | shouldClose = c.glfwWindowShouldClose(window) == c.GL_TRUE or 98 | c.glfwGetKey(window, c.GLFW_KEY_ESCAPE) == c.GLFW_PRESS; 99 | 100 | // Compute times between frames (delta time). 101 | { 102 | var current_time = c.glfwGetTime(); 103 | delta_time = current_time - last_frame; 104 | last_frame = current_time; 105 | } 106 | 107 | c.glUseProgram(shader.program_id); 108 | shader.setMat4("projection", &proj); 109 | shader.setMat4("view", &view); 110 | 111 | const is_pressed = c.glfwGetMouseButton(window, c.GLFW_MOUSE_BUTTON_LEFT) == c.GLFW_PRESS; 112 | var pos_x: f64 = 0; 113 | var pos_y: f64 = 0; 114 | c.glfwGetCursorPos(window, &pos_x, &pos_y); 115 | 116 | camera.update(window, delta_time, c.glfwGetKey(window, c.GLFW_KEY_LEFT_SHIFT) == c.GLFW_PRESS); 117 | 118 | if (c.glfwGetKey(window, c.GLFW_KEY_LEFT_SHIFT) == c.GLFW_PRESS) { 119 | c.glfwSetInputMode(window, c.GLFW_CURSOR, c.GLFW_CURSOR_DISABLED); 120 | view = lookAt(camera.position, Vec3.add(camera.position, camera.front), camera.up); 121 | } else { 122 | c.glfwSetInputMode(window, c.GLFW_CURSOR, c.GLFW_CURSOR_NORMAL); 123 | } 124 | 125 | gizmo.setCursor(pos_x, pos_y, is_pressed); 126 | gizmo.setCamera(view, proj); 127 | 128 | if (c.glfwGetKey(window, c.GLFW_KEY_M) == c.GLFW_PRESS) { 129 | gizmo_mode = Mode.Move; 130 | } else if (c.glfwGetKey(window, c.GLFW_KEY_R) == c.GLFW_PRESS) { 131 | gizmo_mode = Mode.Rotate; 132 | } else if (c.glfwGetKey(window, c.GLFW_KEY_T) == c.GLFW_PRESS) { 133 | gizmo_mode = Mode.Scale; 134 | } 135 | 136 | const target_model = our_target_object.transform.get_model(); 137 | if (gizmo.manipulate(target_model, gizmo_mode)) |result| { 138 | switch (gizmo_mode) { 139 | Mode.Move => { 140 | our_target_object.transform.position = result.position; 141 | }, 142 | Mode.Rotate => { 143 | our_target_object.transform.rotation = result.rotation; 144 | }, 145 | Mode.Scale => { 146 | our_target_object.transform.scale = result.scale; 147 | }, 148 | else => {}, 149 | } 150 | } 151 | 152 | // Render our target object. 153 | opengl.draw_geometry(&our_target_object, &shader, target_model, null, false); 154 | 155 | // Render our magnifico gizmo! 156 | { 157 | c.glDisable(c.GL_DEPTH_TEST); 158 | c.glEnable(c.GL_BLEND); 159 | c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); 160 | 161 | const alpha = 0.5; 162 | const model = Mat4.fromTranslate(gizmo.position); 163 | 164 | switch (gizmo_mode) { 165 | Mode.Move => { 166 | // Render all panels. 167 | opengl.draw_geometry(&yz, &shader, model, Vec4.new(0, 100, 255, if (gizmo.isHover(GizmoItem.PanelYZ)) 1 else alpha), true); 168 | opengl.draw_geometry(&xz, &shader, model, Vec4.new(100, 255, 0, if (gizmo.isHover(GizmoItem.PanelXZ)) 1 else alpha), true); 169 | opengl.draw_geometry(&xy, &shader, model, Vec4.new(255, 0, 255, if (gizmo.isHover(GizmoItem.PanelXY)) 1 else alpha), true); 170 | 171 | // Render all axis. 172 | opengl.draw_geometry(&x_axis, &shader, model, Vec4.new(255, 0, 0, if (gizmo.isHover(GizmoItem.ArrowX)) 1 else alpha), true); 173 | opengl.draw_geometry(&y_axis, &shader, model, Vec4.new(0, 255, 0, if (gizmo.isHover(GizmoItem.ArrowY)) 1 else alpha), true); 174 | opengl.draw_geometry(&z_axis, &shader, model, Vec4.new(0, 0, 255, if (gizmo.isHover(GizmoItem.ArrowZ)) 1 else alpha), true); 175 | }, 176 | Mode.Rotate => { 177 | c.glEnable(c.GL_DEPTH_TEST); 178 | opengl.draw_geometry(&rotate_x, &shader, model, Vec4.new(255, 0, 0, if (gizmo.isHover(GizmoItem.RotateZ)) 1 else alpha), true); 179 | opengl.draw_geometry(&rotate_y, &shader, model, Vec4.new(0, 255, 0, if (gizmo.isHover(GizmoItem.RotateX)) 1 else alpha), true); 180 | opengl.draw_geometry(&rotate_z, &shader, model, Vec4.new(0, 0, 255, if (gizmo.isHover(GizmoItem.RotateY)) 1 else alpha), true); 181 | c.glDisable(c.GL_DEPTH_TEST); 182 | }, 183 | Mode.Scale => { 184 | opengl.draw_geometry(&scale_x, &shader, model, Vec4.new(255, 0, 0, if (gizmo.isHover(GizmoItem.ScalerX)) 1 else alpha), true); 185 | opengl.draw_geometry(&scale_y, &shader, model, Vec4.new(0, 255, 0, if (gizmo.isHover(GizmoItem.ScalerY)) 1 else alpha), true); 186 | opengl.draw_geometry(&scale_z, &shader, model, Vec4.new(0, 0, 255, if (gizmo.isHover(GizmoItem.ScalerZ)) 1 else alpha), true); 187 | }, 188 | else => {} 189 | } 190 | 191 | c.glDisable(c.GL_BLEND); 192 | c.glEnable(c.GL_DEPTH_TEST); 193 | } 194 | 195 | c.glfwSwapBuffers(window); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /exemples/assets/default.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | in vec2 TexCoord; 5 | in vec3 MeshColor; 6 | 7 | uniform sampler2D ourTexture; 8 | uniform vec4 color; 9 | uniform bool with_texture; 10 | uniform bool with_color; 11 | 12 | void main() { 13 | vec4 c = color; 14 | 15 | if (with_texture) { 16 | c = texture(ourTexture, TexCoord); 17 | } 18 | 19 | if (with_color) { 20 | c = vec4(MeshColor, 1); 21 | } 22 | 23 | FragColor = c; 24 | } 25 | -------------------------------------------------------------------------------- /exemples/assets/default.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in vec2 aTexCoord; 4 | layout (location = 2) in vec3 aMeshColor; 5 | 6 | out vec2 TexCoord; 7 | out vec3 MeshColor; 8 | 9 | uniform mat4 projection; 10 | uniform mat4 view; 11 | uniform mat4 model; 12 | 13 | void main() { 14 | gl_Position = projection * view * model * vec4(aPos, 1.0); 15 | TexCoord = aTexCoord; 16 | MeshColor = aMeshColor; 17 | } 18 | -------------------------------------------------------------------------------- /exemples/assets/grid_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kooparse/mogwai/90fbdb659f42bc7bbd9ee8d6612cd4ebb4ea7ea9/exemples/assets/grid_debug.png -------------------------------------------------------------------------------- /exemples/common/c.zig: -------------------------------------------------------------------------------- 1 | pub usingnamespace @cImport({ 2 | @cDefine("GL_SILENCE_DEPRECATION", ""); 3 | @cDefine("GLFW_INCLUDE_GLCOREARB", ""); 4 | @cInclude("GLFW/glfw3.h"); 5 | }); 6 | -------------------------------------------------------------------------------- /exemples/common/camera.zig: -------------------------------------------------------------------------------- 1 | const c = @import("c.zig"); 2 | const math = @import("std").math; 3 | usingnamespace @import("zalgebra"); 4 | 5 | pub const Camera = struct { 6 | position: Vec3, 7 | front: Vec3 = Vec3.new(0, 0, 1), 8 | up: Vec3 = Vec3.new(0, 1, 0), 9 | yaw: f64 = 90, 10 | pitch: f64 = 0, 11 | 12 | speed: f32 = 3, 13 | is_first_mouse: bool = true, 14 | 15 | last_cursor_pos_x: f64 = 0, 16 | last_cursor_pos_y: f64 = 0, 17 | 18 | should_update: bool = true, 19 | 20 | const Self = @This(); 21 | 22 | pub fn init(position: Vec3) Self { 23 | return Self{ .position = position }; 24 | } 25 | 26 | pub fn update(self: *Self, window: *c.GLFWwindow, delta_time: f64, should_update: bool) void { 27 | const speed: f32 = self.speed * @floatCast(f32, delta_time); 28 | 29 | self.should_update = should_update; 30 | 31 | if (self.should_update) { 32 | if (c.glfwGetKey(window, c.GLFW_KEY_W) == c.GLFW_PRESS) { 33 | self.position = self.position.add(self.front.scale(speed)); 34 | } 35 | 36 | if (c.glfwGetKey(window, c.GLFW_KEY_W) == c.GLFW_PRESS) { 37 | self.position = self.position.add(self.front.scale(speed)); 38 | } 39 | 40 | if (c.glfwGetKey(window, c.GLFW_KEY_S) == c.GLFW_PRESS) { 41 | self.position = self.position.sub(self.front.scale(speed)); 42 | } 43 | 44 | if (c.glfwGetKey(window, c.GLFW_KEY_A) == c.GLFW_PRESS) { 45 | self.position = self.position.sub((self.front.cross(self.up).norm()).scale(speed)); 46 | } 47 | 48 | if (c.glfwGetKey(window, c.GLFW_KEY_D) == c.GLFW_PRESS) { 49 | self.position = self.position.add((self.front.cross(self.up).norm()).scale(speed)); 50 | } 51 | 52 | if (c.glfwGetKey(window, c.GLFW_KEY_E) == c.GLFW_PRESS) { 53 | self.position = self.position.add(self.up.scale(speed)); 54 | } 55 | 56 | if (c.glfwGetKey(window, c.GLFW_KEY_Q) == c.GLFW_PRESS) { 57 | self.position = self.position.sub(self.up.scale(speed)); 58 | } 59 | } 60 | 61 | var pos_x: f64 = 0; 62 | var pos_y: f64 = 0; 63 | c.glfwGetCursorPos(window, &pos_x, &pos_y); 64 | 65 | if (self.is_first_mouse or !self.should_update) { 66 | self.last_cursor_pos_x = pos_x; 67 | self.last_cursor_pos_y = pos_y; 68 | self.is_first_mouse = false; 69 | } 70 | 71 | var x_offset = pos_x - self.last_cursor_pos_x; 72 | var y_offset = self.last_cursor_pos_y - pos_y; 73 | self.last_cursor_pos_x = pos_x; 74 | self.last_cursor_pos_y = pos_y; 75 | 76 | if (!self.should_update) { 77 | return; 78 | } 79 | 80 | x_offset *= speed; 81 | y_offset *= speed; 82 | 83 | self.yaw += x_offset; 84 | self.pitch += y_offset; 85 | 86 | if (self.pitch > 89.0) 87 | self.pitch = 89.0; 88 | if (self.pitch < -89.0) 89 | self.pitch = -89.0; 90 | 91 | var direction: Vec3 = undefined; 92 | direction.x = @floatCast(f32, math.cos(toRadians(self.yaw)) * math.cos(toRadians(self.pitch))); 93 | direction.y = @floatCast(f32, math.sin(toRadians(self.pitch))); 94 | direction.z = @floatCast(f32, math.sin(toRadians(self.yaw)) * math.cos(toRadians(self.pitch))); 95 | self.front = direction.norm(); 96 | } 97 | }; 98 | -------------------------------------------------------------------------------- /exemples/common/opengl.zig: -------------------------------------------------------------------------------- 1 | const c = @import("c.zig"); 2 | const std = @import("std"); 3 | const math = std.math; 4 | const warn = std.debug.warn; 5 | 6 | usingnamespace @import("shader.zig"); 7 | usingnamespace @import("zalgebra"); 8 | usingnamespace @import("transform.zig"); 9 | 10 | const ArrayList = std.ArrayList; 11 | 12 | const gpa = std.heap.page_allocator; 13 | 14 | const Texture = struct { 15 | id: u32, 16 | width: i32, 17 | height: i32, 18 | channels: i32 = 4, 19 | }; 20 | 21 | pub const Mesh = struct { 22 | vertices: ArrayList(f32), 23 | indices: ?ArrayList(i32), 24 | uvs: ?ArrayList(f32), 25 | colors: ?ArrayList(f32), 26 | }; 27 | 28 | 29 | pub const GeometryObject = struct { 30 | gl: RenderDataObject, 31 | mesh: Mesh, 32 | texture: ?Texture, 33 | 34 | transform: Transform, 35 | 36 | const Self = @This(); 37 | 38 | pub fn new(vertices: []const f32, indices: ?[]const i32, uvs: ?[]const f32, colors: ?[]const f32, _: ?[]const u8) !Self { 39 | var obj: Self = undefined; 40 | obj.transform = Transform{}; 41 | 42 | obj.mesh.vertices = ArrayList(f32).init(gpa); 43 | try obj.mesh.vertices.appendSlice(vertices); 44 | 45 | if (indices) |data| { 46 | obj.mesh.indices = ArrayList(i32).init(gpa); 47 | try obj.mesh.indices.?.appendSlice(data); 48 | } 49 | 50 | if (uvs) |data| { 51 | obj.mesh.uvs = ArrayList(f32).init(gpa); 52 | try obj.mesh.uvs.?.appendSlice(data); 53 | } 54 | 55 | if (colors) |data| { 56 | obj.mesh.colors = ArrayList(f32).init(gpa); 57 | try obj.mesh.colors.?.appendSlice(data); 58 | } 59 | 60 | obj.gl = RenderDataObject.from_mesh(&obj.mesh); 61 | return obj; 62 | } 63 | 64 | pub fn deinit(self: *Self) void { 65 | self.mesh.vertices.deinit(); 66 | 67 | if (self.mesh.indices) |indices| { 68 | indices.deinit(); 69 | } 70 | 71 | if (self.mesh.uvs) |uv| { 72 | uv.deinit(); 73 | } 74 | 75 | if (self.mesh.colors) |colors| { 76 | colors.deinit(); 77 | } 78 | } 79 | }; 80 | 81 | pub const RenderDataObject = struct { 82 | vao: u32, 83 | vbo: u32, 84 | ebo: ?u32, 85 | triangles: i32, 86 | 87 | pub fn from_mesh(mesh: *const Mesh) RenderDataObject { 88 | var render_data_object: RenderDataObject = undefined; 89 | render_data_object.triangles = @divExact(@intCast(i32, mesh.vertices.items.len), 3); 90 | 91 | c.glGenVertexArrays(1, &render_data_object.vao); 92 | c.glBindVertexArray(render_data_object.vao); 93 | 94 | var total = @intCast(isize, mesh.vertices.items.len) * @sizeOf(f32); 95 | 96 | if (mesh.uvs) |uvs| { 97 | total += @intCast(isize, uvs.items.len) * @sizeOf(f32); 98 | } 99 | 100 | if (mesh.colors) |colors| { 101 | total += @intCast(isize, colors.items.len) * @sizeOf(f32); 102 | } 103 | 104 | // Allocate memory for our vertex buffer object. 105 | // We use a 111222333 pattern, so we use BufferSubData api for this. 106 | c.glGenBuffers(1, &render_data_object.vbo); 107 | c.glBindBuffer(c.GL_ARRAY_BUFFER, render_data_object.vbo); 108 | c.glBufferData(c.GL_ARRAY_BUFFER, total, null, c.GL_STATIC_DRAW); 109 | 110 | var cursor: isize = 0; 111 | // Vertices batch 112 | { 113 | var size = @intCast(isize, mesh.vertices.items.len) * @sizeOf(f32); 114 | c.glBufferSubData(c.GL_ARRAY_BUFFER, 0, size, mesh.vertices.items.ptr); 115 | 116 | c.glVertexAttribPointer(0, 3, c.GL_FLOAT, c.GL_FALSE, 3 * @sizeOf(f32), null); 117 | c.glEnableVertexAttribArray(0); 118 | 119 | cursor += size; 120 | } 121 | 122 | // Uvs batches 123 | if (mesh.uvs) |uvs| { 124 | var size = @intCast(isize, uvs.items.len) * @sizeOf(f32); 125 | c.glBufferSubData(c.GL_ARRAY_BUFFER, cursor, size, uvs.items.ptr); 126 | 127 | c.glVertexAttribPointer(1, 2, c.GL_FLOAT, c.GL_FALSE, 2 * @sizeOf(f32), @intToPtr(*const c_void, @intCast(usize, cursor))); 128 | c.glEnableVertexAttribArray(1); 129 | 130 | cursor += size; 131 | } 132 | 133 | // Colors batches 134 | if (mesh.colors) |colors| { 135 | var size = @intCast(isize, colors.items.len) * @sizeOf(f32); 136 | c.glBufferSubData(c.GL_ARRAY_BUFFER, cursor, size, colors.items.ptr); 137 | 138 | c.glVertexAttribPointer(2, 3, c.GL_FLOAT, c.GL_FALSE, 3 * @sizeOf(f32), @intToPtr(*const c_void, @intCast(usize, cursor))); 139 | c.glEnableVertexAttribArray(2); 140 | 141 | cursor += size; 142 | } 143 | 144 | if (mesh.indices) |indices| { 145 | var len = indices.items.len; 146 | render_data_object.triangles = @intCast(i32, len); 147 | render_data_object.ebo = 0; 148 | c.glGenBuffers(1, &render_data_object.ebo.?); 149 | c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, render_data_object.ebo.?); 150 | c.glBufferData(c.GL_ELEMENT_ARRAY_BUFFER, @intCast(isize, len) * @sizeOf(i32), @ptrCast(*const c_void, indices.items), c.GL_STATIC_DRAW); 151 | } 152 | 153 | c.glBindBuffer(c.GL_ARRAY_BUFFER, 0); 154 | c.glBindVertexArray(0); 155 | 156 | return render_data_object; 157 | } 158 | 159 | }; 160 | 161 | pub fn draw_geometry(obj: *const GeometryObject, shader: *const Shader, model: Mat4, color: ?Vec4, only_color: bool) void { 162 | c.glBindVertexArray(obj.gl.vao); 163 | shader.setMat4("model", &model); 164 | 165 | if (color) |rgba| { 166 | shader.setRgba("color", &rgba); 167 | } 168 | 169 | if (obj.texture) |texture| { 170 | shader.setBool("with_texture", true); 171 | c.glActiveTexture(c.GL_TEXTURE0); 172 | c.glBindTexture(c.GL_TEXTURE_2D, texture.id); 173 | } 174 | 175 | if (only_color) { 176 | shader.setBool("with_texture", false); 177 | } 178 | 179 | if (obj.mesh.colors) |_| { 180 | shader.setBool("with_color", true); 181 | } 182 | 183 | if (obj.gl.ebo) |ebo| { 184 | c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, ebo); 185 | c.glDrawElements(c.GL_TRIANGLES, obj.gl.triangles, c.GL_UNSIGNED_INT, null); 186 | } else { 187 | c.glDrawArrays(c.GL_TRIANGLES, 0, obj.gl.triangles); 188 | } 189 | 190 | // Cleanup uniforms. 191 | shader.setRgba("color", &Vec4.new(10, 100, 50, 1)); 192 | shader.setMat4("model", &Mat4.identity()); 193 | shader.setBool("with_texture", false); 194 | shader.setBool("with_color", false); 195 | } 196 | -------------------------------------------------------------------------------- /exemples/common/primitive.zig: -------------------------------------------------------------------------------- 1 | /// Cube with 1 unit as dimentions 2 | pub const CUBE_VERTICES = [_]f32{ 3 | -0.5, 0.5, -0.5, 4 | -0.5, -0.5, -0.5, 5 | 0.5, -0.5, -0.5, 6 | 0.5, 0.5, -0.5, 7 | 8 | -0.5, 0.5, 0.5, 9 | -0.5, -0.5, 0.5, 10 | 0.5, -0.5, 0.5, 11 | 0.5, 0.5, 0.5, 12 | 13 | 0.5, 0.5, -0.5, 14 | 0.5, -0.5, -0.5, 15 | 0.5, -0.5, 0.5, 16 | 0.5, 0.5, 0.5, 17 | 18 | -0.5, 0.5, -0.5, 19 | -0.5, -0.5, -0.5, 20 | -0.5, -0.5, 0.5, 21 | -0.5, 0.5, 0.5, 22 | 23 | -0.5, 0.5, 0.5, 24 | -0.5, 0.5, -0.5, 25 | 0.5, 0.5, -0.5, 26 | 0.5, 0.5, 0.5, 27 | 28 | -0.5, -0.5, 0.5, 29 | -0.5, -0.5, -0.5, 30 | 0.5, -0.5, -0.5, 31 | 0.5, -0.5, 0.5, 32 | }; 33 | 34 | pub const CUBE_UV_COORDS = [_]f32{ 35 | 0, 0, 36 | 0, 1, 37 | 1, 1, 38 | 1, 0, 39 | 0, 0, 40 | 0, 1, 41 | 1, 1, 42 | 1, 0, 43 | 0, 0, 44 | 0, 1, 45 | 1, 1, 46 | 1, 0, 47 | 0, 0, 48 | 0, 1, 49 | 1, 1, 50 | 1, 0, 51 | 0, 0, 52 | 0, 1, 53 | 1, 1, 54 | 1, 0, 55 | 0, 0, 56 | 0, 1, 57 | 1, 1, 58 | 1, 0, 59 | }; 60 | 61 | pub const CUBE_INDICES = [_]i32{ 62 | 0, 1, 3, 63 | 3, 1, 2, 64 | 4, 5, 7, 65 | 7, 5, 6, 66 | 8, 9, 11, 67 | 11, 9, 10, 68 | 12, 13, 15, 69 | 15, 13, 14, 70 | 16, 17, 19, 71 | 19, 17, 18, 72 | 20, 21, 23, 73 | 23, 21, 22, 74 | }; 75 | 76 | pub const CUBE_COLORS = [_]f32{ 77 | 0.583, 0.771, 0.014, 78 | 0.609, 0.115, 0.436, 79 | 0.327, 0.483, 0.844, 80 | 0.822, 0.569, 0.201, 81 | 0.435, 0.602, 0.223, 82 | 0.310, 0.747, 0.185, 83 | 0.597, 0.770, 0.761, 84 | 0.559, 0.436, 0.730, 85 | 0.359, 0.583, 0.152, 86 | 0.483, 0.596, 0.789, 87 | 0.559, 0.861, 0.639, 88 | 0.195, 0.548, 0.859, 89 | 0.014, 0.184, 0.576, 90 | 0.771, 0.328, 0.970, 91 | 0.406, 0.615, 0.116, 92 | 0.676, 0.977, 0.133, 93 | 0.971, 0.572, 0.833, 94 | 0.140, 0.616, 0.489, 95 | 0.997, 0.513, 0.064, 96 | 0.945, 0.719, 0.592, 97 | 0.543, 0.021, 0.978, 98 | 0.279, 0.317, 0.505, 99 | 0.167, 0.620, 0.077, 100 | 0.347, 0.857, 0.137, 101 | 0.055, 0.953, 0.042, 102 | 0.714, 0.505, 0.345, 103 | 0.783, 0.290, 0.734, 104 | 0.722, 0.645, 0.174, 105 | 0.302, 0.455, 0.848, 106 | 0.225, 0.587, 0.040, 107 | 0.517, 0.713, 0.338, 108 | 0.053, 0.959, 0.120, 109 | 0.393, 0.621, 0.362, 110 | 0.673, 0.211, 0.457, 111 | 0.820, 0.883, 0.371, 112 | 0.982, 0.099, 0.879 113 | }; 114 | -------------------------------------------------------------------------------- /exemples/common/shader.zig: -------------------------------------------------------------------------------- 1 | usingnamespace @import("zalgebra"); 2 | const c = @import("c.zig"); 3 | const c_allocator = @import("std").heap.c_allocator; 4 | const panic = @import("std").debug.panic; 5 | 6 | pub const Shader = struct { 7 | name: []const u8, 8 | program_id: u32, 9 | vertex_id: u32, 10 | fragment_id: u32, 11 | geometry_id: ?u32, 12 | 13 | pub fn create(name: []const u8, vert_content: []const u8, frag_content: []const u8) !Shader { 14 | var sp: Shader = undefined; 15 | sp.name = name; 16 | 17 | { 18 | sp.vertex_id = c.glCreateShader(c.GL_VERTEX_SHADER); 19 | const source_ptr: ?[*]const u8 = vert_content.ptr; 20 | const source_len = @intCast(c.GLint, vert_content.len); 21 | c.glShaderSource(sp.vertex_id, 1, &source_ptr, &source_len); 22 | c.glCompileShader(sp.vertex_id); 23 | 24 | var ok: c.GLint = undefined; 25 | c.glGetShaderiv(sp.vertex_id, c.GL_COMPILE_STATUS, &ok); 26 | 27 | if (ok == 0) { 28 | var error_size: c.GLint = undefined; 29 | c.glGetShaderiv(sp.vertex_id, c.GL_INFO_LOG_LENGTH, &error_size); 30 | 31 | const message = try c_allocator.alloc(u8, @intCast(usize, error_size)); 32 | c.glGetShaderInfoLog(sp.vertex_id, error_size, &error_size, message.ptr); 33 | panic("Error compiling vertex shader:\n{s}\n", .{message}); 34 | } 35 | } 36 | 37 | { 38 | sp.fragment_id = c.glCreateShader(c.GL_FRAGMENT_SHADER); 39 | const source_ptr: ?[*]const u8 = frag_content.ptr; 40 | const source_len = @intCast(c.GLint, frag_content.len); 41 | c.glShaderSource(sp.fragment_id, 1, &source_ptr, &source_len); 42 | c.glCompileShader(sp.fragment_id); 43 | 44 | var ok: c.GLint = undefined; 45 | c.glGetShaderiv(sp.fragment_id, c.GL_COMPILE_STATUS, &ok); 46 | 47 | if (ok == 0) { 48 | var error_size: c.GLint = undefined; 49 | c.glGetShaderiv(sp.fragment_id, c.GL_INFO_LOG_LENGTH, &error_size); 50 | 51 | const message = try c_allocator.alloc(u8, @intCast(usize, error_size)); 52 | c.glGetShaderInfoLog(sp.fragment_id, error_size, &error_size, message.ptr); 53 | panic("Error compiling fragment shader:\n{s}\n", .{message}); 54 | } 55 | } 56 | 57 | sp.program_id = c.glCreateProgram(); 58 | c.glAttachShader(sp.program_id, sp.vertex_id); 59 | c.glAttachShader(sp.program_id, sp.fragment_id); 60 | c.glLinkProgram(sp.program_id); 61 | 62 | var ok: c.GLint = undefined; 63 | c.glGetProgramiv(sp.program_id, c.GL_LINK_STATUS, &ok); 64 | 65 | if (ok == 0) { 66 | var error_size: c.GLint = undefined; 67 | c.glGetProgramiv(sp.program_id, c.GL_INFO_LOG_LENGTH, &error_size); 68 | const message = try c_allocator.alloc(u8, @intCast(usize, error_size)); 69 | c.glGetProgramInfoLog(sp.program_id, error_size, &error_size, message.ptr); 70 | panic("Error linking shader program: {s}\n", .{message}); 71 | } 72 | 73 | // Cleanup shaders (from gl doc). 74 | c.glDeleteShader(sp.vertex_id); 75 | c.glDeleteShader(sp.fragment_id); 76 | 77 | return sp; 78 | } 79 | 80 | pub fn setMat4(sp: Shader, name: [*c]const u8, value: *const Mat4) void { 81 | const id = c.glGetUniformLocation(sp.program_id, name); 82 | c.glUniformMatrix4fv(id, 1, c.GL_FALSE, value.getData()); 83 | } 84 | 85 | pub fn setBool(sp: Shader, name: [*c]const u8, value: bool) void { 86 | const id = c.glGetUniformLocation(sp.program_id, name); 87 | c.glUniform1i(id, @boolToInt(value)); 88 | } 89 | 90 | pub fn setFloat(sp: Shader, name: [*c]const u8, value: f32) void { 91 | const id = c.glGetUniformLocation(sp.program_id, name); 92 | c.glUniform1f(id, value); 93 | } 94 | 95 | pub fn setRgb(sp: Shader, name: [*c]const u8, value: *const Vec3) void { 96 | const id = c.glGetUniformLocation(sp.program_id, name); 97 | c.glUniform3f(id, value.x / 255.0, value.y / 255.0, value.z / 255.0); 98 | } 99 | 100 | pub fn setRgba(sp: Shader, name: [*c]const u8, value: *const Vec4) void { 101 | const id = c.glGetUniformLocation(sp.program_id, name); 102 | c.glUniform4f(id, value.x / 255.0, value.y / 255.0, value.z / 255.0, value.w); 103 | } 104 | }; 105 | -------------------------------------------------------------------------------- /exemples/common/transform.zig: -------------------------------------------------------------------------------- 1 | usingnamespace @import("zalgebra"); 2 | 3 | pub const Transform = struct { 4 | position: Vec3 = Vec3.new(0, 0, 0), 5 | rotation: Quat = Quat.new(1, 0, 0, 0), 6 | scale: Vec3 = Vec3.new(1, 1, 1), 7 | 8 | pub fn new(position: Vec3, rotation: Quat, scale: Vec3) Transform { 9 | return .{ 10 | .position = position, 11 | .rotation = rotation, 12 | .scale = scale, 13 | }; 14 | } 15 | 16 | pub fn get_model(self: *const Transform) Mat4 { 17 | const rotation = self.rotation.toMat4(); 18 | const scale = Mat4.fromScale(self.scale); 19 | const transform = Mat4.fromTranslate(self.position); 20 | 21 | return transform.mult(rotation.mult(scale)); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const math = std.math; 3 | const panic = std.debug.panic; 4 | usingnamespace @import("zalgebra"); 5 | 6 | pub const Mode = enum { None, Move, Rotate, Scale }; 7 | pub const State = enum { Idle, Hover, Dragging }; 8 | 9 | /// Ray used to store the projected cursor ray. 10 | const Ray = struct { 11 | origin: Vec3, 12 | dir: Vec3 13 | }; 14 | 15 | /// Bounding Box used for collision detection. 16 | /// Also used to construct box vertices. 17 | const BoundingBox = struct { 18 | min: Vec3, 19 | max: Vec3, 20 | }; 21 | 22 | /// Collection of bounding boxes. 23 | const BoundingCollection = struct { 24 | /// X axis. 25 | x: BoundingBox, 26 | /// Y axis. 27 | y: BoundingBox, 28 | /// Z axis. 29 | z: BoundingBox, 30 | 31 | /// YZ Panel. 32 | yz: BoundingBox, 33 | /// XZ Panel. 34 | xz: BoundingBox, 35 | /// XY Panel. 36 | xy: BoundingBox, 37 | 38 | /// X box (scaling gizmo). 39 | x_box: BoundingBox, 40 | /// Y box (scaling gizmo). 41 | y_box: BoundingBox, 42 | /// Z box (scaling gizmo). 43 | z_box: BoundingBox, 44 | 45 | const Self = @This(); 46 | 47 | pub fn init(config: *const Config) Self { 48 | const max_pos = config.panel_size + config.panel_offset; 49 | 50 | return .{ 51 | .x = BoundingBox { 52 | .min = Vec3.zero(), 53 | .max = Vec3.new(config.axis_length, config.axis_size, config.axis_size) 54 | }, 55 | 56 | .y = BoundingBox { 57 | .min = Vec3.zero(), 58 | .max = Vec3.new(config.axis_size, config.axis_length, config.axis_size) 59 | }, 60 | 61 | .z = BoundingBox { 62 | .min = Vec3.zero(), 63 | .max = Vec3.new(config.axis_size, config.axis_size, -config.axis_length) 64 | }, 65 | 66 | .yz = BoundingBox { 67 | .min = Vec3.new(0, config.panel_offset, -config.panel_offset), 68 | .max = Vec3.new(config.panel_width, max_pos, -max_pos) 69 | }, 70 | 71 | .xz = BoundingBox { 72 | .min = Vec3.new(config.panel_offset, 0, -config.panel_offset), 73 | .max = Vec3.new(max_pos, config.panel_width, -max_pos) 74 | }, 75 | 76 | .xy = BoundingBox { 77 | .min = Vec3.new(config.panel_offset, config.panel_offset, 0), 78 | .max = Vec3.new(max_pos, max_pos, config.panel_width) 79 | }, 80 | 81 | .x_box = BoundingBox { 82 | .min = Vec3.new(config.axis_length, -config.scale_box_size , -config.scale_box_size), 83 | .max= Vec3.new(config.axis_length + config.scale_box_size * 2, config.scale_box_size, config.scale_box_size), 84 | }, 85 | 86 | .y_box = BoundingBox { 87 | .min = Vec3.new(-config.scale_box_size, config.axis_length, -config.scale_box_size), 88 | .max= Vec3.new(config.scale_box_size, config.axis_length + config.scale_box_size * 2, config.scale_box_size), 89 | }, 90 | 91 | .z_box = BoundingBox { 92 | .min = Vec3.new(-config.scale_box_size , -config.scale_box_size, -config.axis_length), 93 | .max= Vec3.new( config.scale_box_size, config.scale_box_size, -(config.axis_length + config.scale_box_size * 2),), 94 | } 95 | }; 96 | } 97 | 98 | 99 | }; 100 | 101 | /// All Gizmo visible parts. 102 | pub const GizmoItem = enum { 103 | PanelYZ, 104 | PanelXY, 105 | PanelXZ, 106 | ArrowX, 107 | ArrowY, 108 | ArrowZ, 109 | RotateX, 110 | RotateY, 111 | RotateZ, 112 | ScalerX, 113 | ScalerY, 114 | ScalerZ, 115 | }; 116 | 117 | /// This is the computed result from the given target matrix after 118 | /// gizmo manipulation. It's the result type of the `manipulate` method. 119 | /// Hint: To transform `Quat` into `Vec3`, use the `extract_rotation` method. 120 | pub const GizmoTransform = struct { 121 | position: Vec3 = Vec3.new(0, 0, 0), 122 | rotation: Quat = Quat.new(0, 0, 0, 0), 123 | scale: Vec3 = Vec3.new(1, 1, 1), 124 | }; 125 | 126 | /// Configuration. 127 | /// Only viewport is required. 128 | pub const Config = struct { 129 | /// Viewport options. 130 | screen_width: i32, 131 | screen_height: i32, 132 | dpi: i32 = 1, 133 | 134 | /// Snap mode. 135 | snap_axis: ?f32 = null, 136 | snap_angle: ?f32 = null, 137 | 138 | /// Radius of the arcball. 139 | arcball_radius: f32 = 1, 140 | /// The size of the handle when in Rotate mode. 141 | arcball_thickness: f32 = 0.02, 142 | 143 | /// Global size of dragging panels. 144 | panel_size: f32 = 0.5, 145 | /// Offset from gizmo current position. 146 | panel_offset: f32 = 0.2, 147 | /// Width of the panels. 148 | panel_width: f32 = 0.02, 149 | 150 | /// Length of axis. 151 | axis_length: f32 = 1, 152 | /// Axis boldness size. 153 | axis_size: f32 = 0.05, 154 | 155 | // Little "weight" box at the end of scaled axis. 156 | scale_box_size: f32 = 0.1 157 | }; 158 | 159 | const Camera = struct { 160 | view: Mat4, 161 | proj: Mat4, 162 | }; 163 | 164 | const Cursor = struct { 165 | x: f64, 166 | y: f64, 167 | is_pressed: bool = false, 168 | }; 169 | 170 | const CUBOID_INDICES = [36]i32{ 171 | 0,1,2, 172 | 0,2,3, 173 | 4,5,6, 174 | 4,6,7, 175 | 8,9,10, 176 | 8,10,11, 177 | 12,13,14, 178 | 12,14,15, 179 | 16,17,18, 180 | 16,18,19, 181 | 20,21,22, 182 | 20,22,23, 183 | }; 184 | 185 | const MeshPanels = struct { 186 | yz: [72]f32, 187 | xz: [72]f32, 188 | xy: [72]f32, 189 | indices: @TypeOf(CUBOID_INDICES) 190 | }; 191 | 192 | const MeshAxis = struct { 193 | x: [72]f32, 194 | y: [72]f32, 195 | z: [72]f32, 196 | indices: @TypeOf(CUBOID_INDICES) 197 | }; 198 | 199 | pub const MeshRotateAxis = struct { 200 | x: [3240]f32, 201 | y: [3240]f32, 202 | z: [3240]f32, 203 | }; 204 | 205 | pub const MeshScaleAxis = struct { 206 | x: [144]f32, 207 | y: [144]f32, 208 | z: [144]f32, 209 | indices: [72]i32, 210 | }; 211 | 212 | pub const Mogwai = struct { 213 | /// Gizmo's mode, if `None` don't show/compute anything. 214 | mode: Mode = Mode.None, 215 | /// Current state. 216 | state: State = State.Idle, 217 | /// Active item. 218 | active: ?GizmoItem = null, 219 | 220 | /// All bounding box used for collision are stored here. 221 | bb: BoundingCollection, 222 | 223 | /// Gizmo general Config. 224 | config: Config, 225 | /// Client's view and projection matrices. 226 | cam: Camera, 227 | /// Client's cursor positions and state. 228 | cursor: Cursor, 229 | 230 | /// This is the point representing the 231 | /// nearest successful collision. 232 | click_offset: Vec3 = Vec3.zero(), 233 | /// Current position of the gizmo, usually the position of the target. 234 | position: Vec3 = Vec3.zero(), 235 | /// All constructed meshes are stored there, used for client's renderer. 236 | meshes: struct { 237 | move_panels: MeshPanels, 238 | move_axis: MeshAxis, 239 | rotate_axis: MeshRotateAxis, 240 | scale_axis: MeshScaleAxis 241 | }, 242 | 243 | /// We keep track of target's original data just before the dragging state starts. 244 | /// We extract those information from the given target matrix. 245 | original_transform: GizmoTransform = .{}, 246 | /// Active axis (X, Y, Z). 247 | active_axis: ?Vec3 = null, 248 | 249 | /// Used for compute theta on Rotate mode. 250 | /// Theta is the angle between `started_arm` and `ended_arm`. 251 | started_arm: ?Vec3 = null, 252 | ended_arm: ?Vec3 = null, 253 | 254 | const Self = @This(); 255 | 256 | /// Construct new gizmo from given config. 257 | pub fn new(config: Config) Self { 258 | return .{ 259 | .config = config, 260 | .bb = BoundingCollection.init(&config), 261 | .meshes = .{ 262 | .move_panels = make_move_panels(config.panel_size, config.panel_offset, config.panel_width), 263 | .move_axis = make_move_axis(config.axis_size, config.axis_length), 264 | .rotate_axis = make_rotate_axis(&config), 265 | .scale_axis = make_scale_axis(config.axis_size, config.axis_length, config.scale_box_size), 266 | }, 267 | .cam = .{ 268 | .view = Mat4.identity(), 269 | .proj = Mat4.identity(), 270 | }, 271 | .cursor = .{ 272 | .x = 0.0, 273 | .y = 0.0, 274 | .is_pressed = false, 275 | }, 276 | }; 277 | } 278 | 279 | /// Return if given object is hovered by the cursor. 280 | pub fn isHover(self: *const Self, object: GizmoItem) bool { 281 | return self.active != null and self.active.? == object; 282 | } 283 | 284 | /// Set the screen size. 285 | pub fn setViewport(self: *Self, width: i32, height: i32, dpi: i32) void { 286 | self.config.screen_width = width; 287 | self.config.screen_height = height; 288 | self.viewport.dpi = dpi; 289 | } 290 | 291 | /// Set the cursor position and if it was pressed. 292 | pub fn setCursor(self: *Self, x: f64, y: f64, is_pressed: bool) void { 293 | self.cursor.x = x; 294 | self.cursor.y = y; 295 | self.cursor.is_pressed = is_pressed; 296 | 297 | // If cursor is released, Gizmo isn't "active". 298 | if (!is_pressed) { 299 | self.state = State.Idle; 300 | self.active = null; 301 | self.click_offset = Vec3.zero(); 302 | self.original_transform = .{}; 303 | self.active_axis = null; 304 | self.started_arm = null; 305 | self.ended_arm = null; 306 | } 307 | } 308 | 309 | /// Set the view matrix and projection matrix, needed to compute 310 | /// the position of the eye, and all intersection in space. 311 | pub fn setCamera(self: *Self, view: Mat4, proj: Mat4) void { 312 | self.cam.view = view; 313 | self.cam.proj = proj; 314 | } 315 | 316 | /// Manipulate the gizmo from the given cursor state and position. 317 | pub fn manipulate(self: *Self, target: Mat4, mode: Mode) ?GizmoTransform { 318 | // If mode is none, we don't want to compute something. 319 | if (mode == Mode.None) { 320 | return null; 321 | } 322 | 323 | var result: ?GizmoTransform = null; 324 | 325 | // Gizmo's position in world space. 326 | self.position = target.extractTranslation(); 327 | // Position of the camera, where the ray will be cast. 328 | const eye = self.cam.view.inv().extractTranslation(); 329 | // Raycast used for collision detection. 330 | const ray = raycast(eye, self.cam, self.config, self.cursor); 331 | 332 | switch (mode) { 333 | Mode.Scale, 334 | Mode.Move => { 335 | var hit: ?Vec3 = null; 336 | var nearest_distance: f32 = math.f32_max; 337 | 338 | if (self.state != State.Dragging) { 339 | if (mode == Mode.Move) { 340 | intersect_cuboid(self, ray, &self.bb.x, GizmoItem.ArrowX, Vec3.right(), &nearest_distance, &hit); 341 | intersect_cuboid(self, ray, &self.bb.y, GizmoItem.ArrowY, Vec3.up(), &nearest_distance, &hit); 342 | intersect_cuboid(self, ray, &self.bb.z, GizmoItem.ArrowZ, Vec3.forward(), &nearest_distance, &hit); 343 | 344 | intersect_cuboid(self, ray, &self.bb.yz, GizmoItem.PanelYZ, Vec3.right(), &nearest_distance, &hit); 345 | intersect_cuboid(self, ray, &self.bb.xz, GizmoItem.PanelXZ, Vec3.up(), &nearest_distance, &hit); 346 | intersect_cuboid(self, ray, &self.bb.xy, GizmoItem.PanelXY, Vec3.forward(), &nearest_distance, &hit); 347 | } 348 | 349 | if (mode == Mode.Scale) { 350 | intersect_cuboid(self, ray, &self.bb.x, GizmoItem.ScalerX, Vec3.right(), &nearest_distance, &hit); 351 | intersect_cuboid(self, ray, &self.bb.y, GizmoItem.ScalerY, Vec3.up(), &nearest_distance, &hit); 352 | intersect_cuboid(self, ray, &self.bb.z, GizmoItem.ScalerZ, Vec3.forward(), &nearest_distance, &hit); 353 | 354 | intersect_cuboid(self, ray, &self.bb.x_box, GizmoItem.ScalerX, Vec3.right(), &nearest_distance, &hit); 355 | intersect_cuboid(self, ray, &self.bb.y_box, GizmoItem.ScalerY, Vec3.up(), &nearest_distance, &hit); 356 | intersect_cuboid(self, ray, &self.bb.z_box, GizmoItem.ScalerZ, Vec3.forward(), &nearest_distance, &hit); 357 | } 358 | 359 | if (hit != null) { 360 | self.click_offset = hit.?; 361 | self.original_transform.position = target.extractTranslation(); 362 | self.original_transform.scale = target.extractScale(); 363 | self.state = State.Hover; 364 | } 365 | } 366 | 367 | if (self.state == State.Dragging) { 368 | const axis = self.active_axis.?; 369 | const position = self.click_offset; 370 | var plane_normal = axis; 371 | 372 | switch (self.active.?) { 373 | GizmoItem.ScalerX, 374 | GizmoItem.ScalerY, 375 | GizmoItem.ScalerZ, 376 | GizmoItem.ArrowX, 377 | GizmoItem.ArrowY, 378 | GizmoItem.ArrowZ => { 379 | const plane_tangent = Vec3.cross(axis, Vec3.sub(position, eye)); 380 | plane_normal = Vec3.cross(axis, plane_tangent); 381 | }, 382 | else => {} 383 | } 384 | 385 | if (ray_vs_plane(plane_normal, position, ray)) |dist| { 386 | hit = ray.origin.add(ray.dir.scale(dist)); 387 | } 388 | } 389 | 390 | if (!self.cursor.is_pressed or self.state == State.Idle) { 391 | return result; 392 | } 393 | 394 | if (hit) |p| { 395 | self.state = State.Dragging; 396 | 397 | const original = self.original_transform; 398 | var diff = p.sub(self.click_offset); 399 | 400 | if (self.config.snap_axis) |snap| { 401 | diff.x = math.floor(diff.x / snap) * snap; 402 | diff.y = math.floor(diff.y / snap) * snap; 403 | diff.z = math.floor(diff.z / snap) * snap; 404 | } 405 | 406 | result = .{}; 407 | 408 | // Used to clamp scale values at Epsilon. 409 | const epsilon_vec = Vec3.new(math.f32_epsilon, math.f32_epsilon, math.f32_epsilon); 410 | 411 | switch (self.active.?) { 412 | GizmoItem.ArrowX => { 413 | result.?.position = original.position.add(Vec3.new(diff.x, 0, 0)); 414 | }, 415 | GizmoItem.ArrowY => { 416 | result.?.position = original.position.add(Vec3.new(0, diff.y, 0)); 417 | }, 418 | GizmoItem.ArrowZ => { 419 | result.?.position = original.position.add(Vec3.new(0, 0, diff.z)); 420 | }, 421 | GizmoItem.PanelYZ => { 422 | result.?.position = original.position.add(Vec3.new(0, diff.y, diff.z)); 423 | }, 424 | GizmoItem.PanelXZ => { 425 | result.?.position = original.position.add(Vec3.new(diff.x, 0, diff.z)); 426 | }, 427 | GizmoItem.PanelXY => { 428 | result.?.position = original.position.add(Vec3.new(diff.x, diff.y, 0)); 429 | }, 430 | GizmoItem.ScalerX => { 431 | result.?.scale = Vec3.max(original.scale.add(Vec3.new(diff.x, 0, 0)), epsilon_vec); 432 | }, 433 | GizmoItem.ScalerY => { 434 | result.?.scale = Vec3.max(original.scale.add(Vec3.new(0, diff.y, 0)), epsilon_vec); 435 | }, 436 | GizmoItem.ScalerZ => { 437 | result.?.scale = Vec3.max(original.scale.add(Vec3.new(0, 0, -diff.z)), epsilon_vec); 438 | }, 439 | else => {} 440 | } 441 | } 442 | }, 443 | 444 | Mode.Rotate => { 445 | var hit: ?Vec3 = null; 446 | var nearest_distance: f32 = math.f32_max; 447 | 448 | if (self.state != State.Dragging) { 449 | intersect_circle(self, ray, GizmoItem.RotateX, Vec3.right(), &nearest_distance, &hit); 450 | intersect_circle(self, ray, GizmoItem.RotateY, Vec3.up(), &nearest_distance, &hit); 451 | intersect_circle(self, ray, GizmoItem.RotateZ, Vec3.forward(), &nearest_distance, &hit); 452 | 453 | if (hit != null) { 454 | const normalized = target.orthoNormalize(); 455 | self.original_transform.rotation = Quat.fromMat4(normalized); 456 | self.state = State.Hover; 457 | self.click_offset = hit.?; 458 | } 459 | 460 | } 461 | 462 | if (self.state == State.Dragging) { 463 | const axis = self.active_axis.?; 464 | const position = self.click_offset; 465 | 466 | if (ray_vs_plane(axis, position, ray)) |dist| { 467 | hit = ray.origin.add(ray.dir.scale(dist)); 468 | } 469 | } 470 | 471 | if (!self.cursor.is_pressed or self.state == State.Idle) { 472 | return result; 473 | } 474 | 475 | if (hit) |p| { 476 | self.state = State.Dragging; 477 | 478 | const arm = Vec3.sub(self.position, p).norm(); 479 | 480 | if (self.started_arm == null) { 481 | self.started_arm = arm; 482 | } 483 | 484 | self.ended_arm = arm; 485 | 486 | // Now, we want to get the angle in degrees of those arms. 487 | const dot_product = math.min(1, Vec3.dot(self.ended_arm.?, self.started_arm.?)); 488 | // The `acos` of a dot product will gives us the angle between two vectors, in radians. 489 | // We just have to convert it to degrees. 490 | var angle = toDegrees(math.acos(dot_product)); 491 | 492 | if (self.config.snap_angle) |snap| { 493 | angle = math.floor(angle / snap) * snap; 494 | } 495 | 496 | // If angle is less than 1 degree, we don't want to do anything. 497 | if (angle < 1) return null; 498 | 499 | const cross_product = Vec3.cross(self.started_arm.?, self.ended_arm.?).norm(); 500 | const new_rot = Quat.fromAxis(angle, cross_product); 501 | const rotation = Quat.mult(new_rot, self.original_transform.rotation); 502 | 503 | result = .{ .rotation = rotation }; 504 | } 505 | }, 506 | else => {}, 507 | } 508 | 509 | return result; 510 | } 511 | 512 | /// Collision between ray and axis-aligned bounding box. 513 | /// If hit happen, return the distance between the origin and the hit. 514 | fn ray_vs_aabb(min: Vec3, max: Vec3, r: Ray) ?f32 { 515 | var tmin = -math.inf_f32; 516 | var tmax = math.inf_f32; 517 | 518 | var i: i32 = 0; 519 | while (i < 3) : (i += 1) { 520 | if (r.dir.at(i) != 0) { 521 | const t1 = (min.at(i) - r.origin.at(i))/r.dir.at(i); 522 | const t2 = (max.at(i) - r.origin.at(i))/r.dir.at(i); 523 | 524 | tmin = math.max(tmin, math.min(t1, t2)); 525 | tmax = math.min(tmax, math.max(t1, t2)); 526 | } else if (r.origin.at(i) < min.at(i) or r.origin.at(i) > max.at(i)) { 527 | return null; 528 | } 529 | } 530 | 531 | if (tmax >= tmin and tmax >= 0.0) { 532 | return tmin; 533 | } 534 | 535 | return null; 536 | } 537 | 538 | fn ray_vs_plane(normal: Vec3, plane_pos: Vec3, ray: Ray) ?f32 { 539 | var intersection: f32 = undefined; 540 | const denom: f32 = Vec3.dot(normal, ray.dir); 541 | 542 | if (math.absFloat(denom) > 1e-6) { 543 | const line = Vec3.sub(plane_pos, ray.origin); 544 | intersection = Vec3.dot(line, normal) / denom; 545 | 546 | return if (intersection > 0) intersection else null; 547 | } 548 | 549 | 550 | return null; 551 | } 552 | 553 | fn ray_vs_disk(normal: Vec3, disk_pos: Vec3, ray: Ray, radius: f32) ?f32 { 554 | if (ray_vs_plane(normal, disk_pos, ray)) |intersection| { 555 | const p = Vec3.add(ray.origin, Vec3.scale(ray.dir, intersection)); 556 | const v = Vec3.sub(p, disk_pos); 557 | const d2 = Vec3.dot(v, v); 558 | 559 | return if (math.sqrt(d2) <= radius) intersection else null; 560 | } 561 | 562 | return null; 563 | } 564 | 565 | fn intersect_cuboid(self: *Self, ray: Ray, bb: *const BoundingBox, selected_object: GizmoItem, axis: Vec3, near_dist: *f32, near_hit: *?Vec3) void { 566 | const min = self.position.add(bb.min); 567 | const max = self.position.add(bb.max); 568 | 569 | if (ray_vs_aabb(min, max, ray)) |distance| { 570 | if (distance < near_dist.*) { 571 | const hit = ray.origin.add(ray.dir.scale(distance)); 572 | 573 | near_dist.* = distance; 574 | self.active = selected_object; 575 | self.active_axis = axis; 576 | 577 | near_hit.* = hit; 578 | } 579 | } 580 | } 581 | 582 | fn intersect_circle(self: *Self, ray: Ray, selected_object: GizmoItem, axis: Vec3, near_dist: *f32, near_hit: *?Vec3) void { 583 | const outer_hit = ray_vs_disk(axis, self.position, ray, self.config.arcball_radius + self.config.arcball_thickness); 584 | const inner_hit = ray_vs_disk(axis, self.position, ray, self.config.arcball_radius - self.config.arcball_thickness); 585 | 586 | if (outer_hit != null and inner_hit == null and outer_hit.? < near_dist.*) { 587 | near_dist.* = outer_hit.?; 588 | self.active = selected_object; 589 | self.active_axis = axis; 590 | 591 | near_hit.* = ray.origin.add(ray.dir.scale(outer_hit.?)); 592 | } 593 | } 594 | 595 | /// Simple raycast function used to intersect cursor and gizmo objects. 596 | fn raycast(pos: Vec3, cam: Camera, config: Config, cursor: Cursor) Ray { 597 | const clip_ndc = Vec2.new( 598 | (@floatCast(f32, cursor.x) * @intToFloat(f32, config.dpi)) / @intToFloat(f32, config.screen_width) - 1, 599 | 1 - (@floatCast(f32, cursor.y) * @intToFloat(f32, config.dpi)) / @intToFloat(f32, config.screen_height) 600 | ); 601 | 602 | const clip_space = Vec4.new(clip_ndc.x, clip_ndc.y, -1, 1); 603 | const eye_tmp = Mat4.multByVec4(Mat4.inv(cam.proj), clip_space); 604 | const world_tmp = Mat4.multByVec4(Mat4.inv(cam.view), Vec4.new(eye_tmp.x, eye_tmp.y, -1, 0)); 605 | 606 | return .{ 607 | .origin = pos, 608 | .dir = Vec3.new(world_tmp.x, world_tmp.y, world_tmp.z).norm(), 609 | }; 610 | } 611 | 612 | /// Construct cuboid from given bounds. 613 | /// Used to construct cubes, axis and planes. 614 | fn construct_cuboid(min_bounds: Vec3, max_bounds: Vec3) [72]f32 { 615 | const a = min_bounds; 616 | const b = max_bounds; 617 | 618 | return .{ 619 | a.x, a.y, a.z , a.x, a.y, b.z, 620 | a.x, b.y, b.z , a.x, b.y, a.z, 621 | b.x, a.y, a.z , b.x, b.y, a.z, 622 | b.x, b.y, b.z , b.x, a.y, b.z, 623 | a.x, a.y, a.z , b.x, a.y, a.z, 624 | b.x, a.y, b.z , a.x, a.y, b.z, 625 | a.x, b.y, a.z , a.x, b.y, b.z, 626 | b.x, b.y, b.z , b.x, b.y, a.z, 627 | a.x, a.y, a.z , a.x, b.y, a.z, 628 | b.x, b.y, a.z , b.x, a.y, a.z, 629 | a.x, a.y, b.z , b.x, a.y, b.z, 630 | b.x, b.y, b.z , a.x, b.y, b.z, 631 | }; 632 | 633 | } 634 | 635 | /// Make gizmo planes (YZ, XZ, XY). 636 | fn make_move_panels(panel_size: f32, panel_offset: f32, panel_width: f32) MeshPanels { 637 | var mesh: MeshPanels = undefined; 638 | 639 | const max = panel_offset + panel_size; 640 | 641 | mesh.yz = construct_cuboid(Vec3.new(0, panel_offset, -panel_offset), Vec3.new(panel_width, max, -max)); 642 | mesh.xz = construct_cuboid(Vec3.new(panel_offset, 0, -panel_offset), Vec3.new(max, panel_width, -max)); 643 | mesh.xy = construct_cuboid(Vec3.new(panel_offset, panel_offset, 0), Vec3.new(max, max, panel_width)); 644 | mesh.indices = CUBOID_INDICES; 645 | 646 | return mesh; 647 | } 648 | 649 | /// Make gizmo axis as cuboid. (X, Y, Z). 650 | fn make_move_axis(size: f32, length: f32) MeshAxis { 651 | var mesh: MeshAxis = undefined; 652 | 653 | mesh.x = construct_cuboid(Vec3.new(0, 0, 0), Vec3.new(length, size, size)); 654 | mesh.y = construct_cuboid(Vec3.new(0, 0, 0), Vec3.new(size, length, size)); 655 | mesh.z = construct_cuboid(Vec3.new(0, 0, 0), Vec3.new(size, size, -length)); 656 | 657 | mesh.indices = CUBOID_INDICES; 658 | 659 | return mesh; 660 | } 661 | 662 | /// Make gizmo scale axis (with orthogonal cuboid at the end). 663 | /// TODO: I think we could a lot better here... 664 | fn make_scale_axis(size: f32, length: f32, box_size: f32) MeshScaleAxis { 665 | var mesh: MeshScaleAxis = undefined; 666 | 667 | const half_size = size * 0.5; 668 | const total_size = length + box_size * 2; 669 | 670 | const x_axis = construct_cuboid(Vec3.new(0, 0, 0), Vec3.new(length, size, size)); 671 | const x_box = construct_cuboid(Vec3.new(length, -box_size + half_size, -box_size + half_size), Vec3.new(total_size, box_size + half_size, box_size + half_size)); 672 | std.mem.copy(f32, &mesh.x, x_axis[0..]); 673 | std.mem.copy(f32, mesh.x[72..], x_box[0..]); 674 | 675 | const y_axis = construct_cuboid(Vec3.new(0, 0, 0), Vec3.new(size, length, size)); 676 | const y_box = construct_cuboid(Vec3.new(-box_size + half_size, length, -box_size + half_size), Vec3.new(box_size + half_size, total_size, box_size + half_size)); 677 | std.mem.copy(f32, &mesh.y, y_axis[0..]); 678 | std.mem.copy(f32, mesh.y[72..], y_box[0..]); 679 | 680 | const z_axis = construct_cuboid(Vec3.new(0, 0, 0), Vec3.new(size, size, -length)); 681 | const z_box = construct_cuboid(Vec3.new(-box_size + half_size, -box_size + half_size, -length), Vec3.new(box_size + half_size, box_size + half_size, -total_size)); 682 | std.mem.copy(f32, &mesh.z, z_axis[0..]); 683 | std.mem.copy(f32, mesh.z[72..], z_box[0..]); 684 | 685 | 686 | mesh.indices = CUBOID_INDICES ** 2; 687 | 688 | for (mesh.indices) |_, index| { 689 | if (index >= CUBOID_INDICES.len) { 690 | mesh.indices[index] += 24; 691 | } 692 | } 693 | 694 | return mesh; 695 | } 696 | 697 | /// This will create vertices for all axis circle 698 | /// used to rotate the target. 699 | pub fn make_rotate_axis(config: *const Config) MeshRotateAxis { 700 | var mesh: MeshRotateAxis = undefined; 701 | 702 | mesh.x = create_axis_arcball(GizmoItem.RotateX, config.arcball_radius, config.arcball_thickness); 703 | mesh.y = create_axis_arcball(GizmoItem.RotateY, config.arcball_radius, config.arcball_thickness); 704 | mesh.z = create_axis_arcball(GizmoItem.RotateZ, config.arcball_radius, config.arcball_thickness); 705 | 706 | return mesh; 707 | } 708 | 709 | pub fn create_axis_arcball(axis: GizmoItem, radius: f32, thickness: f32) [60 * 18 * 3]f32 { 710 | const segments = 60; 711 | 712 | var deg: i32 = 0; 713 | var i: usize = 0; 714 | var vertex: [segments * 18 * 3]f32 = undefined; 715 | 716 | const max_deg: i32 = 360; 717 | var segment_vertex: [segments]Vec3 = undefined; 718 | 719 | while (deg < max_deg) : (deg += @divExact(max_deg, segments)) { 720 | const angle = toRadians(@intToFloat(f32, deg)); 721 | const x = math.cos(angle) * radius; 722 | const y = math.sin(angle) * radius; 723 | 724 | segment_vertex[i] = switch (axis) { 725 | GizmoItem.RotateX => Vec3.new(x, y, 0), 726 | GizmoItem.RotateY => Vec3.new(0, y, x), 727 | GizmoItem.RotateZ => Vec3.new(x, 0, y), 728 | else => std.debug.panic("Object selected isn't a rotate axis.\n", .{}) 729 | }; 730 | 731 | i += 1; 732 | } 733 | 734 | var j: usize = 0; 735 | var k: usize = 0; 736 | while (j < segment_vertex.len) : (j += 1) { 737 | const p0 = segment_vertex[j]; 738 | const p1 = if (j == segment_vertex.len - 1) segment_vertex[0] else segment_vertex[j + 1]; 739 | const previous = if (j == 0) segment_vertex[segment_vertex.len - 1] else segment_vertex[j - 1]; 740 | const next = if (j == segment_vertex.len - 2) segment_vertex[0] else if (j == segment_vertex.len - 1) segment_vertex[1] else segment_vertex[j + 2]; 741 | 742 | for (create_segment(p0, p1, previous, next, thickness, axis)) |v, idx| { 743 | vertex[k + idx] = v; 744 | } 745 | 746 | k += 18; 747 | } 748 | 749 | return vertex; 750 | 751 | } 752 | 753 | /// This function will create a '2d' segment from two points. But to construct a 'perfect' joined path, 754 | /// we also need two other points, the previous and next one. Those two points are used to 755 | /// correctly compute the cross-section, which gives us sharp angles. 756 | /// More details in those posts: 757 | /// https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader. 758 | fn create_segment(p0: Vec3, p1: Vec3, previous: Vec3, next: Vec3, thickness: f32, axis: GizmoItem) [18]f32 { 759 | // Compute middle line. 760 | const line = Vec3.sub(p1, p0); 761 | var normal: Vec3 = undefined; 762 | 763 | // Compute tangeants. 764 | const t0 = Vec3.add(Vec3.sub(p0, previous).norm(), Vec3.sub(p1, p0).norm()).norm(); 765 | const t1 = Vec3.add(Vec3.sub(next, p1).norm(), Vec3.sub(p1, p0).norm()).norm(); 766 | 767 | var miter0: Vec3 = undefined; 768 | var miter1: Vec3 = undefined; 769 | 770 | switch (axis) { 771 | GizmoItem.RotateX => { 772 | normal = Vec3.new(-line.y, line.x, 0).norm(); 773 | miter0 = Vec3.new(-t0.y, t0.x, 0); 774 | miter1 = Vec3.new(-t1.y, t1.x, 0); 775 | 776 | }, 777 | GizmoItem.RotateY => { 778 | normal = Vec3.new(0, -line.z, line.y).norm(); 779 | miter0 = Vec3.new(0, -t0.z, t0.y); 780 | miter1 = Vec3.new(0, -t1.z, t1.y); 781 | 782 | }, 783 | GizmoItem.RotateZ => { 784 | normal = Vec3.new(-line.z, 0, line.x).norm(); 785 | miter0 = Vec3.new(-t0.z, 0, t0.x); 786 | miter1 = Vec3.new(-t1.z, 0, t1.x); 787 | 788 | }, 789 | else => std.debug.panic("Object selected isn't a rotate axis.\n", .{}) 790 | } 791 | 792 | const length0 = thickness / Vec3.dot(miter0, normal); 793 | const length1 = thickness / Vec3.dot(miter1, normal); 794 | 795 | const a = Vec3.add(p0, Vec3.scale(miter0, length0)); 796 | const b = Vec3.add(p1, Vec3.scale(miter1, length1)); 797 | const e = Vec3.sub(p0, Vec3.scale(miter0, length0)); 798 | const d = Vec3.sub(p1, Vec3.scale(miter1, length1)); 799 | 800 | return .{ 801 | a.x, a.y, a.z, 802 | b.x, b.y, b.z, 803 | e.x, e.y, e.z, 804 | 805 | b.x, b.y, b.z, 806 | d.x, d.y, d.z, 807 | e.x, e.y, e.z, 808 | }; 809 | } 810 | }; 811 | -------------------------------------------------------------------------------- /zig-out/bin/glfw_opengl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kooparse/mogwai/90fbdb659f42bc7bbd9ee8d6612cd4ebb4ea7ea9/zig-out/bin/glfw_opengl --------------------------------------------------------------------------------