An implementation of David Eberly's mesh clipping algorithm with demo program in odin. See comment at top of file for controls.
Last active
December 28, 2024 22:28
-
-
Save TheSandvichMaker/86baba805952150436cfb481e3a2bd6c to your computer and use it in GitHub Desktop.
Implementation of David Eberly's mesh clipping algorithm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package clip3d | |
| // Implementation of algorithm described by David Eberly here: | |
| // https://www.geometrictools.com/Documentation/ClipMesh.pdf | |
| // CONTROLS: | |
| // arrows keys to move camera | |
| // number keys to switch view modes: | |
| // 1) basic | |
| // 2) vertices | |
| // 3) edges | |
| // 4) faces | |
| // 5) face inspector | |
| // 6) triangulated "application" mesh | |
| // 7) triangulated mesh edges | |
| // 8) triangulated mesh faces | |
| // T to toggle text | |
| // spacebar to pause/unpause the animation | |
| // shift + left/right arrow to decrease/increase the number of cutting planes | |
| // tap the number key for the current mode to toggle hidden feature visibility or show the triangulated mesh in wireframe | |
| // in face inspector mode, use ctrl + left/right arrow to select a face to inspect | |
| // alt+enter to toggle fullscreen | |
| import "base:runtime" | |
| import c "core:c" | |
| import "core:math" | |
| import "core:math/linalg" | |
| import "core:mem" | |
| import "core:fmt" | |
| import "core:strings" | |
| import rl "vendor:raylib" | |
| Vec2 :: [2]f32 | |
| Vec3 :: [3]f32 | |
| Vec4 :: [4]f32 | |
| Plane :: struct { | |
| n: Vec3, | |
| d: f32, | |
| } | |
| // "application" mesh | |
| A_Vertex :: Vec3 | |
| A_Edge :: struct { | |
| v0: i32, | |
| v1: i32, | |
| f0: i32, | |
| f1: i32, | |
| } | |
| A_Face :: struct { | |
| e0: i32, | |
| e1: i32, | |
| e2: i32, | |
| } | |
| A_Mesh :: struct { | |
| V: [dynamic]A_Vertex, | |
| I: [dynamic]i32, | |
| E: [dynamic]A_Edge, | |
| F: [dynamic]A_Face, | |
| } | |
| // clipper mesh | |
| C_Vertex :: struct { | |
| p : Vec3, | |
| distance : f32, | |
| occurs : i32, | |
| visible : bool, | |
| } | |
| C_Edge :: struct { | |
| vertex: [2]i32, | |
| f0: i32, | |
| f1: i32, | |
| visible: bool, | |
| } | |
| C_Face :: struct { | |
| edges : [dynamic]i32, | |
| plane : Plane, | |
| visible : bool, | |
| } | |
| C_Mesh :: struct { | |
| V: [dynamic]C_Vertex, | |
| E: [dynamic]C_Edge, | |
| F: [dynamic]C_Face, | |
| } | |
| // clipping algorithm | |
| Clip_Result :: enum { | |
| CLIPPED_NONE, | |
| CLIPPED_SOME, | |
| CLIPPED_ALL, | |
| } | |
| clip_mesh_against_planes :: proc(mesh: ^C_Mesh, planes: []Plane) -> Clip_Result { | |
| result := Clip_Result.CLIPPED_NONE | |
| for plane in planes { | |
| EPSILON :: f32(0.0001) | |
| // | |
| // vertex processing | |
| // | |
| positive, negative := 0, 0 | |
| for &vertex in mesh.V { | |
| if !vertex.visible do continue | |
| vertex.distance = linalg.dot(plane.n, vertex.p) - plane.d | |
| if vertex.distance >= EPSILON { | |
| positive += 1 | |
| } | |
| else if vertex.distance <= -EPSILON { | |
| negative += 1 | |
| vertex.visible = false | |
| } | |
| else { | |
| vertex.distance = 0.0 | |
| } | |
| } | |
| if negative == 0 { | |
| // all vertices on nonnegative side, no clipping | |
| continue | |
| } | |
| if positive == 0 { | |
| // all vertices on nonpositive side, everything clipped | |
| result = .CLIPPED_ALL | |
| delete_clipper_mesh(mesh) | |
| break | |
| } | |
| // | |
| // edge processing | |
| // | |
| result = .CLIPPED_SOME | |
| for &edge, edge_index in mesh.E { | |
| v0 := mesh.V[edge.vertex[0]] | |
| v1 := mesh.V[edge.vertex[1]] | |
| d0 := v0.distance | |
| d1 := v1.distance | |
| if d0 <= 0 && d1 <= 0 { | |
| // edge is culled - remove edge from faces sharing it | |
| if edge.f0 != -1 do remove_edge(&mesh.F[edge.f0], edge_index) | |
| if edge.f1 != -1 do remove_edge(&mesh.F[edge.f1], edge_index) | |
| edge.visible = false | |
| continue | |
| } | |
| if d0 >= 0 && d1 >= 0 { | |
| // edge is on nonnegative side - faces retain the edge | |
| continue | |
| } | |
| // the edge is split by the plane | |
| t := d0 / (d0 - d1) | |
| intersect := (1.0 - t)*v0.p + t*v1.p | |
| index := len(mesh.V) | |
| append(&mesh.V, C_Vertex{p=intersect, visible=true}) | |
| if d0 > 0 { | |
| edge.vertex[1] = i32(index) | |
| } | |
| else { | |
| edge.vertex[0] = i32(index) | |
| } | |
| } | |
| // | |
| // face processing | |
| // | |
| close_face_index := i32(len(mesh.F)) | |
| append(&mesh.F, C_Face{}) | |
| close_face := &mesh.F[close_face_index] | |
| close_face.plane = Plane{-plane.n, plane.d} | |
| close_face.visible = true | |
| for &face, face_index in mesh.F { | |
| if !face.visible do continue | |
| for edge_index in face.edges { | |
| mesh.V[mesh.E[edge_index].vertex[0]].occurs = 0 | |
| mesh.V[mesh.E[edge_index].vertex[1]].occurs = 0 | |
| } | |
| for edge_index in face.edges { | |
| mesh.V[mesh.E[edge_index].vertex[0]].occurs += 1 | |
| mesh.V[mesh.E[edge_index].vertex[1]].occurs += 1 | |
| } | |
| start, final := i32(-1), i32(-1) | |
| for edge_index in face.edges { | |
| i0 := mesh.E[edge_index].vertex[0] | |
| i1 := mesh.E[edge_index].vertex[1] | |
| if mesh.V[i0].occurs == 1 { | |
| if start == -1 { | |
| start = i0 | |
| } | |
| else if final == -1 { | |
| final = i0 | |
| } | |
| } | |
| if mesh.V[i1].occurs == 1 { | |
| if start == -1 { | |
| start = i1 | |
| } | |
| else if final == -1 { | |
| final = i1 | |
| } | |
| } | |
| if start != -1 && final != -1 do break | |
| } | |
| if start != -1 { | |
| // close polyline | |
| close_edge_index := i32(len(mesh.E)) | |
| close_edge: C_Edge | |
| close_edge.vertex[0] = start | |
| close_edge.vertex[1] = final | |
| close_edge.f0 = i32(face_index) | |
| close_edge.f1 = close_face_index | |
| close_edge.visible = true | |
| append(&mesh.E, close_edge) | |
| append(&face.edges, close_edge_index) | |
| append(&close_face.edges, close_edge_index) | |
| } | |
| } | |
| } | |
| return result | |
| } | |
| delete_clipper_mesh :: proc(mesh: ^C_Mesh) { | |
| delete(mesh.V) | |
| delete(mesh.E) | |
| for &face in mesh.F { | |
| delete(face.edges) | |
| } | |
| delete(mesh.F) | |
| mesh ^= {} | |
| } | |
| remove_edge :: proc(face: ^C_Face, to_remove: int) { | |
| for edge, edge_index in face.edges { | |
| if edge == i32(to_remove) { | |
| unordered_remove(&face.edges, edge_index) | |
| break | |
| } | |
| } | |
| if len(face.edges) == 0 { | |
| face.visible = false | |
| } | |
| } | |
| // convert C_Mesh to A_Mesh | |
| swap :: proc(a: ^$T, b: ^T) { a^, b^ = b^, a^ } | |
| get_ordered_vertices :: proc(mesh_edges: []C_Edge, face_edges: []i32, allocator := context.temp_allocator) -> []i32 { | |
| edges := make([]i32, len(face_edges), context.temp_allocator) | |
| for i := 0; i < len(face_edges); i += 1 { | |
| edges[i] = face_edges[i] | |
| } | |
| choice := 1 | |
| for i0 := 0; i0 < len(face_edges) - 2; i0 += 1 { | |
| i1 := i0 + 1 | |
| current_edge := &mesh_edges[edges[i0]] | |
| current_vertex := current_edge.vertex[choice] | |
| for j := i1; j < len(face_edges); j += 1 { | |
| edge := &mesh_edges[edges[j]] | |
| if edge.vertex[0] == current_vertex { | |
| swap(&edges[i1], &edges[j]) | |
| choice = 1 | |
| break | |
| } | |
| if edge.vertex[1] == current_vertex { | |
| swap(&edges[i1], &edges[j]) | |
| choice = 0 | |
| break | |
| } | |
| } | |
| } | |
| vertices := make([]i32, len(face_edges) + 1, allocator) | |
| vertices[0] = mesh_edges[edges[0]].vertex[0] | |
| vertices[1] = mesh_edges[edges[0]].vertex[1] | |
| for i := 1; i < len(face_edges); i += 1 { | |
| edge := &mesh_edges[edges[i]] | |
| if edge.vertex[0] == vertices[i] { | |
| vertices[i + 1] = edge.vertex[1] | |
| } | |
| else { | |
| vertices[i + 1] = edge.vertex[0] | |
| } | |
| } | |
| return vertices | |
| } | |
| convert_c_mesh_to_a_mesh :: proc(mesh: C_Mesh) -> A_Mesh { | |
| vertices := make([dynamic]Vec3, allocator=context.temp_allocator) | |
| vmap := make([]i32, len(mesh.V), allocator=context.temp_allocator) | |
| for vertex, i in mesh.V { | |
| if vertex.visible { | |
| vmap[i] = i32(len(vertices)) | |
| append(&vertices, vertex.p) | |
| } | |
| else { | |
| vmap[i] = -1 | |
| } | |
| } | |
| faces := make([dynamic]i32, allocator=context.temp_allocator) | |
| // make stream of ordered faces | |
| for face in mesh.F { | |
| if !face.visible do continue | |
| vertices := get_ordered_vertices(mesh.E[:], face.edges[:], context.temp_allocator) | |
| append(&faces, i32(len(vertices) - 1)) | |
| normal: Vec3 | |
| for i := 0; i < len(vertices) - 1; i += 1 { | |
| v0 := mesh.V[vertices[i + 0]].p | |
| v1 := mesh.V[vertices[i + 1]].p | |
| normal += linalg.cross(v0, v1) | |
| } | |
| normal = linalg.normalize(normal) | |
| if (linalg.dot(face.plane.n, normal) > 0.0) { | |
| // clockwise, need to swap | |
| for j := len(vertices) - 2; j >= 0; j -= 1 { | |
| append(&faces, vmap[vertices[j]]) | |
| } | |
| } | |
| else { | |
| // counter-clockwise | |
| for j := 0; j <= len(vertices) - 2; j += 1 { | |
| append(&faces, vmap[vertices[j]]) | |
| } | |
| } | |
| } | |
| result := make_a_mesh_from_vertices_and_ordered_faces(vertices[:], faces[:]) | |
| return result | |
| } | |
| make_a_mesh_from_vertices_and_ordered_faces :: proc(vertices: []Vec3, faces: []i32) -> A_Mesh { | |
| result: A_Mesh | |
| append(&result.V, ..vertices) | |
| // Faces are given as a stream of subarrays, each first a count and then the vertices. | |
| face_index := i32(0) | |
| for i := 0; i < len(faces); { | |
| index_count := faces[i] | |
| i += 1 | |
| // triangulate with triangle fan | |
| for j := 1; j < int(index_count) - 1; j += 1 { | |
| append(&result.I, faces[i + 0]) | |
| append(&result.I, faces[i + j + 1]) | |
| append(&result.I, faces[i + j]) | |
| } | |
| i += int(index_count) | |
| } | |
| // reconstruct edges/faces from triangles | |
| triangle_count := len(result.I) / 3 | |
| reserve(&result.F, triangle_count) | |
| Vertex_Tuple :: [2]i32 | |
| sorted_tuple :: proc(v0_, v1_: i32) -> Vertex_Tuple { | |
| v0, v1 := v0_, v1_ | |
| if v0 > v1 do swap(&v0, &v1) | |
| return Vertex_Tuple{v0, v1} | |
| } | |
| edge_map := make(map[Vertex_Tuple]i32, allocator=context.temp_allocator) | |
| for face_index := 0; face_index < triangle_count; face_index += 1 { | |
| v0 := result.I[3*face_index + 0] | |
| v1 := result.I[3*face_index + 1] | |
| v2 := result.I[3*face_index + 2] | |
| tuples := [?]Vertex_Tuple{ | |
| sorted_tuple(v0, v1), | |
| sorted_tuple(v1, v2), | |
| sorted_tuple(v2, v0), | |
| } | |
| edge_indices: [3]i32 | |
| for tuple, tuple_index in tuples { | |
| edge_index, ok := edge_map[tuple] | |
| if ok { | |
| result.E[edge_index].f1 = i32(face_index) | |
| } | |
| else { | |
| edge_index = i32(len(&result.E)) | |
| append(&result.E, A_Edge{ | |
| v0 = tuple[0], | |
| v1 = tuple[1], | |
| f0 = i32(face_index), | |
| }) | |
| edge_map[tuple] = edge_index | |
| } | |
| edge_indices[tuple_index] = edge_index | |
| } | |
| append(&result.F, A_Face{ | |
| e0 = edge_indices[0], | |
| e1 = edge_indices[1], | |
| e2 = edge_indices[2], | |
| }) | |
| } | |
| return result | |
| } | |
| // program | |
| Mode :: enum { | |
| BASIC, | |
| VIEW_VERTICES, | |
| VIEW_EDGES, | |
| VIEW_FACES, | |
| INSPECT_FACE, | |
| VIEW_AMESH, | |
| VIEW_AMESH_EDGES, | |
| VIEW_AMESH_FACES, | |
| } | |
| g_mode_feature_toggle: [Mode]bool | |
| g_mode_has_feature_toggle: [Mode]bool = { | |
| .BASIC = false, | |
| .VIEW_VERTICES = true, | |
| .VIEW_EDGES = true, | |
| .VIEW_FACES = false, | |
| .INSPECT_FACE = false, | |
| .VIEW_AMESH = true, | |
| .VIEW_AMESH_EDGES = true, | |
| .VIEW_AMESH_FACES = true, | |
| } | |
| g_mode : Mode | |
| g_planes : [dynamic]Plane | |
| g_cutting_planes_count : int | |
| g_inspect_face : int | |
| g_paused : bool | |
| g_manual_rotation : f32 | |
| g_fill_triangle_mesh : bool = true | |
| g_show_text : bool = true | |
| MAX_SIZE :: Vec3{16, 16, 16} | |
| main :: proc() { | |
| rl.SetWindowState(rl.ConfigFlags{.WINDOW_RESIZABLE}) | |
| rl.InitWindow(1280, 720, "Clip3D"); | |
| defer rl.CloseWindow() | |
| rl.SetTargetFPS(60) | |
| camera: rl.Camera | |
| camera.position = Vec3{0.8*MAX_SIZE.x, 1.2*MAX_SIZE.x, -2.0*MAX_SIZE.x} | |
| camera.target = Vec3{0.0, 0.0, 0.0} | |
| camera.up = Vec3{0.0, 1.0, 0.0} | |
| camera.fovy = 60.0 | |
| camera.projection = .PERSPECTIVE | |
| { | |
| plane: Plane | |
| plane.n = linalg.normalize(Vec3{1, 1, 1}) | |
| append(&g_planes, plane) | |
| } | |
| { | |
| plane: Plane | |
| plane.n = linalg.normalize(Vec3{1, 0.5, -0.2}) | |
| append(&g_planes, plane) | |
| } | |
| { | |
| plane: Plane | |
| plane.n = linalg.normalize(Vec3{-0.3, -0.5, 0.4}) | |
| append(&g_planes, plane) | |
| } | |
| // start out with just one | |
| g_cutting_planes_count = 1 | |
| time := 0.0 | |
| dt := f32(1.0 / 60.0) | |
| distance := f32(1.4) | |
| for (!rl.WindowShouldClose()) { | |
| if rl.IsKeyDown(.LEFT_ALT) && rl.IsKeyPressed(.ENTER) { | |
| rl.ToggleBorderlessWindowed() | |
| } | |
| window_w := rl.GetRenderWidth() | |
| window_h := rl.GetRenderHeight() | |
| center := Vec2{ 0.5*f32(window_w), 0.5*f32(window_h) } | |
| if rl.IsKeyPressed(.SPACE) { | |
| g_paused = !g_paused | |
| } | |
| if rl.IsKeyPressed(.T) { | |
| g_show_text = !g_show_text | |
| } | |
| if rl.IsKeyDown(.LEFT_CONTROL) { | |
| if g_mode == .INSPECT_FACE { | |
| if rl.IsKeyPressed(.LEFT) { | |
| if g_inspect_face > 0 do g_inspect_face -= 1 | |
| } | |
| if rl.IsKeyPressed(.RIGHT) { | |
| if g_inspect_face < 32 do g_inspect_face += 1 | |
| } | |
| } | |
| } | |
| else if rl.IsKeyDown(.LEFT_SHIFT) { | |
| if rl.IsKeyPressed(.LEFT) { | |
| if g_cutting_planes_count > 0 do g_cutting_planes_count -= 1 | |
| } | |
| if rl.IsKeyPressed(.RIGHT) { | |
| if g_cutting_planes_count < len(g_planes) do g_cutting_planes_count += 1 | |
| } | |
| } | |
| else { | |
| if rl.IsKeyDown(.LEFT) { | |
| g_manual_rotation -= dt | |
| } | |
| if rl.IsKeyDown(.RIGHT) { | |
| g_manual_rotation += dt | |
| } | |
| } | |
| if rl.IsKeyDown(.DOWN) { | |
| distance += dt | |
| } | |
| if rl.IsKeyDown(.UP) { | |
| distance -= dt | |
| } | |
| // rotate camera | |
| camera.position.x = distance*MAX_SIZE.x*f32(math.cos(0.1*time + f64(g_manual_rotation))) | |
| camera.position.z = distance*MAX_SIZE.x*f32(math.sin(0.1*time + f64(g_manual_rotation))) | |
| g_planes[0].d = 0.5*MAX_SIZE.x*f32(math.sin(time)) | |
| g_planes[1].d = 0.5*MAX_SIZE.x*f32(math.cos(0.7*time)) | |
| g_planes[2].d = 0.3*MAX_SIZE.x*f32(math.sin(-0.3*time) - 0.8) | |
| planes := g_planes[:g_cutting_planes_count] | |
| c_mesh := mesh_from_intersection_of_half_spaces(planes) | |
| defer delete_clipper_mesh(&c_mesh) | |
| toggle_mode :: proc(mode: Mode) { | |
| if g_mode == mode { | |
| g_mode_feature_toggle[g_mode] = !g_mode_feature_toggle[g_mode] | |
| } | |
| g_mode = mode | |
| } | |
| if rl.IsKeyPressed(.ONE) do toggle_mode(.BASIC) | |
| if rl.IsKeyPressed(.TWO) do toggle_mode(.VIEW_VERTICES) | |
| if rl.IsKeyPressed(.THREE) do toggle_mode(.VIEW_EDGES) | |
| if rl.IsKeyPressed(.FOUR) do toggle_mode(.VIEW_FACES) | |
| if rl.IsKeyPressed(.FIVE) do toggle_mode(.INSPECT_FACE) | |
| if rl.IsKeyPressed(.SIX) do toggle_mode(.VIEW_AMESH) | |
| if rl.IsKeyPressed(.SEVEN) do toggle_mode(.VIEW_AMESH_EDGES) | |
| if rl.IsKeyPressed(.EIGHT) do toggle_mode(.VIEW_AMESH_FACES) | |
| rl.BeginDrawing() | |
| rl.ClearBackground(rl.BLACK) | |
| // mode | |
| { | |
| text := strings.clone_to_cstring(fmt.tprintf("mode: %v", g_mode), context.temp_allocator) | |
| text_color := rl.RAYWHITE | |
| if g_mode_feature_toggle[g_mode] && g_mode_has_feature_toggle[g_mode] { | |
| text_color = rl.Color{245, 245, 127, 255} | |
| } | |
| rl.DrawText(text, 32, 32, 16, text_color) | |
| } | |
| text_offset := c.int(8) | |
| // 3D rendering | |
| if g_mode == .VIEW_AMESH { | |
| rl.BeginMode3D(camera) | |
| context.allocator = context.temp_allocator | |
| a_mesh := convert_c_mesh_to_a_mesh(c_mesh) | |
| show_filled_triangle := !g_mode_feature_toggle[.VIEW_AMESH] | |
| normal_from_triangle :: proc(a, b, c: Vec3) -> Vec3 { | |
| ab := b - a | |
| ac := c - a | |
| normal := linalg.normalize(linalg.cross(ab, ac)) | |
| return normal | |
| } | |
| if show_filled_triangle { | |
| for i := 0; i < len(a_mesh.I) - 2; i += 3 { | |
| a := a_mesh.V[a_mesh.I[i + 0]] | |
| b := a_mesh.V[a_mesh.I[i + 1]] | |
| c := a_mesh.V[a_mesh.I[i + 2]] | |
| normal := normal_from_triangle(a, b, c) | |
| color_f := 0.5 + 0.5*normal | |
| color: rl.Color | |
| color.r = u8(255.0*color_f.r) | |
| color.g = u8(255.0*color_f.g) | |
| color.b = u8(255.0*color_f.b) | |
| color.a = 255 | |
| rl.DrawTriangle3D(a, b, c, color) | |
| } | |
| } | |
| for i := 0; i < len(a_mesh.I) - 2; i += 3 { | |
| a := a_mesh.V[a_mesh.I[i + 0]] | |
| b := a_mesh.V[a_mesh.I[i + 1]] | |
| c := a_mesh.V[a_mesh.I[i + 2]] | |
| normal := normal_from_triangle(a, b, c) | |
| a += 0.02*normal | |
| b += 0.02*normal | |
| c += 0.02*normal | |
| rl.DrawLine3D(a, b, rl.RED) | |
| rl.DrawLine3D(b, c, rl.RED) | |
| rl.DrawLine3D(c, a, rl.RED) | |
| } | |
| rl.EndMode3D() | |
| } | |
| else if g_mode == .VIEW_AMESH_EDGES { | |
| rl.BeginMode3D(camera) | |
| context.allocator = context.temp_allocator | |
| a_mesh := convert_c_mesh_to_a_mesh(c_mesh) | |
| draw_edge :: proc(mesh: A_Mesh, edge: A_Edge) { | |
| v0 := mesh.V[edge.v0] | |
| v1 := mesh.V[edge.v1] | |
| rl.DrawLine3D(v0, v1, rl.BLUE) | |
| rl.DrawSphere(v0, 0.1, rl.GREEN) | |
| rl.DrawSphere(v1, 0.1, rl.GREEN) | |
| } | |
| for edge in a_mesh.E { | |
| draw_edge(a_mesh, edge) | |
| } | |
| rl.EndMode3D() | |
| if !g_mode_feature_toggle[.VIEW_AMESH_EDGES] { | |
| for edge, edge_index in a_mesh.E { | |
| v0 := a_mesh.V[edge.v0] | |
| v1 := a_mesh.V[edge.v1] | |
| center := 0.5*(v0 + v1) | |
| screen := rl.GetWorldToScreen(center, camera) | |
| text_color := rl.RAYWHITE | |
| rl.DrawLineV(screen, screen + f32(text_offset), rl.BLUE) | |
| text := strings.clone_to_cstring(fmt.tprintf("e%v [f%v f%v]", edge_index, edge.f0, edge.f1), context.temp_allocator) | |
| rl.DrawText(text, c.int(screen.x) + text_offset, c.int(screen.y) + text_offset, 16, text_color) | |
| } | |
| } | |
| } | |
| else if g_mode == .VIEW_AMESH_FACES { | |
| rl.BeginMode3D(camera) | |
| context.allocator = context.temp_allocator | |
| a_mesh := convert_c_mesh_to_a_mesh(c_mesh) | |
| draw_edge :: proc(mesh: A_Mesh, edge: A_Edge) { | |
| v0 := mesh.V[edge.v0] | |
| v1 := mesh.V[edge.v1] | |
| rl.DrawLine3D(v0, v1, rl.YELLOW) | |
| rl.DrawSphere(v0, 0.1, rl.PINK) | |
| rl.DrawSphere(v1, 0.1, rl.PINK) | |
| } | |
| for face, face_index in a_mesh.F { | |
| e0 := a_mesh.E[face.e0] | |
| e1 := a_mesh.E[face.e1] | |
| e2 := a_mesh.E[face.e2] | |
| draw_edge(a_mesh, e0) | |
| draw_edge(a_mesh, e1) | |
| draw_edge(a_mesh, e2) | |
| } | |
| rl.EndMode3D() | |
| if !g_mode_feature_toggle[.VIEW_AMESH_FACES] { | |
| for face, face_index in a_mesh.F { | |
| e0 := a_mesh.E[face.e0] | |
| e1 := a_mesh.E[face.e1] | |
| e2 := a_mesh.E[face.e1] | |
| center: Vec3 | |
| center += a_mesh.V[e0.v0] | |
| center += a_mesh.V[e0.v1] | |
| center += a_mesh.V[e1.v0] | |
| center += a_mesh.V[e1.v1] | |
| center += a_mesh.V[e2.v0] | |
| center += a_mesh.V[e2.v1] | |
| center /= 6.0 | |
| screen := rl.GetWorldToScreen(center, camera) | |
| text_color := rl.RAYWHITE | |
| t := f32(face_index) / f32(len(a_mesh.F)) | |
| color := rl.ColorFromHSV(360.0*t, 1.0, 1.0) | |
| rl.DrawCircleV(screen, 4.0, rl.GREEN) | |
| rl.DrawLineV(screen, screen + f32(text_offset), rl.GREEN) | |
| text := strings.clone_to_cstring(fmt.tprintf("f%v [e%v e%v e%v]", face_index, face.e0, face.e1, face.e2), context.temp_allocator) | |
| rl.DrawText(text, c.int(screen.x) + text_offset, c.int(screen.y) + text_offset, 16, text_color) | |
| } | |
| } | |
| } | |
| else { | |
| rl.BeginMode3D(camera) | |
| show_hidden_vertices := g_mode_feature_toggle[.VIEW_VERTICES] | |
| show_hidden_edges := g_mode_feature_toggle[.VIEW_EDGES] | |
| for edge in c_mesh.E { | |
| if !edge.visible do continue | |
| p0 := c_mesh.V[edge.vertex[0]] | |
| p1 := c_mesh.V[edge.vertex[1]] | |
| color := rl.RED | |
| if g_mode == .INSPECT_FACE { | |
| color.a = 127 | |
| } | |
| rl.DrawLine3D(p0.p, p1.p, color) | |
| } | |
| if g_mode == .VIEW_FACES { | |
| for face, face_index in c_mesh.F { | |
| if !face.visible do continue | |
| t := f32(face_index) / f32(len(c_mesh.F)) | |
| color := rl.ColorFromHSV(360.0*t, 1.0, 1.0) | |
| color.a = face.visible ? 127 : 32 | |
| n := face.plane.n | |
| for edge, edge_index in face.edges { | |
| v0 := c_mesh.V[c_mesh.E[edge].vertex[0]] | |
| v1 := c_mesh.V[c_mesh.E[edge].vertex[1]] | |
| rl.DrawLine3D(v0.p + 0.1*n, v1.p + 0.1*n, color) | |
| } | |
| } | |
| } | |
| // draw planes | |
| for plane in planes { | |
| p := plane.n*plane.d | |
| rl.DrawSphere(p, 0.1, rl.BLUE) | |
| rl.DrawLine3D(p, p + plane.n, rl.BLUE) | |
| t, b := get_tangent_vectors(plane.n) | |
| r := MAX_SIZE.x*0.1 | |
| v00 := p - r*t - r*b | |
| v10 := p + r*t - r*b | |
| v11 := p + r*t + r*b | |
| v01 := p - r*t + r*b | |
| plane_color := rl.BLUE | |
| plane_color.a = 64 | |
| rl.DrawLine3D(v00, v10, plane_color) | |
| rl.DrawLine3D(v10, v11, plane_color) | |
| rl.DrawLine3D(v11, v01, plane_color) | |
| rl.DrawLine3D(v01, v00, plane_color) | |
| } | |
| rl.EndMode3D() | |
| // 2D rendering | |
| if g_mode == .VIEW_VERTICES { | |
| for vert, vert_index in c_mesh.V { | |
| if !show_hidden_vertices && !vert.visible do continue | |
| screen := rl.GetWorldToScreen(vert.p, camera) | |
| color := rl.GREEN | |
| if !vert.visible do color.a = 48 | |
| rl.DrawCircleV(screen, 8.0, color) | |
| text_color := rl.RAYWHITE | |
| if !vert.visible do text_color.a = 48 | |
| text := strings.clone_to_cstring(fmt.tprintf("v%v", vert_index), context.temp_allocator) | |
| rl.DrawText(text, c.int(screen.x) + text_offset, c.int(screen.y) + text_offset, 16, text_color) | |
| } | |
| if g_show_text do print_array("mesh.V", {32, 64}, c_mesh.V[:]) | |
| } | |
| if g_mode == .VIEW_EDGES { | |
| for edge, edge_index in c_mesh.E { | |
| if !show_hidden_edges && !edge.visible do continue | |
| v0 := c_mesh.V[edge.vertex[0]] | |
| v1 := c_mesh.V[edge.vertex[1]] | |
| v0_screen := rl.GetWorldToScreen(v0.p, camera) | |
| v1_screen := rl.GetWorldToScreen(v1.p, camera) | |
| screen := rl.GetWorldToScreen(0.5*(v0.p + v1.p), camera) | |
| color := rl.BLUE; | |
| text_color := rl.RAYWHITE; | |
| if !edge.visible { | |
| color .a = 48 | |
| text_color.a = 48 | |
| } | |
| rl.DrawLineEx(v0_screen, v1_screen, edge.visible ? 2.0 : 1.0, color) | |
| text := strings.clone_to_cstring(fmt.tprintf("e%v", edge_index), context.temp_allocator) | |
| rl.DrawText(text, c.int(screen.x) + text_offset, c.int(screen.y) + text_offset, 16, text_color) | |
| } | |
| if g_show_text do print_array("mesh.E", {32, 64}, c_mesh.E[:]) | |
| } | |
| if g_mode == .VIEW_FACES { | |
| for face, face_index in c_mesh.F { | |
| if !face.visible do continue | |
| count : f32 | |
| center_p : Vec3 | |
| t := f32(face_index) / f32(len(c_mesh.F)) | |
| color := rl.ColorFromHSV(360.0*t, 1.0, 1.0) | |
| for edge, edge_index in face.edges { | |
| v0 := c_mesh.V[c_mesh.E[edge].vertex[0]] | |
| v1 := c_mesh.V[c_mesh.E[edge].vertex[1]] | |
| center_p += 0.5*(v0.p + v1.p) | |
| count += 1.0 | |
| } | |
| center_p /= count | |
| normal := face.plane.n | |
| screen := rl.GetWorldToScreen(center_p, camera) | |
| screen_tip := rl.GetWorldToScreen(center_p + 2.0*normal, camera) | |
| rl.DrawCircleV(screen, 4.0, color) | |
| rl.DrawLineV(screen, screen_tip, color) | |
| rl.DrawCircleV(screen_tip, 2.0, color) | |
| text_color := rl.RAYWHITE | |
| text := strings.clone_to_cstring(fmt.tprintf("f%v", face_index), context.temp_allocator) | |
| rl.DrawText(text, c.int(screen.x) + text_offset, c.int(screen.y) + text_offset, 16, text_color) | |
| } | |
| if g_show_text do print_array("mesh.F", {32, 64}, c_mesh.F[:]) | |
| } | |
| if g_mode == .INSPECT_FACE { | |
| if g_inspect_face < len(c_mesh.F) { | |
| position := Vec2{32, 64} | |
| face := &c_mesh.F[g_inspect_face] | |
| ordered_vertices: []i32 | |
| if face.visible { | |
| ordered_vertices = get_ordered_vertices(c_mesh.E[:], face.edges[:], context.temp_allocator) | |
| } | |
| // edges | |
| for i := 0; i < len(ordered_vertices) - 1; i += 1 { | |
| v0 := c_mesh.V[ordered_vertices[i + 0]].p | |
| v1 := c_mesh.V[ordered_vertices[i + 1]].p | |
| v0_screen := rl.GetWorldToScreen(v0, camera) | |
| v1_screen := rl.GetWorldToScreen(v1, camera) | |
| d_screen := v1_screen - v0_screen | |
| perp_screen := linalg.normalize(Vec2{-d_screen.y, d_screen.x}) | |
| arrow0 := 8*(-linalg.normalize(d_screen) + perp_screen) | |
| arrow1 := 8*(-linalg.normalize(d_screen) - perp_screen) | |
| rl.DrawLineV(v0_screen, v1_screen, rl.BLUE) | |
| rl.DrawLineV(v1_screen, v1_screen + arrow0, rl.BLUE) | |
| rl.DrawLineV(v1_screen, v1_screen + arrow1, rl.BLUE) | |
| e_screen := 0.5*(v0_screen + v1_screen) | |
| rl.DrawLineV(e_screen, e_screen + 0.5*arrow0, rl.BLUE) | |
| rl.DrawLineV(e_screen, e_screen + 0.5*arrow1, rl.BLUE) | |
| } | |
| // vertices | |
| for i := 0; i < len(ordered_vertices) - 1; i += 1 { | |
| v0 := c_mesh.V[ordered_vertices[i]].p | |
| v0_screen := rl.GetWorldToScreen(v0, camera) | |
| color := i == 0 ? rl.YELLOW : rl.GREEN | |
| rl.DrawCircleV(v0_screen, 4.0, color) | |
| } | |
| if g_show_text do position = print_item(fmt.tprintf("face[%v]", g_inspect_face), position, face) | |
| position.y += 2 | |
| for edge_index in face.edges { | |
| edge := c_mesh.E[edge_index] | |
| if g_show_text do position = print_item(fmt.tprintf("e%v", edge_index), position, edge) | |
| v0 := c_mesh.V[edge.vertex[0]] | |
| v1 := c_mesh.V[edge.vertex[1]] | |
| v0_screen := rl.GetWorldToScreen(v0.p, camera) | |
| v1_screen := rl.GetWorldToScreen(v1.p, camera) | |
| screen := rl.GetWorldToScreen(0.5*(v0.p + v1.p), camera) | |
| { | |
| color := edge.visible ? rl.BLUE : rl.Color{24, 32, 64, 255} | |
| text_color := edge.visible ? rl.RAYWHITE : rl.Color{64, 64, 64, 255} | |
| text := strings.clone_to_cstring(fmt.tprintf("e%v", edge_index), context.temp_allocator) | |
| rl.DrawText(text, c.int(screen.x) + text_offset, c.int(screen.y) + text_offset, 16, text_color) | |
| } | |
| { | |
| text := strings.clone_to_cstring(fmt.tprintf("v%v", edge.vertex[0]), context.temp_allocator) | |
| rl.DrawText(text, c.int(v0_screen.x) + text_offset, c.int(v0_screen.y) + text_offset, 16, rl.RAYWHITE) | |
| } | |
| { | |
| text := strings.clone_to_cstring(fmt.tprintf("v%v", edge.vertex[1]), context.temp_allocator) | |
| rl.DrawText(text, c.int(v1_screen.x) + text_offset, c.int(v1_screen.y) + text_offset, 16, rl.RAYWHITE) | |
| } | |
| } | |
| position.y += 2 | |
| if g_show_text && face.visible { | |
| text := strings.clone_to_cstring(fmt.tprintf("ordered_vertices = %v", ordered_vertices), context.temp_allocator) | |
| rl.DrawText(text, c.int(position.x), c.int(position.y), 18, rl.RAYWHITE) | |
| } | |
| } | |
| } | |
| } | |
| { | |
| text := strings.clone_to_cstring(fmt.tprintf("cutting planes count: %v", g_cutting_planes_count), context.temp_allocator) | |
| text_color := rl.RAYWHITE | |
| if rl.IsKeyDown(.LEFT_SHIFT) { | |
| text_color = rl.BLUE | |
| } | |
| rl.DrawText(text, 32, c.int(window_h) - 32, 18, text_color) | |
| } | |
| rl.EndDrawing() | |
| if !g_paused { | |
| time += 1.0 / 60.0 | |
| } | |
| mem.free_all(context.temp_allocator) | |
| } | |
| } | |
| // initial cube | |
| make_cube_clipper_mesh :: proc(initial_size: Vec3) -> C_Mesh { | |
| mesh: C_Mesh | |
| r := 0.5*initial_size | |
| // diagram (both views are top down looking down the negative y axis) | |
| // | |
| // bottom top | |
| // f3 | |
| // v e11 e10 | |
| // \ / | |
| // v3 --- e2 --- v2 v7 --- e6 --- v6 | |
| // | | | | | |
| // | | | | | |
| // f4 > e3 f0 e1 < f2 e7 f5 e5 | |
| // | | | | | |
| // | | | | | |
| // v0 --- e0 --- v1 v4 --- e4 --- v5 | |
| // / \ | |
| // ^ e8 e9 | |
| // ^ f1 | |
| // | | |
| // +z | | |
| // --------> | |
| // +x | |
| // | |
| // bottom quad | |
| // | |
| // vertices | |
| append(&mesh.V, C_Vertex{p={-r.x, -r.y, -r.z}, visible=true}) // v0 | |
| append(&mesh.V, C_Vertex{p={ r.x, -r.y, -r.z}, visible=true}) // v1 | |
| append(&mesh.V, C_Vertex{p={ r.x, -r.y, r.z}, visible=true}) // v2 | |
| append(&mesh.V, C_Vertex{p={-r.x, -r.y, r.z}, visible=true}) // v3 | |
| // edges | |
| append(&mesh.E, C_Edge{{0, 1}, 0, 1, true}) // e0 | |
| append(&mesh.E, C_Edge{{1, 2}, 0, 2, true}) // e1 | |
| append(&mesh.E, C_Edge{{2, 3}, 0, 3, true}) // e2 | |
| append(&mesh.E, C_Edge{{3, 0}, 0, 4, true}) // e3 | |
| // | |
| // top quad | |
| // | |
| // vertices | |
| append(&mesh.V, C_Vertex{p={-r.x, r.y, -r.z}, visible=true}) // v4 | |
| append(&mesh.V, C_Vertex{p={ r.x, r.y, -r.z}, visible=true}) // v5 | |
| append(&mesh.V, C_Vertex{p={ r.x, r.y, r.z}, visible=true}) // v6 | |
| append(&mesh.V, C_Vertex{p={-r.x, r.y, r.z}, visible=true}) // v7 | |
| // edges | |
| append(&mesh.E, C_Edge{{4, 5}, 5, 1, true}) // e4 | |
| append(&mesh.E, C_Edge{{5, 6}, 5, 2, true}) // e5 | |
| append(&mesh.E, C_Edge{{6, 7}, 5, 3, true}) // e6 | |
| append(&mesh.E, C_Edge{{7, 4}, 5, 4, true}) // e7 | |
| // | |
| // "column" edges | |
| // | |
| append(&mesh.E, C_Edge{{0, 4}, 4, 1, true}) // e8 | |
| append(&mesh.E, C_Edge{{1, 5}, 1, 2, true}) // e9 | |
| append(&mesh.E, C_Edge{{2, 6}, 2, 3, true}) // e10 | |
| append(&mesh.E, C_Edge{{3, 7}, 3, 4, true}) // e11 | |
| // | |
| // faces | |
| // | |
| append(&mesh.F, C_Face{edges=[dynamic]i32{0, 1, 2, 3}, plane={{ 0, -1, 0}, r.y}, visible=true}) // f0 | |
| append(&mesh.F, C_Face{edges=[dynamic]i32{0, 9, 4, 8}, plane={{ 0, 0, -1}, r.z}, visible=true}) // f1 | |
| append(&mesh.F, C_Face{edges=[dynamic]i32{1, 10, 5, 9}, plane={{ 1, 0, 0}, r.x}, visible=true}) // f2 | |
| append(&mesh.F, C_Face{edges=[dynamic]i32{2, 11, 6, 10}, plane={{ 0, 0, 1}, r.z}, visible=true}) // f3 | |
| append(&mesh.F, C_Face{edges=[dynamic]i32{3, 8, 7, 11}, plane={{-1, 0, 0}, r.x}, visible=true}) // f4 | |
| append(&mesh.F, C_Face{edges=[dynamic]i32{4, 5, 6, 7}, plane={{ 0, 1, 0}, r.y}, visible=true}) // f5 | |
| return mesh | |
| } | |
| mesh_from_intersection_of_half_spaces :: proc(planes: []Plane) -> C_Mesh { | |
| mesh := make_cube_clipper_mesh(MAX_SIZE) | |
| clip_mesh_against_planes(&mesh, planes) | |
| return mesh | |
| } | |
| // misc helpers | |
| get_tangent_vectors :: proc(n: Vec3) -> (Vec3, Vec3) { | |
| up := Vec3{0, 1, 0}; | |
| t, b: Vec3 | |
| t = linalg.cross(up, n); | |
| if linalg.length(t) <= 0.01 | |
| { | |
| up = Vec3{0, 0, 1} | |
| t = linalg.cross(up, n); | |
| } | |
| t = linalg.normalize(t); | |
| b = linalg.normalize(linalg.cross(n, t)); | |
| return t, b | |
| } | |
| print_item :: proc(name: string, position: Vec2, item: $T, font_size := c.int(18), color := rl.RAYWHITE) -> Vec2 { | |
| x := c.int(position.x) | |
| y := c.int(position.y) | |
| text := strings.clone_to_cstring(fmt.tprintf("%v = %v", name, item), context.temp_allocator) | |
| actual_color := item.visible ? color : rl.Color{64, 64, 64, 255} | |
| rl.DrawText(text, x, y, font_size, actual_color) | |
| y += font_size + 2 | |
| return Vec2{f32(x), f32(y)} | |
| } | |
| print_array :: proc(name: string, position: Vec2, array: []$T, font_size := c.int(18), color := rl.RAYWHITE) -> Vec2 { | |
| x := c.int(position.x) | |
| y := c.int(position.y) | |
| for item, index in array { | |
| text := strings.clone_to_cstring(fmt.tprintf("%v[%v] = %v", name, index, item), context.temp_allocator) | |
| actual_color := item.visible ? color : rl.Color{64, 64, 64, 255} | |
| rl.DrawText(text, x, y, font_size, actual_color) | |
| y += font_size + 2 | |
| } | |
| return Vec2{f32(x), f32(y)} | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
