fabrik/
main.rs

1//! FABRIK (Forward And Backward Reaching Inverse Kinematics) example.
2
3use std::io;
4use std::io::stdout;
5use std::time::Instant;
6use crossterm::event::KeyCode;
7use teng::components::Component;
8use teng::rendering::pixel::Pixel;
9use teng::rendering::renderer::Renderer;
10use teng::util::planarvec::Bounds;
11use teng::util::planarvec2_experimental::ExponentialGrowingBounds;
12use teng::{
13    install_panic_handler, terminal_cleanup, terminal_setup, Game, SetupInfo, SharedState,
14    UpdateInfo,
15};
16use teng::rendering::color::Color;
17use teng::rendering::render::{HalfBlockDisplayRender, Render};
18use teng::util::for_coord_in_line;
19
20fn main() -> io::Result<()> {
21    terminal_setup()?;
22    install_panic_handler();
23
24    let mut game = Game::new_with_custom_buf_writer();
25    game.install_recommended_components();
26    game.add_component(Box::new(FabrikComponent::new()));
27    game.run()?;
28
29    terminal_cleanup()?;
30
31    Ok(())
32}
33
34#[derive(Copy, Clone)]
35struct Point<T> {
36    x: T,
37    y: T,
38}
39
40impl Point<f64> {
41    fn distance(&self, other: &Self) -> f64 {
42        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
43    }
44}
45
46struct Segment {
47    start: Point<f64>,
48    length: f64,
49}
50
51pub struct FabrikComponent {
52    base_anchor: Option<Point<f64>>,
53    target: Option<Point<f64>>,
54    segments: Vec<Segment>,
55    last_point: Option<Point<f64>>,
56    currently_creating_segment: Option<Point<f64>>,
57    half_block_display_render: HalfBlockDisplayRender,
58}
59
60impl FabrikComponent {
61    pub fn new() -> Self {
62        Self {
63            base_anchor: None,
64            target: None,
65            segments: vec![],
66            last_point: None,
67            currently_creating_segment: None,
68            half_block_display_render: HalfBlockDisplayRender::new(0, 0),
69        }
70    }
71
72    fn forward_reach(&mut self) {
73        let Some(target) = self.target else {
74            return;
75        };
76        let mut last_point = target;
77        self.last_point = Some(target);
78        for segment in self.segments.iter_mut().rev() {
79            let start = last_point;
80            let end = segment.start;
81
82            let length = segment.length;
83            let mut distance = start.distance(&end);
84            if distance < 0.0001 {
85                distance = 0.0001;
86            }
87            let ratio = length / distance;
88
89            let new_end = Point {
90                x: (1.0 - ratio) * start.x + ratio * end.x,
91                y: (1.0 - ratio) * start.y + ratio * end.y,
92            };
93
94            segment.start = new_end;
95
96            last_point = new_end;
97        }
98    }
99
100    fn backward_reach(&mut self) {
101        let Some(base_anchor) = self.base_anchor else {
102            return;
103        };
104        let Some(target) = self.target else {
105            return;
106        };
107        let mut last_point = base_anchor;
108        for i in 0..self.segments.len() {
109            let start = last_point;
110            let end = if i < self.segments.len() - 1 {
111                self.segments[i + 1].start
112            } else {
113                target
114            };
115            let segment = &mut self.segments[i];
116            let length = segment.length;
117            let mut distance = start.distance(&end);
118            if distance < 0.0001 {
119                distance = 0.0001;
120            }
121            let ratio = length / distance;
122
123            let new_end = Point {
124                x: (1.0 - ratio) * start.x + ratio * end.x,
125                y: (1.0 - ratio) * start.y + ratio * end.y,
126            };
127
128            segment.start = start;
129
130            last_point = new_end;
131        }
132
133        self.last_point = Some(last_point);
134    }
135
136    fn render_to_half_block_display(&mut self, mouse_point: Point<f64>) {
137        self.half_block_display_render.clear();
138
139        let segment_line_color = Color::Rgb([255, 255, 255]);
140        let target_color = Color::Rgb([0, 255, 0]);
141        let creating_segment_color = Color::Rgb([255, 0, 0]);
142        let segment_start_color = Color::Rgb([255, 255, 0]);
143
144        let base_anchor_color = Color::Rgb([0, 0, 255]);
145
146        if self.segments.len() > 0 {
147            let mut last_point = self.last_point.unwrap(); // must have last point
148            for segment in self.segments.iter().rev() {
149                let start = last_point;
150                let end = segment.start;
151
152                let x_start = start.x.floor() as i64;
153                let y_start = start.y.floor() as i64;
154                let x_end = end.x.floor() as i64;
155                let y_end = end.y.floor() as i64;
156
157                for_coord_in_line(true, (x_start, y_start), (x_end, y_end), |x, y| {
158                    if x < 0 || y < 0 {
159                        return;
160                    }
161                    self.half_block_display_render.set_color(x as usize, y as usize, segment_line_color);
162                });
163
164                if x_start >= 0 && y_start >= 0 {
165                    self.half_block_display_render.set_color(x_start as usize, y_start as usize, segment_start_color);
166                }
167
168                last_point = segment.start;
169            }
170        }
171
172
173        if let Some(start_point) = self.currently_creating_segment {
174            let x_u_start = start_point.x.floor() as usize;
175            let y_u_start = start_point.y.floor() as usize;
176            let x_u_end = mouse_point.x.floor() as usize;
177            let y_u_end = mouse_point.y.floor() as usize;
178
179            for_coord_in_line(false, (x_u_start as i64, y_u_start as i64), (x_u_end as i64, y_u_end as i64), |x, y| {
180                self.half_block_display_render.set_color(x as usize, y as usize, creating_segment_color);
181            });
182        }
183
184        if let Some(target) = self.target {
185            let x_u = target.x.floor() as usize;
186            let y_u = target.y.floor() as usize;
187            self.half_block_display_render.set_color(x_u, y_u, target_color);
188        }
189
190        if let Some(base_anchor) = self.base_anchor {
191            let x_u = base_anchor.x.floor() as usize;
192            let y_u = base_anchor.y.floor() as usize;
193            self.half_block_display_render.set_color(x_u, y_u, base_anchor_color);
194        }
195    }
196}
197
198impl Component for FabrikComponent {
199    fn setup(&mut self, setup_info: &SetupInfo, shared_state: &mut SharedState<()>) {
200        // self.last_point = Point {
201        //     x: setup_info.display_info.width() as f64 / 2.0,
202        //     y: setup_info.display_info.height() as f64 / 2.0,
203        // };
204        self.on_resize(setup_info.display_info.width(), setup_info.display_info.height(), shared_state);
205    }
206
207    fn on_resize(&mut self, width: usize, height: usize, shared_state: &mut SharedState<()>) {
208        self.half_block_display_render = HalfBlockDisplayRender::new(width, 2 * height);
209    }
210
211    fn update(&mut self, update_info: UpdateInfo, shared_state: &mut SharedState<()>) {
212        let (x, y) = shared_state.mouse_info.last_mouse_pos;
213        let mouse_point = Point {
214            x: x as f64,
215            y: y as f64 * 2.0,
216        };
217
218        if shared_state.mouse_pressed.right {
219            // if we are not creating a segment but we do already have a last point, connect it immediately
220            if let Some(last_point) = self.last_point {
221                if self.currently_creating_segment.is_none() {
222                    let new_segment = Segment {
223                        start: last_point,
224                        length: last_point.distance(&mouse_point),
225                    };
226                    self.segments.push(new_segment);
227                    if self.base_anchor.is_none() {
228                        self.base_anchor = Some(last_point);
229                    }
230                    self.last_point = Some(mouse_point);
231                }
232            }
233
234            if let Some(start_point) = self.currently_creating_segment.take() {
235                let length = start_point.distance(&mouse_point);
236                self.last_point = Some(mouse_point);
237                let new_segment = Segment {
238                    start: start_point,
239                    length,
240                };
241                self.segments.push(new_segment);
242                if self.base_anchor.is_none() {
243                    self.base_anchor = Some(start_point);
244                }
245            }
246
247            self.currently_creating_segment = Some(mouse_point);
248        }
249
250        if shared_state.pressed_keys.inner().contains_key(&KeyCode::Esc) {
251            self.currently_creating_segment = None;
252        }
253
254        if shared_state.mouse_info.left_mouse_down {
255            // set target
256            self.target = Some(mouse_point);
257            self.currently_creating_segment = None;
258
259            self.forward_reach();
260            self.backward_reach();
261        }
262
263        shared_state.mouse_events.for_each_linerp_only_fresh(|mi| {
264            if mi.middle_mouse_down {
265                self.currently_creating_segment = None;
266                let mouse_point = Point {
267                    x: mi.last_mouse_pos.0 as f64,
268                    y: mi.last_mouse_pos.1 as f64 * 2.0,
269                };
270
271                if self.base_anchor.is_none() {
272                    self.base_anchor = Some(mouse_point);
273                }
274                let Some(last_point) = self.last_point else {
275                    self.last_point = Some(mouse_point);
276                    return;
277                };
278
279                let new_segment = Segment {
280                    start: last_point,
281                    length: last_point.distance(&mouse_point),
282                };
283                self.segments.push(new_segment);
284                self.last_point = Some(mouse_point);
285            }
286        });
287
288        // if shared_state.pressed_keys.did_press_char_ignore_case('f') {
289        //     self.forward_reach();
290        // }
291        // if shared_state.pressed_keys.did_press_char_ignore_case('b') {
292        //     self.backward_reach();
293        // }
294
295        if shared_state.pressed_keys.did_press_char_ignore_case('c') {
296            self.segments.clear();
297            self.base_anchor = None;
298            self.target = None;
299            self.currently_creating_segment = None;
300            self.last_point = None;
301        }
302
303
304        // render into half block display
305        self.render_to_half_block_display(mouse_point);
306    }
307
308    fn render(&self, renderer: &mut dyn Renderer, shared_state: &SharedState<()>, depth_base: i32) {
309        self.half_block_display_render.render(renderer, 0, 0, depth_base);
310    }
311}