use crate::{
command::{Command, CommandGroup},
fyrox::{
core::pool::Handle,
engine::Engine,
graph::SceneGraph,
gui::{
button::Button,
button::{ButtonBuilder, ButtonMessage},
grid::{Column, GridBuilder, Row},
message::UiMessage,
scroll_viewer::ScrollViewerBuilder,
stack_panel::StackPanel,
stack_panel::StackPanelBuilder,
text::{Text, TextBuilder, TextMessage},
utils::make_simple_tooltip,
widget::WidgetBuilder,
widget::WidgetMessage,
window::{Window, WindowAlignment},
window::{WindowBuilder, WindowMessage, WindowTitle},
BuildContext, Thickness,
},
scene::mesh::surface::SurfaceData,
scene::{
base::BaseBuilder,
collider::{ColliderBuilder, ColliderShape, ConvexPolyhedronShape, GeometrySource},
mesh::{
surface::{SurfaceBuilder, SurfaceResource},
Mesh, MeshBuilder,
},
node::Node,
rigidbody::{RigidBody, RigidBodyBuilder, RigidBodyType},
Scene,
},
},
message::MessageSender,
preview::PreviewPanel,
scene::{
commands::graph::{AddNodeCommand, LinkNodesCommand},
GameScene, Selection,
},
world::selection::GraphSelection,
Message,
};
use fyrox::core::math::TriangleDefinition;
use fyrox::gui::VerticalAlignment;
pub struct MeshControlPanel {
pub root_widget: Handle<StackPanel>,
create_trimesh_collider: Handle<Button>,
create_convex_collider: Handle<Button>,
create_trimesh_rigid_body: Handle<Button>,
add_convex_collider: Handle<Button>,
add_trimesh_collider: Handle<Button>,
}
fn make_button(text: &str, tooltip: &str, ctx: &mut BuildContext) -> Handle<Button> {
ButtonBuilder::new(
WidgetBuilder::new()
.with_margin(Thickness::uniform(1.0))
.with_tooltip(make_simple_tooltip(ctx, tooltip)),
)
.with_text(text)
.build(ctx)
}
fn meshes_iter<'a>(
selection: &'a GraphSelection,
scene: &'a Scene,
) -> impl Iterator<Item = (Handle<Node>, &'a Mesh)> + 'a {
selection.nodes.iter().filter_map(|handle| {
scene
.graph
.try_get_of_type::<Mesh>(*handle)
.ok()
.map(|mesh| (*handle, mesh))
})
}
impl MeshControlPanel {
pub fn new(inspector_head: Handle<StackPanel>, ctx: &mut BuildContext) -> Self {
let create_trimesh_collider = make_button(
"Create Trimesh Collider",
"Creates a new trimesh collider and attaches it to the selected mesh(es)",
ctx,
);
let create_convex_collider = make_button(
"Create Convex Collider",
"Creates a new convex (polyhedron) collider and attaches it to the selected mesh(es).",
ctx,
);
let create_trimesh_rigid_body = make_button(
"Create Trimesh Rigid Body",
"Creates a new static rigid body with trimesh collider and attaches the selected \
mesh(es) to it.",
ctx,
);
let add_convex_collider = make_button(
"Add Convex Collider",
"Creates a new convex (polyhedron) collider and attaches it to an ancestor rigid \
body. This option could be useful if you have multiple meshes and want to put them into \
a single rigid body.",
ctx,
);
let add_trimesh_collider = make_button(
"Add Trimesh Collider",
"Creates a new trimesh collider and attaches it to an ancestor rigid body. This \
option could be useful if you have multiple meshes and want to put them into a single \
rigid body.",
ctx,
);
let root_widget = StackPanelBuilder::new(
WidgetBuilder::new()
.with_visibility(false)
.with_child(create_trimesh_collider)
.with_child(create_convex_collider)
.with_child(create_trimesh_rigid_body)
.with_child(add_convex_collider)
.with_child(add_trimesh_collider),
)
.build(ctx);
ctx.inner()
.send(root_widget, WidgetMessage::link_with(inspector_head));
Self {
root_widget,
create_trimesh_collider,
create_convex_collider,
create_trimesh_rigid_body,
add_convex_collider,
add_trimesh_collider,
}
}
pub fn handle_ui_message(
&mut self,
message: &UiMessage,
editor_selection: &Selection,
game_scene: &mut GameScene,
engine: &mut Engine,
sender: &MessageSender,
) {
let Some(selection) = editor_selection.as_graph() else {
return;
};
let scene = &engine.scenes[game_scene.scene];
let mut commands = Vec::new();
if let Some(ButtonMessage::Click) = message.data() {
if message.destination() == self.create_trimesh_collider {
for (mesh_handle, _) in meshes_iter(selection, scene) {
let collider =
ColliderBuilder::new(BaseBuilder::new().with_name("TrimeshCollider"))
.with_shape(ColliderShape::trimesh(vec![GeometrySource(mesh_handle)]))
.build_node();
commands.push(Command::new(AddNodeCommand::new(
collider,
mesh_handle,
false,
)))
}
} else if message.destination() == self.create_convex_collider {
for (mesh_handle, _) in meshes_iter(selection, scene) {
let collider =
ColliderBuilder::new(BaseBuilder::new().with_name("ConvexCollider"))
.with_shape(ColliderShape::Polyhedron(ConvexPolyhedronShape {
geometry_source: GeometrySource(mesh_handle),
}))
.build_node();
commands.push(Command::new(AddNodeCommand::new(
collider,
mesh_handle,
false,
)))
}
} else if message.destination() == self.create_trimesh_rigid_body {
let handles = scene
.graph
.generate_free_handles(2 * meshes_iter(selection, scene).count());
for (rb_collider_handles, (mesh_handle, mesh)) in
handles.chunks(2).zip(meshes_iter(selection, scene))
{
let rigid_body_handle = rb_collider_handles[0];
let collider_handle = rb_collider_handles[1];
let rigid_body =
RigidBodyBuilder::new(BaseBuilder::new().with_name("RigidBody"))
.with_body_type(RigidBodyType::Static)
.build_node();
let collider =
ColliderBuilder::new(BaseBuilder::new().with_name("TrimeshCollider"))
.with_shape(ColliderShape::trimesh(vec![GeometrySource(mesh_handle)]))
.build_node();
commands.extend([
Command::new(AddNodeCommand::new(rigid_body, mesh_handle, false)),
Command::new(AddNodeCommand::new(collider, rigid_body_handle, false)),
Command::new(LinkNodesCommand::new(rigid_body_handle, mesh.parent())),
Command::new(LinkNodesCommand::new(mesh_handle, rigid_body_handle)),
Command::new(LinkNodesCommand::new(collider_handle, rigid_body_handle)),
]);
}
} else if message.destination() == self.add_convex_collider {
for (mesh_handle, _) in meshes_iter(selection, scene) {
if let Some((ancestor_rigid_body, _)) =
scene.graph.find_component_up::<RigidBody>(mesh_handle)
{
let collider =
ColliderBuilder::new(BaseBuilder::new().with_name("ConvexCollider"))
.with_shape(ColliderShape::Polyhedron(ConvexPolyhedronShape {
geometry_source: GeometrySource(mesh_handle),
}))
.build_node();
commands.push(Command::new(AddNodeCommand::new(
collider,
ancestor_rigid_body,
false,
)))
}
}
} else if message.destination() == self.add_trimesh_collider {
for (mesh_handle, _) in meshes_iter(selection, scene) {
if let Some((ancestor_rigid_body, _)) =
scene.graph.find_component_up::<RigidBody>(mesh_handle)
{
let collider =
ColliderBuilder::new(BaseBuilder::new().with_name("TrimeshCollider"))
.with_shape(ColliderShape::trimesh(vec![GeometrySource(
mesh_handle,
)]))
.build_node();
commands.push(Command::new(AddNodeCommand::new(
collider,
ancestor_rigid_body,
false,
)))
}
}
}
}
if !commands.is_empty() {
sender.do_command(CommandGroup::from(commands));
}
}
pub fn handle_message(
&mut self,
message: &Message,
editor_selection: &Selection,
game_scene: Option<&mut GameScene>,
engine: &mut Engine,
) {
let Message::SelectionChanged { .. } = message else {
return;
};
let any_mesh = if let Some(game_scene) = game_scene {
let scene = &engine.scenes[game_scene.scene];
editor_selection.as_graph().is_some_and(|s| {
s.nodes()
.iter()
.any(|n| scene.graph.try_get_of_type::<Mesh>(*n).is_ok())
})
} else {
false
};
engine
.user_interfaces
.first()
.send(self.root_widget, WidgetMessage::Visibility(any_mesh));
}
}
pub struct SurfaceDataViewer {
pub window: Handle<Window>,
info: Handle<Text>,
preview_panel: PreviewPanel,
}
fn surface_data_statistics(surface_data: &SurfaceData) -> Result<String, std::fmt::Error> {
use std::fmt::Write;
let mut stats = String::new();
writeln!(
&mut stats,
"Vertices: {}\nVertex Size: {} bytes\nVertex Buffer Size: {} bytes",
surface_data.vertex_buffer.vertex_count(),
surface_data.vertex_buffer.vertex_size(),
surface_data.vertex_buffer.raw_data().len()
)?;
for (i, attribute) in surface_data.vertex_buffer.layout().iter().enumerate() {
writeln!(&mut stats, "[{i}]{attribute}")?;
}
let triangle_size = size_of::<TriangleDefinition>();
writeln!(
&mut stats,
"Triangles: {}\nTriangle Size: {} bytes\nTriangle Buffer Size: {} bytes",
surface_data.geometry_buffer.len(),
triangle_size,
surface_data.geometry_buffer.len() * triangle_size
)?;
Ok(stats)
}
impl SurfaceDataViewer {
pub fn new(engine: &mut Engine) -> Self {
let preview_panel = PreviewPanel::new(engine, 386, 386);
let ctx = &mut engine.user_interfaces.first_mut().build_ctx();
let info =
TextBuilder::new(WidgetBuilder::new().with_vertical_alignment(VerticalAlignment::Top))
.build(ctx);
let content = GridBuilder::new(
WidgetBuilder::new()
.with_child(preview_panel.root)
.with_child(
ScrollViewerBuilder::new(WidgetBuilder::new().on_row(0).on_column(1))
.with_content(info)
.build(ctx),
),
)
.add_row(Row::stretch())
.add_column(Column::stretch())
.add_column(Column::strict(220.0))
.build(ctx);
let window = WindowBuilder::new(WidgetBuilder::new().with_width(650.0).with_height(400.0))
.open(false)
.with_title(WindowTitle::text("Surface Data"))
.with_content(content)
.build(ctx);
Self {
window,
info,
preview_panel,
}
}
pub fn open(&mut self, surface_data: SurfaceResource, engine: &mut Engine) {
let guard = surface_data.data_ref();
let ui = engine.user_interfaces.first();
ui.send(
self.info,
TextMessage::Text(surface_data_statistics(&guard).unwrap_or_default()),
);
ui.send(
self.window,
WindowMessage::Open {
alignment: WindowAlignment::Center,
modal: true,
focus_content: true,
},
);
drop(guard);
let graph = &mut engine.scenes[self.preview_panel.scene()].graph;
let mesh = MeshBuilder::new(BaseBuilder::new())
.with_surfaces(vec![SurfaceBuilder::new(surface_data).build()])
.build(graph);
self.preview_panel.set_model(mesh, engine);
}
pub fn handle_ui_message(mut self, message: &UiMessage, engine: &mut Engine) -> Option<Self> {
self.preview_panel.handle_message(message, engine);
if let Some(WindowMessage::Close) = message.data() {
if message.destination() == self.window {
self.preview_panel.destroy(engine);
return None;
}
}
Some(self)
}
pub fn update(&mut self, engine: &mut Engine) {
self.preview_panel.update(engine)
}
}