Skip to content

Instantly share code, notes, and snippets.

@TheSandvichMaker
Last active December 28, 2024 22:28
Show Gist options
  • Save TheSandvichMaker/86baba805952150436cfb481e3a2bd6c to your computer and use it in GitHub Desktop.
Save TheSandvichMaker/86baba805952150436cfb481e3a2bd6c to your computer and use it in GitHub Desktop.
Implementation of David Eberly's mesh clipping algorithm
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