Skip to content

Commit

Permalink
add: scene system & basic menu
Browse files Browse the repository at this point in the history
Split bézier logic inside separate file to only keep scene logic in main
Add simple menu to select scene to load
  • Loading branch information
Captainfl4me committed May 5, 2024
1 parent d375f95 commit bf8c8dc
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 128 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ At first, I try setting up a version with the [Bevy game engine](https://github.

Well, if you have read until here, I will assume that you are kind of interesting in that project. Here is a quick list of the features and key binding:

- While inside a scene use `ESC` to go back to the menu.
- Bézier curve (from 2 to 62 control points)
- Use `SPACE` to add a new control point at the mouse position.
- Use `BACKSPACE` to remove the last control point.
Expand Down
126 changes: 126 additions & 0 deletions src/bezier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use raylib::prelude::*;

pub fn binomial(n: u64, k: u64) -> u64 {
if n >= 63 {
panic!("N is too great, will overflow!");
}
if k > n {
0
} else if k == 0 {
1
} else {
n * binomial(n - 1, n - k) / k
}
}

const POINTS_RADIUS: f32 = 10.0;
const POINTS_RADIUS_HOVER: f32 = 15.0;
const ANIMATION_SPEED: f32 = 1.0;
#[derive(Debug, Clone, Copy)]
pub struct Point {
pub position: Vector2,
pub radius: f32,
pub color: Color,
pub is_selected: bool,
pub is_hovered: bool,
}
impl Point {
pub fn new(position: Vector2, color: Color) -> Self {
Self {
position,
radius: POINTS_RADIUS,
color,
is_selected: false,
is_hovered: false,
}
}

pub fn udpate(&mut self, mouse_position: Vector2) {
self.is_hovered =
mouse_position.distance_to(self.position) < self.radius || self.is_selected;
if self.is_hovered {
if self.radius < POINTS_RADIUS_HOVER {
self.radius += ANIMATION_SPEED;
} else if self.radius > POINTS_RADIUS_HOVER {
self.radius = POINTS_RADIUS_HOVER;
}
} else if self.radius > POINTS_RADIUS {
self.radius -= ANIMATION_SPEED;
} else if self.radius < POINTS_RADIUS {
self.radius = POINTS_RADIUS;
}

if self.is_selected {
self.position = mouse_position;
}
}

pub fn draw(&self, d: &mut RaylibDrawHandle) {
d.draw_circle_v(self.position, self.radius, self.color);
}
}

const SAMPLES: usize = 50;

/// Evaluate a point on the curve
pub fn evalute_bezier_curve(points: &[Point], t: f32) -> Vector2 {
let n = points.len() - 1;
let tuple_point = points.iter().enumerate().fold((0.0, 0.0), |acc, (i, e)| {
let a = (binomial(n as u64, i as u64) as f32)
* (1.0 - t).powi((n - i) as i32)
* t.powi(i as i32);
(acc.0 + e.position.x * a, acc.1 + e.position.y * a)
});
Vector2::new(tuple_point.0, tuple_point.1)
}

/// Draw the curve
pub fn draw_bezier(points: &[Point], d: &mut RaylibDrawHandle, t: Option<f32>) {
for line_points in points.windows(2) {
d.draw_line_ex(
line_points[0].position,
line_points[1].position,
3.0,
Color::RED,
);
}

let mut final_point = None;
if let Some(t) = t {
let rec_size = Vector2::new(POINTS_RADIUS, POINTS_RADIUS);
let mut debug_points: Vec<Vector2> = points.iter().map(|p| p.position).collect::<Vec<_>>();
while debug_points.len() > 2 {
let next_points = debug_points
.windows(2)
.map(|w| w[0].lerp(w[1], t))
.collect::<Vec<_>>();
// Drawing lines before points so that points will override them
for p in next_points.windows(2) {
d.draw_line_ex(p[0], p[1], 2.0, Color::RED);
}
// Draw lerp points for this run
for p in next_points.iter() {
d.draw_rectangle_v(*p - rec_size * 0.5, rec_size, Color::GREEN);
}
debug_points = next_points;
}
final_point = Some(debug_points[0].lerp(debug_points[1], t));
}

let step = 1.0 / SAMPLES as f32;
let step_points = (0..=SAMPLES)
.map(|i| evalute_bezier_curve(points, i as f32 * step))
.collect::<Vec<_>>();

for line_points in step_points.windows(2) {
d.draw_line_ex(line_points[0], line_points[1], 3.0, Color::GREEN);
}

if let Some(final_point) = final_point {
d.draw_circle_v(final_point, POINTS_RADIUS / 2.0, Color::YELLOW);
}

for point in points.iter() {
point.draw(d);
}
}
197 changes: 69 additions & 128 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,81 @@ use raylib::prelude::*;
use std::ffi::CStr;
use std::time::SystemTime;

const T_ANIMATION_SPEED: f32 = 0.005;
mod bezier;
use bezier::*;

enum Scenes {
BezierCurve,
BezierSpline,
}

fn main() {
let (mut rl_handle, rl_thread) = raylib::init()
.size(640, 480)
.resizable()
.title("Spline drawer")
.build();
rl_handle.set_target_fps(60);
rl_handle.set_exit_key(None);
rl_handle.set_window_state(rl_handle.get_window_state().set_window_maximized(true));

const TITLE_FONT_SIZE: i32 = 60;
let mut scene_to_load = None;
while !rl_handle.window_should_close() {
{
let mut rl_draw_handle = rl_handle.begin_drawing(&rl_thread);
let screen_width = rl_draw_handle.get_screen_width();
let screen_height = rl_draw_handle.get_screen_height();
draw_background(&mut rl_draw_handle);

rl_draw_handle.draw_text(
"Spline Drawer",
(screen_width - rl_draw_handle.measure_text("Spline Drawer", TITLE_FONT_SIZE)) / 2,
(screen_height - TITLE_FONT_SIZE) / 2,
TITLE_FONT_SIZE,
Color::WHITE,
);

if rl_draw_handle.gui_button(
Rectangle::new(
(screen_width - 200) as f32 / 2.0,
(screen_height + TITLE_FONT_SIZE + 10) as f32 / 2.0,
200.0,
25.0,
),
Some(CStr::from_bytes_with_nul(b"Load Bezier curve\0").unwrap()),
) {
scene_to_load = Some(Scenes::BezierCurve);
}

if rl_draw_handle.gui_button(
Rectangle::new(
(screen_width - 200) as f32 / 2.0,
(screen_height + TITLE_FONT_SIZE*2 + 10) as f32 / 2.0,
200.0,
25.0,
),
Some(CStr::from_bytes_with_nul(b"Load Bezier spline\0").unwrap()),
) {
scene_to_load = Some(Scenes::BezierSpline);
}
}

match scene_to_load {
Some(Scenes::BezierCurve) => bezier_curve_scene(&mut rl_handle, &rl_thread),
Some(Scenes::BezierSpline) => bezier_curve_scene(&mut rl_handle, &rl_thread),
None => {}
}
scene_to_load = None;
}
}

fn draw_background(rl_draw_handle: &mut RaylibDrawHandle) {
rl_draw_handle.clear_background(Color::BLACK);
}

const T_ANIMATION_SPEED: f32 = 0.005;
fn bezier_curve_scene(rl_handle: &mut RaylibHandle, rl_thread: &RaylibThread) {
// Initialize
let mut points = [
Vector2::new(300.0, 600.0),
Expand Down Expand Up @@ -89,15 +154,16 @@ fn main() {
points.last_mut().unwrap().color = Color::BLUE;
}
}
KeyboardKey::KEY_ESCAPE => { break; }
_ => {}
}
}
}

// Update Frame
let mut rl_draw_handle = rl_handle.begin_drawing(&rl_thread);
let mut rl_draw_handle = rl_handle.begin_drawing(rl_thread);
let screen_width = rl_draw_handle.get_screen_width();
rl_draw_handle.clear_background(Color::BLACK);
draw_background(&mut rl_draw_handle);

let current_fps_text = format!("{} FPS", rl_draw_handle.get_fps());
rl_draw_handle.draw_text(
Expand Down Expand Up @@ -169,128 +235,3 @@ fn main() {
}
}
}

pub fn binomial(n: u64, k: u64) -> u64 {
if n >= 63 {
panic!("N is too great, will overflow!");
}
if k > n {
0
} else if k == 0 {
1
} else {
n * binomial(n - 1, n - k) / k
}
}

const POINTS_RADIUS: f32 = 10.0;
const POINTS_RADIUS_HOVER: f32 = 15.0;
const ANIMATION_SPEED: f32 = 1.0;
#[derive(Debug, Clone, Copy)]
struct Point {
position: Vector2,
radius: f32,
color: Color,
is_selected: bool,
is_hovered: bool,
}
impl Point {
pub fn new(position: Vector2, color: Color) -> Self {
Self {
position,
radius: POINTS_RADIUS,
color,
is_selected: false,
is_hovered: false,
}
}

pub fn udpate(&mut self, mouse_position: Vector2) {
self.is_hovered =
mouse_position.distance_to(self.position) < self.radius || self.is_selected;
if self.is_hovered {
if self.radius < POINTS_RADIUS_HOVER {
self.radius += ANIMATION_SPEED;
} else if self.radius > POINTS_RADIUS_HOVER {
self.radius = POINTS_RADIUS_HOVER;
}
} else if self.radius > POINTS_RADIUS {
self.radius -= ANIMATION_SPEED;
} else if self.radius < POINTS_RADIUS {
self.radius = POINTS_RADIUS;
}

if self.is_selected {
self.position = mouse_position;
}
}

pub fn draw(&self, d: &mut RaylibDrawHandle) {
d.draw_circle_v(self.position, self.radius, self.color);
}
}

const SAMPLES: usize = 50;

/// Evaluate a point on the curve
fn evalute_bezier_curve(points: &[Point], t: f32) -> Vector2 {
let n = points.len() - 1;
let tuple_point = points.iter().enumerate().fold((0.0, 0.0), |acc, (i, e)| {
let a = (binomial(n as u64, i as u64) as f32)
* (1.0 - t).powi((n - i) as i32)
* t.powi(i as i32);
(acc.0 + e.position.x * a, acc.1 + e.position.y * a)
});
Vector2::new(tuple_point.0, tuple_point.1)
}

/// Draw the curve
fn draw_bezier(points: &[Point], d: &mut RaylibDrawHandle, t: Option<f32>) {
for line_points in points.windows(2) {
d.draw_line_ex(
line_points[0].position,
line_points[1].position,
3.0,
Color::RED,
);
}

let mut final_point = None;
if let Some(t) = t {
let rec_size = Vector2::new(POINTS_RADIUS, POINTS_RADIUS);
let mut debug_points: Vec<Vector2> = points.iter().map(|p| p.position).collect::<Vec<_>>();
while debug_points.len() > 2 {
let next_points = debug_points
.windows(2)
.map(|w| w[0].lerp(w[1], t))
.collect::<Vec<_>>();
// Drawing lines before points so that points will override them
for p in next_points.windows(2) {
d.draw_line_ex(p[0], p[1], 2.0, Color::RED);
}
// Draw lerp points for this run
for p in next_points.iter() {
d.draw_rectangle_v(*p - rec_size * 0.5, rec_size, Color::GREEN);
}
debug_points = next_points;
}
final_point = Some(debug_points[0].lerp(debug_points[1], t));
}

let step = 1.0 / SAMPLES as f32;
let step_points = (0..=SAMPLES)
.map(|i| evalute_bezier_curve(points, i as f32 * step))
.collect::<Vec<_>>();

for line_points in step_points.windows(2) {
d.draw_line_ex(line_points[0], line_points[1], 3.0, Color::GREEN);
}

if let Some(final_point) = final_point {
d.draw_circle_v(final_point, POINTS_RADIUS / 2.0, Color::YELLOW);
}

for point in points.iter() {
point.draw(d);
}
}

0 comments on commit bf8c8dc

Please sign in to comment.