mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
added astar pathfinder, started adding styles for ui
This commit is contained in:
Generated
+20
-2
@@ -5,7 +5,25 @@
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="1932b2c4-d6ff-413c-9558-19959667326b" name="Default Changelist" comment="scene graph">
|
||||
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/engine/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/engine/mod.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/engine/resource_manager.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/engine/resource_manager.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/border.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/border.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/button.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/button.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/check_box.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/check_box.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/grid.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/grid.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/image.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/image.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/mod.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/scroll_bar.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/scroll_bar.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/text.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/text.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/text_box.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/text_box.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/widget.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/widget.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/gui/window.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/gui/window.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/renderer/deferred_light_renderer.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/renderer/deferred_light_renderer.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/resource/ttf.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/resource/ttf.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/animation.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/scene/animation.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/scene/graph.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/scene/graph.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/utils/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/utils/mod.rs" afterDir="false" />
|
||||
</list>
|
||||
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
@@ -60,7 +78,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="RunManager" selected="Cargo Command.Clippy">
|
||||
<component name="RunManager" selected="Cargo Command.Release">
|
||||
<configuration name="Clippy" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="command" value="clippy" />
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rg3d"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
authors = ["Dmitry Stepanov <[email protected]>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
|
||||
+12
-10
@@ -13,10 +13,12 @@ use crate::{
|
||||
renderer::{Renderer, error::RendererError, gl},
|
||||
window::{WindowBuilder, Window},
|
||||
scene::SceneContainer,
|
||||
};
|
||||
use crate::{
|
||||
PossiblyCurrent, GlRequest, GlProfile,
|
||||
WindowedContext, NotCurrent, Api,
|
||||
PossiblyCurrent,
|
||||
GlRequest,
|
||||
GlProfile,
|
||||
WindowedContext,
|
||||
NotCurrent,
|
||||
Api,
|
||||
event_loop::EventLoop
|
||||
};
|
||||
|
||||
@@ -55,9 +57,9 @@ impl Engine {
|
||||
/// ```
|
||||
/// use rg3d::engine::Engine;
|
||||
/// use rg3d::window::WindowBuilder;
|
||||
/// use rg3d::event::EventsLoop;
|
||||
/// use rg3d::event_loop::EventLoop;
|
||||
///
|
||||
/// let evt = EventsLoop::new();
|
||||
/// let evt = EventLoop::new();
|
||||
/// let window_builder = WindowBuilder::new()
|
||||
/// .with_title("Test")
|
||||
/// .with_fullscreen(None);
|
||||
@@ -105,9 +107,9 @@ impl Engine {
|
||||
/// ```no_run
|
||||
/// use rg3d::engine::{Engine, EngineInterfaceMut};
|
||||
/// use rg3d::window::WindowBuilder;
|
||||
/// use rg3d::EventsLoop;
|
||||
/// use rg3d::event_loop::EventLoop;
|
||||
///
|
||||
/// let evt = EventsLoop::new();
|
||||
/// let evt = EventLoop::new();
|
||||
/// let mut engine = Engine::new(WindowBuilder::new(), &evt).unwrap();
|
||||
/// let EngineInterfaceMut{scenes, ui, renderer, sound_context, resource_manager} = engine.interface_mut();
|
||||
/// ```
|
||||
@@ -130,9 +132,9 @@ impl Engine {
|
||||
/// ```no_run
|
||||
/// use rg3d::engine::{EngineInterface, Engine};
|
||||
/// use rg3d::window::WindowBuilder;
|
||||
/// use rg3d::EventsLoop;
|
||||
/// use rg3d::event_loop::EventLoop;
|
||||
///
|
||||
/// let evt = EventsLoop::new();
|
||||
/// let evt = EventLoop::new();
|
||||
/// let engine = Engine::new(WindowBuilder::new(), &evt).unwrap();
|
||||
/// let EngineInterface{scenes, ui, renderer, sound_context, resource_manager} = engine.interface();
|
||||
/// ```
|
||||
|
||||
@@ -5,12 +5,12 @@ use std::{
|
||||
use crate::resource::{
|
||||
texture::Texture,
|
||||
model::Model,
|
||||
texture::TextureKind,
|
||||
};
|
||||
use rg3d_core::{
|
||||
visitor::{Visitor, VisitResult, Visit}
|
||||
};
|
||||
use rg3d_sound::buffer::{Buffer, BufferKind};
|
||||
use crate::resource::texture::TextureKind;
|
||||
|
||||
pub struct ResourceManager {
|
||||
textures: Vec<Arc<Mutex<Texture>>>,
|
||||
@@ -195,7 +195,7 @@ impl ResourceManager {
|
||||
|
||||
for old_model in self.get_models().to_vec() {
|
||||
let mut old_model = old_model.lock().unwrap();
|
||||
let new_model = match Model::load(old_model.path.as_path(),self) {
|
||||
let new_model = match Model::load(old_model.path.as_path(), self) {
|
||||
Ok(new_model) => new_model,
|
||||
Err(e) => {
|
||||
println!("Unable to reload {:?} model! Reason: {}", old_model.path, e);
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ impl BorderBuilder {
|
||||
impl Draw for Border {
|
||||
fn draw(&mut self, drawing_context: &mut DrawingContext) {
|
||||
let bounds= self.widget.get_screen_bounds();
|
||||
drawing_context.push_rect_filled(&bounds, None, self.widget.color);
|
||||
drawing_context.push_rect_filled(&bounds, None, self.widget.color());
|
||||
drawing_context.push_rect_vary(&bounds, self.stroke_thickness, self.stroke_color);
|
||||
drawing_context.commit(CommandKind::Geometry, CommandTexture::None);
|
||||
}
|
||||
|
||||
+30
-26
@@ -1,30 +1,34 @@
|
||||
use crate::gui::{
|
||||
node::UINode,
|
||||
widget::{
|
||||
Widget,
|
||||
WidgetBuilder,
|
||||
AsWidget
|
||||
use crate::{
|
||||
gui::{
|
||||
node::UINode,
|
||||
widget::{
|
||||
Widget,
|
||||
WidgetBuilder,
|
||||
AsWidget,
|
||||
},
|
||||
UserInterface,
|
||||
HorizontalAlignment,
|
||||
VerticalAlignment,
|
||||
Thickness,
|
||||
text::TextBuilder,
|
||||
border::BorderBuilder,
|
||||
event::{UIEvent, UIEventKind},
|
||||
Layout,
|
||||
Draw,
|
||||
draw::DrawingContext,
|
||||
Update,
|
||||
},
|
||||
UserInterface,
|
||||
HorizontalAlignment,
|
||||
VerticalAlignment,
|
||||
Thickness,
|
||||
text::TextBuilder,
|
||||
border::BorderBuilder,
|
||||
event::{UIEvent, UIEventKind},
|
||||
Layout,
|
||||
Draw,
|
||||
draw::DrawingContext,
|
||||
Update,
|
||||
resource::ttf::Font,
|
||||
};
|
||||
use rg3d_core::{
|
||||
color::Color,
|
||||
pool::Handle,
|
||||
math::vec2::Vec2,
|
||||
};
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use crate::resource::ttf::Font;
|
||||
use std::{
|
||||
rc::Rc,
|
||||
cell::RefCell,
|
||||
};
|
||||
|
||||
/// Button
|
||||
///
|
||||
@@ -75,7 +79,7 @@ pub enum ButtonContent {
|
||||
pub struct ButtonBuilder {
|
||||
widget_builder: WidgetBuilder,
|
||||
content: Option<ButtonContent>,
|
||||
font: Option<Rc<RefCell<Font>>>
|
||||
font: Option<Rc<RefCell<Font>>>,
|
||||
}
|
||||
|
||||
impl ButtonBuilder {
|
||||
@@ -130,16 +134,16 @@ impl ButtonBuilder {
|
||||
if evt.source == handle || ui.is_node_child_of(evt.source, handle) {
|
||||
let back = ui.nodes.borrow_mut(handle).widget_mut();
|
||||
match evt.kind {
|
||||
UIEventKind::MouseDown { .. } => back.color = pressed_color,
|
||||
UIEventKind::MouseDown { .. } => back.set_color(pressed_color),
|
||||
UIEventKind::MouseUp { .. } => {
|
||||
if back.is_mouse_over {
|
||||
back.color = hover_color;
|
||||
back.set_color(hover_color);
|
||||
} else {
|
||||
back.color = normal_color;
|
||||
back.set_color(normal_color);
|
||||
}
|
||||
}
|
||||
UIEventKind::MouseLeave => back.color = normal_color,
|
||||
UIEventKind::MouseEnter => back.color = hover_color,
|
||||
UIEventKind::MouseLeave => back.set_color(normal_color),
|
||||
UIEventKind::MouseEnter => back.set_color(hover_color),
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
+27
-3
@@ -1,10 +1,24 @@
|
||||
use crate::gui::{widget::{Widget, AsWidget, WidgetBuilder}, Draw, Layout, UserInterface, Update, draw::DrawingContext, node::UINode, event::{UIEvent, UIEventKind}, Thickness, Visibility, bool_to_visibility};
|
||||
use crate::gui::{
|
||||
widget::{Widget, AsWidget, WidgetBuilder},
|
||||
Draw,
|
||||
Layout,
|
||||
UserInterface,
|
||||
Update,
|
||||
draw::DrawingContext,
|
||||
node::UINode,
|
||||
event::{UIEvent, UIEventKind},
|
||||
Thickness,
|
||||
Visibility,
|
||||
bool_to_visibility,
|
||||
Styleable,
|
||||
border::BorderBuilder,
|
||||
};
|
||||
use rg3d_core::{
|
||||
math::vec2::Vec2,
|
||||
pool::Handle,
|
||||
color::Color
|
||||
};
|
||||
use crate::gui::border::BorderBuilder;
|
||||
use rg3d_core::color::Color;
|
||||
use std::any::Any;
|
||||
|
||||
pub struct CheckBox {
|
||||
widget: Widget,
|
||||
@@ -139,4 +153,14 @@ impl CheckBoxBuilder {
|
||||
|
||||
ui.add_node(check_box)
|
||||
}
|
||||
}
|
||||
|
||||
impl Styleable for CheckBox {
|
||||
fn set_property(&mut self, name: &str, value: &dyn Any) {
|
||||
|
||||
}
|
||||
|
||||
fn get_property(&self, name: &str) -> Option<&dyn Any> {
|
||||
None
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -171,8 +171,8 @@ impl Layout for Grid {
|
||||
let size_for_child = {
|
||||
let child = ui.nodes.borrow(*child_handle).widget();
|
||||
Vec2 {
|
||||
x: self.columns.borrow()[child.column].actual_width,
|
||||
y: self.rows.borrow()[child.row].actual_height,
|
||||
x: self.columns.borrow()[child.column()].actual_width,
|
||||
y: self.rows.borrow()[child.row()].actual_height,
|
||||
}
|
||||
};
|
||||
ui.measure(*child_handle, size_for_child);
|
||||
@@ -202,8 +202,8 @@ impl Layout for Grid {
|
||||
let mut final_rect = None;
|
||||
|
||||
let child = ui.nodes.borrow(*child_handle).widget();
|
||||
if let Some(column) = self.columns.borrow().get(child.column) {
|
||||
if let Some(row) = self.rows.borrow().get(child.row) {
|
||||
if let Some(column) = self.columns.borrow().get(child.column()) {
|
||||
if let Some(row) = self.rows.borrow().get(child.row()) {
|
||||
final_rect = Some(Rect::new(
|
||||
column.x,
|
||||
row.y,
|
||||
@@ -290,7 +290,7 @@ impl Grid {
|
||||
col.actual_width = col.desired_width;
|
||||
for child_handle in self.widget.children.iter() {
|
||||
let child = ui.nodes.borrow(*child_handle).widget();
|
||||
if child.column == i && child.visibility == Visibility::Visible && child.desired_size.get().x > col.actual_width {
|
||||
if child.column() == i && child.visibility == Visibility::Visible && child.desired_size.get().x > col.actual_width {
|
||||
col.actual_width = child.desired_size.get().x;
|
||||
}
|
||||
}
|
||||
@@ -313,7 +313,7 @@ impl Grid {
|
||||
row.actual_height = row.desired_height;
|
||||
for child_handle in self.widget.children.iter() {
|
||||
let child = ui.nodes.borrow(*child_handle).widget();
|
||||
if child.row == i && child.visibility == Visibility::Visible && child.desired_size.get().y > row.actual_height {
|
||||
if child.row() == i && child.visibility == Visibility::Visible && child.desired_size.get().y > row.actual_height {
|
||||
row.actual_height = child.desired_size.get().y;
|
||||
}
|
||||
}
|
||||
@@ -329,7 +329,7 @@ impl Grid {
|
||||
if available_size.x.is_infinite() {
|
||||
for child_handle in self.widget.children.iter() {
|
||||
let child = ui.nodes.borrow(*child_handle).widget();
|
||||
if let Some(column) = self.columns.borrow().get(child.column) {
|
||||
if let Some(column) = self.columns.borrow().get(child.column()) {
|
||||
if column.size_mode == SizeMode::Stretch {
|
||||
rest_width += child.desired_size.get().x;
|
||||
}
|
||||
@@ -362,7 +362,7 @@ impl Grid {
|
||||
if available_size.y.is_infinite() {
|
||||
for child_handle in self.widget.children.iter() {
|
||||
let child = ui.nodes.borrow(*child_handle).widget();
|
||||
if let Some(row) = self.rows.borrow().get(child.row) {
|
||||
if let Some(row) = self.rows.borrow().get(child.row()) {
|
||||
if row.size_mode == SizeMode::Stretch {
|
||||
rest_height += child.desired_size.get().y;
|
||||
}
|
||||
|
||||
+8
-6
@@ -1,19 +1,21 @@
|
||||
use std::sync::{Mutex, Arc};
|
||||
use rg3d_core::{
|
||||
math::vec2::Vec2,
|
||||
pool::Handle
|
||||
};
|
||||
use crate::{
|
||||
resource::texture::Texture,
|
||||
gui::{
|
||||
node::UINode,
|
||||
Draw,
|
||||
draw::{DrawingContext, CommandKind, CommandTexture},
|
||||
widget::{Widget, AsWidget},
|
||||
Layout,
|
||||
UserInterface,
|
||||
Update,
|
||||
widget::WidgetBuilder
|
||||
},
|
||||
resource::texture::Texture,
|
||||
};
|
||||
use rg3d_core::math::vec2::Vec2;
|
||||
use crate::gui::node::UINode;
|
||||
use rg3d_core::pool::Handle;
|
||||
use crate::gui::widget::WidgetBuilder;
|
||||
|
||||
pub struct Image {
|
||||
widget: Widget,
|
||||
@@ -33,7 +35,7 @@ impl AsWidget for Image {
|
||||
impl Draw for Image {
|
||||
fn draw(&mut self, drawing_context: &mut DrawingContext) {
|
||||
let bounds = self.widget.get_screen_bounds();
|
||||
drawing_context.push_rect_filled(&bounds, None, self.widget.color);
|
||||
drawing_context.push_rect_filled(&bounds, None, self.widget.color());
|
||||
let texture = self.texture.as_ref().map_or(CommandTexture::None,
|
||||
|t| { CommandTexture::Texture(t.clone()) });
|
||||
drawing_context.commit(CommandKind::Geometry, texture);
|
||||
|
||||
+27
-5
@@ -17,11 +17,13 @@ pub mod list_box;
|
||||
pub mod stack_panel;
|
||||
pub mod text_box;
|
||||
pub mod check_box;
|
||||
pub mod style;
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
rc::Rc,
|
||||
cell::RefCell,
|
||||
any::Any
|
||||
};
|
||||
use crate::{
|
||||
gui::{
|
||||
@@ -34,6 +36,7 @@ use crate::{
|
||||
UIEventKind,
|
||||
UIEventHandler,
|
||||
},
|
||||
style::Style
|
||||
},
|
||||
resource::{ttf::Font},
|
||||
utils::UnsafeCollectionView,
|
||||
@@ -104,19 +107,39 @@ pub enum Visibility {
|
||||
Hidden,
|
||||
}
|
||||
|
||||
trait Layout {
|
||||
pub trait Layout {
|
||||
fn measure_override(&self, ui: &UserInterface, available_size: Vec2) -> Vec2;
|
||||
fn arrange_override(&self, ui: &UserInterface, final_size: Vec2) -> Vec2;
|
||||
}
|
||||
|
||||
trait Draw {
|
||||
pub trait Draw {
|
||||
fn draw(&mut self, drawing_context: &mut DrawingContext);
|
||||
}
|
||||
|
||||
trait Update {
|
||||
pub trait Update {
|
||||
fn update(&mut self, dt: f32);
|
||||
}
|
||||
|
||||
pub trait Styleable: AsWidget {
|
||||
fn set_property(&mut self, name: &str, value: &dyn Any);
|
||||
fn get_property(&self, name: &str) -> Option<&'_ dyn Any>;
|
||||
|
||||
fn apply_style(&mut self, style: Rc<Style>) {
|
||||
// Apply base style first.
|
||||
if let Some(base_style) = style.base_style() {
|
||||
self.apply_style(base_style);
|
||||
}
|
||||
|
||||
// Remember last applied style.
|
||||
self.widget_mut().set_style(style.clone());
|
||||
|
||||
// Then apply current.
|
||||
for setter in style.setters() {
|
||||
self.set_property(setter.name(), setter.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UserInterface {
|
||||
nodes: Pool<UINode>,
|
||||
drawing_context: DrawingContext,
|
||||
@@ -691,7 +714,7 @@ impl UserInterface {
|
||||
/// to some specific node. Lets take a window for example, we can close or open
|
||||
/// it by just sending an appropriate event - something like this:
|
||||
///
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// use rg3d::gui::event::{UIEvent, UIEventKind};
|
||||
///
|
||||
/// ui.send_event(UIEvent::targeted(options_window, UIEventKind::Opened));
|
||||
@@ -700,7 +723,6 @@ impl UserInterface {
|
||||
self.events.push_back(event);
|
||||
}
|
||||
|
||||
|
||||
/// Extracts UI event one-by-one from common queue. Each extracted event will go to *all*
|
||||
/// available nodes first and only then will be moved outside of this method. This is one
|
||||
/// of most important methods which must be called each frame of your game loop, otherwise
|
||||
|
||||
+55
-54
@@ -13,7 +13,7 @@ use crate::gui::{
|
||||
widget::{Widget, WidgetBuilder, AsWidget},
|
||||
Draw,
|
||||
draw::DrawingContext,
|
||||
Update
|
||||
Update,
|
||||
};
|
||||
use rg3d_core::{
|
||||
color::Color, math,
|
||||
@@ -262,61 +262,62 @@ impl ScrollBarBuilder {
|
||||
.with_width(30.0)
|
||||
.with_height(30.0)
|
||||
.with_event_handler(Box::new(move |ui, handle, evt| {
|
||||
if evt.source == handle {
|
||||
match evt.kind {
|
||||
UIEventKind::MouseDown { pos, .. } => {
|
||||
let indicator_pos = ui.nodes.borrow(handle).widget().screen_position;
|
||||
let scroll_bar_node = ui.borrow_by_criteria_up_mut(handle, |node| node.is_scroll_bar());
|
||||
let scroll_bar = scroll_bar_node.as_scroll_bar_mut();
|
||||
scroll_bar.is_dragging = true;
|
||||
scroll_bar.offset = indicator_pos - pos;
|
||||
if evt.source != handle {
|
||||
return;
|
||||
}
|
||||
match evt.kind {
|
||||
UIEventKind::MouseDown { pos, .. } => {
|
||||
let indicator_pos = ui.nodes.borrow(handle).widget().screen_position;
|
||||
let scroll_bar_node = ui.borrow_by_criteria_up_mut(handle, |node| node.is_scroll_bar());
|
||||
let scroll_bar = scroll_bar_node.as_scroll_bar_mut();
|
||||
scroll_bar.is_dragging = true;
|
||||
scroll_bar.offset = indicator_pos - pos;
|
||||
|
||||
ui.capture_mouse(handle);
|
||||
evt.handled = true;
|
||||
}
|
||||
UIEventKind::MouseUp { .. } => {
|
||||
let scroll_bar_node = ui.borrow_by_criteria_up_mut(handle, |node| node.is_scroll_bar());
|
||||
scroll_bar_node.as_scroll_bar_mut().is_dragging = false;
|
||||
ui.release_mouse_capture();
|
||||
evt.handled = true;
|
||||
}
|
||||
UIEventKind::MouseMove { pos, .. } => {
|
||||
let (field_pos, field_size) = {
|
||||
let canvas = ui.borrow_by_name_up(handle, ScrollBar::PART_CANVAS).widget();
|
||||
(canvas.screen_position, canvas.actual_size.get())
|
||||
};
|
||||
|
||||
let bar_size = ui.nodes.borrow(handle).widget().actual_size.get();
|
||||
let scroll_bar_node = ui.borrow_by_criteria_up_mut(handle, |node| node.is_scroll_bar());
|
||||
let scroll_bar = scroll_bar_node.as_scroll_bar_mut();
|
||||
let orientation = scroll_bar.orientation;
|
||||
if scroll_bar.is_dragging {
|
||||
let percent = match orientation {
|
||||
Orientation::Horizontal => {
|
||||
let span = field_size.x - bar_size.x;
|
||||
let offset = pos.x - field_pos.x + scroll_bar.offset.x;
|
||||
if span > 0.0 {
|
||||
math::clampf(offset / span, 0.0, 1.0)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
Orientation::Vertical => {
|
||||
let span = field_size.y - bar_size.y;
|
||||
let offset = pos.y - field_pos.y + scroll_bar.offset.y;
|
||||
if span > 0.0 {
|
||||
math::clampf(offset / span, 0.0, 1.0)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
};
|
||||
scroll_bar.set_value(percent * (scroll_bar.max - scroll_bar.min));
|
||||
evt.handled = true;
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
ui.capture_mouse(handle);
|
||||
evt.handled = true;
|
||||
}
|
||||
UIEventKind::MouseUp { .. } => {
|
||||
let scroll_bar_node = ui.borrow_by_criteria_up_mut(handle, |node| node.is_scroll_bar());
|
||||
scroll_bar_node.as_scroll_bar_mut().is_dragging = false;
|
||||
ui.release_mouse_capture();
|
||||
evt.handled = true;
|
||||
}
|
||||
UIEventKind::MouseMove { pos, .. } => {
|
||||
let (field_pos, field_size) = {
|
||||
let canvas = ui.borrow_by_name_up(handle, ScrollBar::PART_CANVAS).widget();
|
||||
(canvas.screen_position, canvas.actual_size.get())
|
||||
};
|
||||
|
||||
let bar_size = ui.nodes.borrow(handle).widget().actual_size.get();
|
||||
let scroll_bar_node = ui.borrow_by_criteria_up_mut(handle, |node| node.is_scroll_bar());
|
||||
let scroll_bar = scroll_bar_node.as_scroll_bar_mut();
|
||||
let orientation = scroll_bar.orientation;
|
||||
if scroll_bar.is_dragging {
|
||||
let percent = match orientation {
|
||||
Orientation::Horizontal => {
|
||||
let span = field_size.x - bar_size.x;
|
||||
let offset = pos.x - field_pos.x + scroll_bar.offset.x;
|
||||
if span > 0.0 {
|
||||
math::clampf(offset / span, 0.0, 1.0)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
Orientation::Vertical => {
|
||||
let span = field_size.y - bar_size.y;
|
||||
let offset = pos.y - field_pos.y + scroll_bar.offset.y;
|
||||
if span > 0.0 {
|
||||
math::clampf(offset / span, 0.0, 1.0)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
};
|
||||
scroll_bar.set_value(percent * (scroll_bar.max - scroll_bar.min));
|
||||
evt.handled = true;
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
})))
|
||||
.with_stroke_color(Color::opaque(50, 50, 50))
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
rc::Rc
|
||||
};
|
||||
|
||||
pub struct PropertySetter {
|
||||
name: String,
|
||||
value: Box<dyn Any>,
|
||||
}
|
||||
|
||||
impl PropertySetter {
|
||||
pub fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &dyn Any {
|
||||
self.value.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Style {
|
||||
base_style: Option<Rc<Style>>,
|
||||
setters: Vec<PropertySetter>,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn setters(&self) -> &[PropertySetter] {
|
||||
self.setters.as_slice()
|
||||
}
|
||||
|
||||
pub fn base_style(&self) -> Option<Rc<Style>> {
|
||||
self.base_style.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StyleBuilder {
|
||||
base_style: Option<Rc<Style>>,
|
||||
setters: Vec<PropertySetter>,
|
||||
}
|
||||
|
||||
impl Default for StyleBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base_style: None,
|
||||
setters: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_base_style(mut self, style: Rc<Style>) -> Self {
|
||||
self.base_style = Some(style);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_setter(mut self, name: &str, value: Box<dyn Any>) -> Self {
|
||||
self.setters.push(PropertySetter { name: name.to_owned(), value });
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Style {
|
||||
Style {
|
||||
base_style: self.base_style,
|
||||
setters: self.setters
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -62,7 +62,7 @@ impl Draw for Text {
|
||||
if self.need_update {
|
||||
self.formatted_text.set_size(Vec2::new(bounds.w, bounds.h));
|
||||
self.formatted_text.set_text(self.text.as_str());
|
||||
self.formatted_text.set_color(self.widget.color);
|
||||
self.formatted_text.set_color(self.widget.color());
|
||||
self.formatted_text.set_horizontal_alignment(self.horizontal_alignment);
|
||||
self.formatted_text.set_vertical_alignment(self.vertical_alignment);
|
||||
self.formatted_text.build();
|
||||
|
||||
+1
-1
@@ -247,7 +247,7 @@ impl Draw for TextBox {
|
||||
drawing_context.commit(CommandKind::Geometry, CommandTexture::None);
|
||||
|
||||
self.formatted_text.set_size(Vec2::new(bounds.w, bounds.h));
|
||||
self.formatted_text.set_color(self.widget.color);
|
||||
self.formatted_text.set_color(self.widget.color());
|
||||
self.formatted_text.build();
|
||||
|
||||
if let Some(ref selection_range) = self.selection_range {
|
||||
|
||||
+127
-16
@@ -1,12 +1,10 @@
|
||||
use std::{
|
||||
cell::Cell,
|
||||
collections::VecDeque,
|
||||
};
|
||||
use rg3d_core::{
|
||||
color::Color,
|
||||
math::vec2::Vec2,
|
||||
math::{
|
||||
vec2::Vec2,
|
||||
Rect,
|
||||
},
|
||||
pool::Handle,
|
||||
math::Rect,
|
||||
};
|
||||
use crate::gui::{
|
||||
VerticalAlignment,
|
||||
@@ -19,9 +17,19 @@ use crate::gui::{
|
||||
node::UINode,
|
||||
event::{UIEventHandler, UIEvent},
|
||||
draw::DrawingContext,
|
||||
Update
|
||||
Update,
|
||||
Styleable,
|
||||
style::Style,
|
||||
};
|
||||
use std::{
|
||||
cell::{
|
||||
RefCell,
|
||||
Cell,
|
||||
},
|
||||
collections::VecDeque,
|
||||
any::Any,
|
||||
rc::Rc
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
pub trait AsWidget {
|
||||
fn widget(&self) -> &Widget;
|
||||
@@ -49,11 +57,11 @@ pub struct Widget {
|
||||
/// Maximum width and height
|
||||
pub(in crate::gui) max_size: Vec2,
|
||||
/// Overlay color of the node
|
||||
pub(in crate::gui) color: Color,
|
||||
color: Color,
|
||||
/// Index of row to which this node belongs
|
||||
pub(in crate::gui) row: usize,
|
||||
row: usize,
|
||||
/// Index of column to which this node belongs
|
||||
pub(in crate::gui) column: usize,
|
||||
column: usize,
|
||||
/// Vertical alignment
|
||||
pub(in crate::gui) vertical_alignment: VerticalAlignment,
|
||||
/// Horizontal alignment
|
||||
@@ -73,6 +81,7 @@ pub struct Widget {
|
||||
pub(in crate::gui) event_handlers: Vec<Box<UIEventHandler>>,
|
||||
pub(in crate::gui) events: RefCell<VecDeque<UIEvent>>,
|
||||
pub(in crate::gui) is_hit_test_visible: bool,
|
||||
pub(in crate::gui) style: Option<Rc<Style>>,
|
||||
}
|
||||
|
||||
impl Default for Widget {
|
||||
@@ -82,9 +91,7 @@ impl Default for Widget {
|
||||
}
|
||||
|
||||
impl Update for Widget {
|
||||
fn update(&mut self, _dt: f32) {
|
||||
|
||||
}
|
||||
fn update(&mut self, _dt: f32) {}
|
||||
}
|
||||
|
||||
impl Layout for Widget {
|
||||
@@ -135,11 +142,28 @@ impl Draw for Widget {
|
||||
}
|
||||
|
||||
impl Widget {
|
||||
pub const WIDTH: &'static str = "Width";
|
||||
pub const HEIGHT: &'static str = "Height";
|
||||
pub const VERTICAL_ALIGNMENT: &'static str = "VerticalAlignment";
|
||||
pub const HORIZONTAL_ALIGNMENT: &'static str = "HorizontalAlignment";
|
||||
pub const MARGIN: &'static str = "Margin";
|
||||
pub const ROW: &'static str = "Row";
|
||||
pub const COLUMN: &'static str = "Column";
|
||||
pub const COLOR: &'static str = "Color";
|
||||
pub const VISIBILITY: &'static str = "Visibility";
|
||||
pub const MIN_SIZE: &'static str = "MinSize";
|
||||
pub const MAX_SIZE: &'static str = "MaxSize";
|
||||
|
||||
#[inline]
|
||||
pub fn set_color(&mut self, color: Color) {
|
||||
self.color = color;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn color(&self) -> Color {
|
||||
self.color
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_width(&mut self, width: f32) {
|
||||
self.width.set(width);
|
||||
@@ -165,6 +189,16 @@ impl Widget {
|
||||
self.horizontal_alignment = halign;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn column(&self) -> usize {
|
||||
self.column
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn row(&self) -> usize {
|
||||
self.row
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_screen_bounds(&self) -> Rect<f32> {
|
||||
Rect::new(self.screen_position.x, self.screen_position.y, self.actual_size.get().x, self.actual_size.get().y)
|
||||
@@ -179,6 +213,29 @@ impl Widget {
|
||||
pub fn get_visibility(&self) -> Visibility {
|
||||
self.visibility
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_style(&mut self, style: Rc<Style>) {
|
||||
self.style = Some(style);
|
||||
}
|
||||
|
||||
fn raw_copy(&self) -> Self {
|
||||
Self {
|
||||
name: self.name.clone(),
|
||||
width: self.width.clone(),
|
||||
height: self.height.clone(),
|
||||
min_size: self.min_size,
|
||||
max_size: self.max_size,
|
||||
color: self.color,
|
||||
row: self.row,
|
||||
column: self.column,
|
||||
vertical_alignment: self.vertical_alignment,
|
||||
horizontal_alignment: self.horizontal_alignment,
|
||||
margin: self.margin,
|
||||
visibility: self.visibility,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WidgetBuilder {
|
||||
@@ -198,6 +255,7 @@ pub struct WidgetBuilder {
|
||||
event_handlers: Vec<Box<UIEventHandler>>,
|
||||
is_hit_test_visible: bool,
|
||||
visibility: Visibility,
|
||||
style: Option<Rc<Style>>,
|
||||
}
|
||||
|
||||
impl Default for WidgetBuilder {
|
||||
@@ -224,7 +282,8 @@ impl WidgetBuilder {
|
||||
children: Vec::new(),
|
||||
event_handlers: Vec::new(),
|
||||
is_hit_test_visible: true,
|
||||
visibility: Visibility::Visible
|
||||
visibility: Visibility::Visible,
|
||||
style: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +342,11 @@ impl WidgetBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_style(mut self, style: Rc<Style>) -> Self {
|
||||
self.style = Some(style);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_child(mut self, handle: Handle<UINode>) -> Self {
|
||||
if handle.is_some() {
|
||||
self.children.push(handle);
|
||||
@@ -318,7 +382,7 @@ impl WidgetBuilder {
|
||||
}
|
||||
|
||||
pub fn build(self) -> Widget {
|
||||
Widget {
|
||||
let mut widget = Widget {
|
||||
name: self.name.unwrap_or_default(),
|
||||
desired_local_position: Cell::new(self.desired_position.unwrap_or(Vec2::ZERO)),
|
||||
width: Cell::new(self.width.unwrap_or(std::f32::NAN)),
|
||||
@@ -346,6 +410,53 @@ impl WidgetBuilder {
|
||||
event_handlers: self.event_handlers,
|
||||
events: RefCell::new(VecDeque::new()),
|
||||
is_hit_test_visible: self.is_hit_test_visible,
|
||||
style: None,
|
||||
};
|
||||
|
||||
if let Some(style) = self.style {
|
||||
widget.apply_style(style);
|
||||
}
|
||||
|
||||
widget
|
||||
}
|
||||
}
|
||||
|
||||
impl Styleable for Widget {
|
||||
fn set_property(&mut self, name: &str, value: &dyn Any) {
|
||||
match name {
|
||||
Self::HORIZONTAL_ALIGNMENT => if let Some(value) = value.downcast_ref() { self.horizontal_alignment = *value },
|
||||
Self::VERTICAL_ALIGNMENT => if let Some(value) = value.downcast_ref() { self.vertical_alignment = *value },
|
||||
Self::WIDTH => {
|
||||
if let Some(value) = value.downcast_ref() { self.width.set(*value) } else if let Some(value) = value.downcast_ref::<f64>() { self.width.set(*value as f32) }
|
||||
}
|
||||
Self::HEIGHT => {
|
||||
if let Some(value) = value.downcast_ref() { self.height.set(*value) } else if let Some(value) = value.downcast_ref::<f64>() { self.height.set(*value as f32) }
|
||||
}
|
||||
Self::MARGIN => if let Some(value) = value.downcast_ref() { self.margin = *value },
|
||||
Self::ROW => if let Some(value) = value.downcast_ref() { self.row = *value },
|
||||
Self::COLUMN => if let Some(value) = value.downcast_ref() { self.column = *value },
|
||||
Self::COLOR => if let Some(value) = value.downcast_ref() { self.color = *value },
|
||||
Self::VISIBILITY => if let Some(value) = value.downcast_ref() { self.visibility = *value },
|
||||
Self::MIN_SIZE => if let Some(value) = value.downcast_ref() { self.min_size = *value },
|
||||
Self::MAX_SIZE => if let Some(value) = value.downcast_ref() { self.max_size = *value },
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_property(&self, name: &str) -> Option<&'_ dyn Any> {
|
||||
match name {
|
||||
Self::HORIZONTAL_ALIGNMENT => Some(&self.horizontal_alignment),
|
||||
Self::VERTICAL_ALIGNMENT => Some(&self.vertical_alignment),
|
||||
Self::WIDTH => Some(&self.width),
|
||||
Self::HEIGHT => Some(&self.height),
|
||||
Self::MARGIN => Some(&self.margin),
|
||||
Self::ROW => Some(&self.row),
|
||||
Self::COLUMN => Some(&self.column),
|
||||
Self::VISIBILITY => Some(&self.visibility),
|
||||
Self::COLOR => Some(&self.color),
|
||||
Self::MIN_SIZE => Some(&self.min_size),
|
||||
Self::MAX_SIZE => Some(&self.max_size),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-6
@@ -5,12 +5,21 @@ use rg3d_core::{
|
||||
};
|
||||
use crate::gui::{
|
||||
event::UIEventKind,
|
||||
border::BorderBuilder, node::UINode,
|
||||
UserInterface, grid::{GridBuilder, Column, Row},
|
||||
HorizontalAlignment, text::TextBuilder, Thickness,
|
||||
button::ButtonBuilder, scroll_viewer::ScrollViewerBuilder,
|
||||
Layout, widget::{Widget, WidgetBuilder, AsWidget}, Draw,
|
||||
draw::DrawingContext, Update, event::UIEvent, Visibility,
|
||||
border::BorderBuilder,
|
||||
node::UINode,
|
||||
UserInterface,
|
||||
grid::{GridBuilder, Column, Row},
|
||||
HorizontalAlignment,
|
||||
text::TextBuilder,
|
||||
Thickness,
|
||||
button::ButtonBuilder,
|
||||
scroll_viewer::ScrollViewerBuilder,
|
||||
Layout,
|
||||
widget::{Widget, WidgetBuilder, AsWidget},
|
||||
Draw,
|
||||
draw::DrawingContext,
|
||||
Update, event::UIEvent,
|
||||
Visibility,
|
||||
bool_to_visibility,
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ use crate::{
|
||||
gpu_texture::GpuTexture,
|
||||
shadow_map_renderer::PointShadowMapRenderer,
|
||||
QualitySettings,
|
||||
},
|
||||
RenderPassStatistics
|
||||
}
|
||||
};
|
||||
use rg3d_core::{
|
||||
math::{
|
||||
@@ -30,7 +31,6 @@ use rg3d_core::{
|
||||
},
|
||||
color::Color,
|
||||
};
|
||||
use crate::renderer::RenderPassStatistics;
|
||||
|
||||
struct AmbientLightShader {
|
||||
program: GpuProgram,
|
||||
@@ -224,8 +224,12 @@ impl DeferredLightRenderer {
|
||||
}
|
||||
|
||||
pub fn set_quality_settings(&mut self, settings: &QualitySettings) -> Result<(), RendererError> {
|
||||
self.spot_shadow_map_renderer = SpotShadowMapRenderer::new(settings.spot_shadow_map_size)?;
|
||||
self.point_shadow_map_renderer = PointShadowMapRenderer::new(settings.point_shadow_map_size)?;
|
||||
if settings.spot_shadow_map_size != self.spot_shadow_map_renderer.size {
|
||||
self.spot_shadow_map_renderer = SpotShadowMapRenderer::new(settings.spot_shadow_map_size)?;
|
||||
}
|
||||
if settings.point_shadow_map_size != self.point_shadow_map_renderer.size {
|
||||
self.point_shadow_map_renderer = PointShadowMapRenderer::new(settings.point_shadow_map_size)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1054,7 +1054,7 @@ mod test {
|
||||
#[test]
|
||||
fn font_test() {
|
||||
let font_bytes = std::include_bytes!("../built_in_font.ttf").to_vec();
|
||||
let font = Font::from_memory(font_bytes, 20.0, (0..255).collect()).unwrap();
|
||||
let font = Font::from_memory(font_bytes, 20.0, Font::default_char_set()).unwrap();
|
||||
let font = Rc::new(RefCell::new(font));
|
||||
let raster_path = Path::new("./raster");
|
||||
if !raster_path.exists() {
|
||||
|
||||
@@ -16,7 +16,9 @@ use rg3d_core::{
|
||||
Handle,
|
||||
PoolIterator,
|
||||
PoolIteratorMut,
|
||||
},
|
||||
PoolPairIterator,
|
||||
PoolPairIteratorMut
|
||||
}
|
||||
};
|
||||
use std::sync::{Mutex, Arc};
|
||||
use crate::{
|
||||
@@ -28,7 +30,6 @@ use crate::{
|
||||
},
|
||||
resource::model::Model,
|
||||
};
|
||||
use rg3d_core::pool::{PoolPairIterator, PoolPairIteratorMut};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct KeyFrame {
|
||||
|
||||
+14
-11
@@ -397,7 +397,7 @@ impl Graph {
|
||||
pub fn traverse_iter(&self, from: Handle<Node>) -> GraphTraverseIterator {
|
||||
GraphTraverseIterator {
|
||||
graph: self,
|
||||
stack: vec![from]
|
||||
stack: vec![from],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,14 +410,14 @@ impl Graph {
|
||||
pub fn traverse_handle_iter(&self, from: Handle<Node>) -> GraphHandleTraverseIterator {
|
||||
GraphHandleTraverseIterator {
|
||||
graph: self,
|
||||
stack: vec![from]
|
||||
stack: vec![from],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GraphTraverseIterator<'a> {
|
||||
graph: &'a Graph,
|
||||
stack: Vec<Handle<Node>>
|
||||
stack: Vec<Handle<Node>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for GraphTraverseIterator<'a> {
|
||||
@@ -431,7 +431,7 @@ impl<'a> Iterator for GraphTraverseIterator<'a> {
|
||||
self.stack.push(*child_handle);
|
||||
}
|
||||
|
||||
return Some(node)
|
||||
return Some(node);
|
||||
}
|
||||
|
||||
None
|
||||
@@ -440,7 +440,7 @@ impl<'a> Iterator for GraphTraverseIterator<'a> {
|
||||
|
||||
pub struct GraphHandleTraverseIterator<'a> {
|
||||
graph: &'a Graph,
|
||||
stack: Vec<Handle<Node>>
|
||||
stack: Vec<Handle<Node>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for GraphHandleTraverseIterator<'a> {
|
||||
@@ -452,7 +452,7 @@ impl<'a> Iterator for GraphHandleTraverseIterator<'a> {
|
||||
self.stack.push(*child_handle);
|
||||
}
|
||||
|
||||
return Some(handle)
|
||||
return Some(handle);
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -476,9 +476,12 @@ impl Visit for Graph {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::scene::graph::Graph;
|
||||
use crate::scene::{
|
||||
graph::Graph,
|
||||
node::Node,
|
||||
base::Base,
|
||||
};
|
||||
use rg3d_core::pool::Handle;
|
||||
use crate::scene::node::{Node, NodeKind};
|
||||
|
||||
#[test]
|
||||
fn graph_init_test() {
|
||||
@@ -490,9 +493,9 @@ mod test {
|
||||
#[test]
|
||||
fn graph_node_test() {
|
||||
let mut graph = Graph::new();
|
||||
let a = graph.add_node(Node::new(NodeKind::Base));
|
||||
let b = graph.add_node(Node::new(NodeKind::Base));
|
||||
let c = graph.add_node(Node::new(NodeKind::Base));
|
||||
let a = graph.add_node(Node::Base(Base::default()));
|
||||
let b = graph.add_node(Node::Base(Base::default()));
|
||||
let c = graph.add_node(Node::Base(Base::default()));
|
||||
assert_eq!(graph.pool.alive_count(), 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
use rg3d_core::math::vec3::Vec3;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum PathVertexState {
|
||||
NonVisited,
|
||||
Open,
|
||||
Closed,
|
||||
}
|
||||
|
||||
pub struct PathVertex {
|
||||
/// Position in world.
|
||||
position: Vec3,
|
||||
state: PathVertexState,
|
||||
g_score: f32,
|
||||
f_score: f32,
|
||||
parent: Option<usize>,
|
||||
neighbours: Vec<usize>,
|
||||
}
|
||||
|
||||
impl PathVertex {
|
||||
pub fn new(position: Vec3) -> Self {
|
||||
Self {
|
||||
position,
|
||||
parent: None,
|
||||
g_score: std::f32::MAX,
|
||||
f_score: std::f32::MAX,
|
||||
state: PathVertexState::NonVisited,
|
||||
neighbours: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.g_score = std::f32::MAX;
|
||||
self.f_score = std::f32::MAX;
|
||||
self.state = PathVertexState::NonVisited;
|
||||
self.parent = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PathFinder {
|
||||
vertices: Vec<PathVertex>
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum PathKind {
|
||||
Full,
|
||||
Partial,
|
||||
Empty,
|
||||
}
|
||||
|
||||
fn heuristic(a: Vec3, b: Vec3) -> f32 {
|
||||
a.sqr_distance(&b)
|
||||
}
|
||||
|
||||
impl Default for PathFinder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PathError {
|
||||
InvalidIndex(usize),
|
||||
CyclicReferenceFound,
|
||||
}
|
||||
|
||||
impl PathFinder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
vertices: Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets active set of vertices. Links between vertices must contain
|
||||
/// valid indices (which are not out-of-bounds), otherwise path from/to
|
||||
/// such vertices won't be built.
|
||||
pub fn set_vertices(&mut self, vertices: Vec<PathVertex>) {
|
||||
self.vertices = vertices;
|
||||
}
|
||||
|
||||
/// Tries to find a vertex closest to given point.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// O(n) complexity.
|
||||
pub fn get_closest_vertex_to(&self, point: Vec3) -> Option<usize> {
|
||||
let mut closest_sqr_distance = std::f32::MAX;
|
||||
let mut closest_index = None;
|
||||
for (i, vertex) in self.vertices.iter().enumerate() {
|
||||
let sqr_distance = (vertex.position - point).sqr_len();
|
||||
if sqr_distance < closest_sqr_distance {
|
||||
closest_sqr_distance = sqr_distance;
|
||||
closest_index = Some(i);
|
||||
}
|
||||
}
|
||||
closest_index
|
||||
}
|
||||
|
||||
/// Creates bidirectional link between two vertices. Bidirectional means
|
||||
/// that point `a` can be reached from point `b` as well as point `b`
|
||||
/// can be reached from point `a`.
|
||||
pub fn link_bidirect(&mut self, a: usize, b: usize) {
|
||||
self.link_unidirect(a, b);
|
||||
self.link_unidirect(b, a);
|
||||
}
|
||||
|
||||
/// Creates unidirectional link between vertex `a` and vertex `b`. Unidirectional
|
||||
/// means that there is no direct link between `b` to `a`, only from `a` to `b`.
|
||||
pub fn link_unidirect(&mut self, a: usize, b: usize) {
|
||||
if let Some(vertex_a) = self.vertices.get_mut(a) {
|
||||
vertex_a.neighbours.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns shared reference to path vertex at given index.
|
||||
pub fn get_vertex(&self, index: usize) -> Option<&PathVertex> {
|
||||
self.vertices.get(index)
|
||||
}
|
||||
|
||||
/// Tries to build path from begin point to end point. Returns path kind:
|
||||
/// - Full: there are direct path from begin to end.
|
||||
/// - Partial: there are not direct path from begin to end, but it is closest.
|
||||
/// - Empty: no path available - in most cases indicates some error in input
|
||||
/// params.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// This is more or less naive implementation, it most certainly will be slower
|
||||
/// than specialized solutions. I haven't benchmarked this algorithm against any
|
||||
/// other library!
|
||||
pub fn build(&mut self, from: usize, to: usize, path: &mut Vec<Vec3>) -> Result<PathKind, PathError> {
|
||||
if self.vertices.is_empty() {
|
||||
return Ok(PathKind::Empty);
|
||||
}
|
||||
|
||||
path.clear();
|
||||
|
||||
for vertex in self.vertices.iter_mut() {
|
||||
vertex.clear();
|
||||
}
|
||||
|
||||
let end_pos = self.vertices.get(to)
|
||||
.ok_or(PathError::InvalidIndex(to))?.position;
|
||||
|
||||
// Put start vertex in open set.
|
||||
let start = self.vertices.get_mut(from).ok_or(PathError::InvalidIndex(from))?;
|
||||
start.state = PathVertexState::Open;
|
||||
start.g_score = 0.0;
|
||||
start.f_score = heuristic(start.position, end_pos);
|
||||
|
||||
let mut open_set_size = 1;
|
||||
while open_set_size > 0 {
|
||||
let mut current_index = 0;
|
||||
for (i, vertex) in self.vertices.iter().enumerate() {
|
||||
let current_vertex = self.vertices.get(current_index)
|
||||
.ok_or(PathError::InvalidIndex(current_index))?;
|
||||
if vertex.state == PathVertexState::Open && vertex.f_score < current_vertex.f_score {
|
||||
current_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
if current_index == to {
|
||||
self.reconstruct_path(current_index, path);
|
||||
return Ok(PathKind::Full);
|
||||
}
|
||||
|
||||
open_set_size -= 1;
|
||||
|
||||
// Take second mutable reference to vertices array, we'll enforce borrowing rules
|
||||
// at runtime. It will *never* give two mutable references to same path vertex.
|
||||
let unsafe_vertices: &mut Vec<PathVertex> = unsafe {
|
||||
&mut *(&mut self.vertices as *mut _)
|
||||
};
|
||||
|
||||
let current_vertex = self.vertices.get_mut(current_index)
|
||||
.ok_or(PathError::InvalidIndex(current_index))?;
|
||||
|
||||
current_vertex.state = PathVertexState::Closed;
|
||||
|
||||
for neighbour_index in current_vertex.neighbours.iter() {
|
||||
// Make sure that borrowing rules are not violated.
|
||||
if *neighbour_index == current_index {
|
||||
return Err(PathError::CyclicReferenceFound);
|
||||
}
|
||||
|
||||
// Safely get mutable reference to neighbour
|
||||
let neighbour = unsafe_vertices.get_mut(*neighbour_index)
|
||||
.ok_or(PathError::InvalidIndex(*neighbour_index))?;
|
||||
|
||||
if neighbour.state == PathVertexState::Closed {
|
||||
continue;
|
||||
}
|
||||
|
||||
neighbour.state = PathVertexState::Open;
|
||||
open_set_size += 1;
|
||||
|
||||
let g_score = current_vertex.g_score + heuristic(current_vertex.position, neighbour.position);
|
||||
|
||||
if g_score >= neighbour.g_score {
|
||||
continue;
|
||||
}
|
||||
|
||||
neighbour.parent = Some(current_index);
|
||||
neighbour.g_score = g_score;
|
||||
neighbour.f_score = g_score + heuristic(neighbour.position, end_pos);
|
||||
}
|
||||
}
|
||||
|
||||
// No direct path found, then there is probably partial path exists.
|
||||
// Look for vertex with least f_score and use it as starting point to
|
||||
// reconstruct partial path.
|
||||
let mut closest_index = 0;
|
||||
for (i, vertex) in self.vertices.iter().enumerate() {
|
||||
let closest_vertex = self.vertices.get(closest_index)
|
||||
.ok_or(PathError::InvalidIndex(closest_index))?;
|
||||
if vertex.f_score < closest_vertex.f_score {
|
||||
closest_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
self.reconstruct_path(closest_index, path);
|
||||
|
||||
if path.is_empty() {
|
||||
Ok(PathKind::Empty)
|
||||
} else {
|
||||
Ok(PathKind::Partial)
|
||||
}
|
||||
}
|
||||
|
||||
fn reconstruct_path(&self, mut current: usize, path: &mut Vec<Vec3>) {
|
||||
while let Some(vertex) = self.vertices.get(current) {
|
||||
path.push(vertex.position);
|
||||
if let Some(parent) = vertex.parent {
|
||||
current = parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::utils::astar::{PathFinder, PathVertex};
|
||||
use rg3d_core::math::vec3::Vec3;
|
||||
use rand::Rng;
|
||||
|
||||
#[test]
|
||||
fn astar_random_points() {
|
||||
let mut pathfinder = PathFinder::new();
|
||||
|
||||
let mut path = Vec::new();
|
||||
assert!(pathfinder.build(0, 0, &mut path).is_ok());
|
||||
assert!(path.is_empty());
|
||||
|
||||
let size = 40;
|
||||
|
||||
// Create vertices.
|
||||
let mut vertices = Vec::new();
|
||||
for y in 0..size {
|
||||
for x in 0..size {
|
||||
vertices.push(PathVertex::new(Vec3::new(x as f32, y as f32, 0.0)));
|
||||
}
|
||||
}
|
||||
pathfinder.set_vertices(vertices);
|
||||
|
||||
assert!(pathfinder.build(100000, 99999, &mut path).is_err());
|
||||
|
||||
// Link vertices as grid.
|
||||
for y in 0..(size - 1) {
|
||||
for x in 0..(size - 1) {
|
||||
pathfinder.link_bidirect(y * size + x, y * size + x + 1);
|
||||
pathfinder.link_bidirect(y * size + x, (y + 1) * size + x);
|
||||
}
|
||||
}
|
||||
|
||||
let mut paths_count = 0;
|
||||
|
||||
for _ in 0..1000 {
|
||||
let sx = rand::thread_rng().gen_range(0, size - 1);
|
||||
let sy = rand::thread_rng().gen_range(0, size - 1);
|
||||
|
||||
let tx = rand::thread_rng().gen_range(0, size - 1);
|
||||
let ty = rand::thread_rng().gen_range(0, size - 1);
|
||||
|
||||
let from = sy * size + sx;
|
||||
let to = ty * size + tx;
|
||||
|
||||
assert!(pathfinder.build(from, to, &mut path).is_ok());
|
||||
assert!(!path.is_empty());
|
||||
|
||||
if path.len() > 1 {
|
||||
paths_count += 1;
|
||||
|
||||
assert_eq!(*path.first().unwrap(), pathfinder.get_vertex(to).unwrap().position);
|
||||
assert_eq!(*path.last().unwrap(), pathfinder.get_vertex(from).unwrap().position);
|
||||
} else {
|
||||
let point = *path.first().unwrap();
|
||||
assert_eq!(point, pathfinder.get_vertex(to).unwrap().position);
|
||||
assert_eq!(point, pathfinder.get_vertex(from).unwrap().position);
|
||||
}
|
||||
|
||||
for pair in path.chunks(2) {
|
||||
if pair.len() == 2 {
|
||||
let a = pair[0];
|
||||
let b = pair[1];
|
||||
|
||||
assert!(a.distance(&b) <= 2.0f32.sqrt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(paths_count > 0);
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -1,7 +1,11 @@
|
||||
pub mod astar;
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use crate::scene::mesh::Mesh;
|
||||
use crate::{
|
||||
scene::mesh::Mesh,
|
||||
scene::base::AsBase
|
||||
};
|
||||
use rg3d_physics::static_geometry::{StaticGeometry, StaticTriangle};
|
||||
use crate::scene::base::AsBase;
|
||||
|
||||
pub struct UnsafeCollectionView<T> {
|
||||
items: *const T,
|
||||
|
||||
Reference in New Issue
Block a user