summaryrefslogtreecommitdiff
path: root/src/sim/lib/components.rs
blob: ac62366bf3a8f00e87d839607837944bee5b14a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use fxhash::FxHashSet;
use glam::{IVec2, Vec2};

use crate::{content::materials::MaterialId, sim::cell::Cell};

struct ComponentCell {
    x: i32,
    y: i32,
    cell: Cell,
}
struct Component {
    // (least_x, least_y, most_x, most_y)
    bounds: [IVec2; 2],
    cells: Vec<ComponentCell>,
}

pub struct PositionedComponent {
    // offset of this chunk compared to the original object
    pub position: Vec2,
    pub size: IVec2,
    pub cells: Vec<Cell>,
}

// i32 could be u8 if this is chunk size bounded
fn dfs(
    seed: IVec2,
    w: i32,
    h: i32,
    cells: &[Cell],
    visited: &mut FxHashSet<(i32, i32)>,
    component: &mut Component,
    stack: &mut Vec<IVec2>,
) -> bool {
    stack.clear();
    stack.push(seed);

    let mut started_component = false;

    while let Some(pos) = stack.pop() {
        if pos.x < 0 || pos.x >= w || pos.y < 0 || pos.y >= h {
            continue;
        }

        // insert returns false if it was already present, so this is the
        // contains-then-insert pair in one lookup
        if !visited.insert((pos.x, pos.y)) {
            continue;
        }

        let cell = cells[(pos.x + pos.y * w) as usize];

        // TODO this is hacky
        // note we mark void cells visited before bailing, so they aren't retried
        if cell.material == MaterialId::Void {
            continue;
        }

        started_component = true;

        component.cells.push(ComponentCell {
            x: pos.x,
            y: pos.y,
            cell,
        });

        component.bounds[0] = component.bounds[0].min(pos);
        component.bounds[1] = component.bounds[1].max(pos);

        // includes diagonal neighbours because marching squares colliders do
        for dx in -1..=1 {
            for dy in -1..=1 {
                if dx == 0 && dy == 0 {
                    continue;
                }
                stack.push(pos + IVec2::new(dx, dy));
            }
        }
    }

    started_component
}

// this is a naive implementation
pub fn compute_components(cells: &[Cell], w: i32, h: i32) -> Vec<PositionedComponent> {
    puffin::profile_function!();
    let mut visited: FxHashSet<(i32, i32)> = FxHashSet::default();
    let mut positioned_components: Vec<PositionedComponent> = Vec::new();
    let mut stack: Vec<IVec2> = Vec::new();

    for x in 0..w {
        for y in 0..h {
            let mut c = Component {
                cells: Vec::new(),
                bounds: [IVec2::MAX, IVec2::MIN],
            };

            if dfs(
                IVec2::new(x, y),
                w,
                h,
                cells,
                &mut visited,
                &mut c,
                &mut stack,
            ) {
                let pc_size = (c.bounds[1] - c.bounds[0]) + IVec2::ONE;
                let mut pc_cells = vec![Cell::void(); (pc_size.x * pc_size.y) as usize];
                for old_cell in c.cells {
                    pc_cells[((old_cell.x - c.bounds[0].x)
                        + (old_cell.y - c.bounds[0].y) * pc_size.x)
                        as usize] = old_cell.cell;
                }

                let pc_centre = (c.bounds[0] + c.bounds[1] + IVec2::ONE).as_vec2() / 2.0;
                let adjusted_pc_centre = pc_centre - (Vec2::new(w as f32, h as f32) / 2.0);
                let pc = PositionedComponent {
                    position: adjusted_pc_centre,
                    size: pc_size,
                    cells: pc_cells,
                };

                positioned_components.push(pc);
            }
        }
    }

    positioned_components
}