Merge branch 'FyroxEngine:master' into terrain

This commit is contained in:
b-guild
2024-06-29 01:39:25 -07:00
committed by GitHub
60 changed files with 3273 additions and 1402 deletions
+1 -1
View File
@@ -144,7 +144,7 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "temurin"
@@ -0,0 +1,101 @@
(
name: "SpriteGizmoShader",
properties: [
(
name: "diffuseTexture",
kind: Sampler(default: None, fallback: White),
),
],
passes: [
(
name: "Forward",
draw_parameters: DrawParameters(
cull_face: None,
color_write: ColorMask(
red: true,
green: true,
blue: true,
alpha: true,
),
depth_write: true,
stencil_test: None,
depth_test: true,
blend: Some(BlendParameters(
func: BlendFunc(
sfactor: SrcAlpha,
dfactor: OneMinusSrcAlpha,
alpha_sfactor: SrcAlpha,
alpha_dfactor: OneMinusSrcAlpha,
),
equation: BlendEquation(
rgb: Add,
alpha: Add
)
)),
stencil_op: StencilOp(
fail: Keep,
zfail: Keep,
zpass: Keep,
write_mask: 0xFFFF_FFFF,
),
),
vertex_shader:
r#"
layout(location = 0) in vec3 vertexPosition;
layout(location = 1) in vec2 vertexTexCoord;
layout(location = 2) in vec2 vertexParams;
layout(location = 3) in vec4 vertexColor;
uniform mat4 fyrox_viewProjectionMatrix;
uniform mat4 fyrox_worldMatrix;
uniform vec3 fyrox_cameraUpVector;
uniform vec3 fyrox_cameraSideVector;
out vec2 texCoord;
out vec4 color;
vec2 rotateVec2(vec2 v, float angle)
{
float c = cos(angle);
float s = sin(angle);
mat2 m = mat2(c, -s, s, c);
return m * v;
}
void main()
{
float size = vertexParams.x;
float rotation = vertexParams.y;
texCoord = vertexTexCoord;
color = vertexColor;
vec2 vertexOffset = rotateVec2(vertexTexCoord * 2.0 - 1.0, rotation);
vec4 worldPosition = fyrox_worldMatrix * vec4(vertexPosition, 1.0);
vec3 offset = (vertexOffset.x * fyrox_cameraSideVector + vertexOffset.y * fyrox_cameraUpVector) * size;
gl_Position = fyrox_viewProjectionMatrix * (worldPosition + vec4(offset.x, offset.y, offset.z, 0.0));
}
"#,
fragment_shader:
r#"
uniform sampler2D diffuseTexture;
out vec4 FragColor;
in vec2 texCoord;
in vec4 color;
void main()
{
FragColor = color * S_SRGBToLinear(texture(diffuseTexture, texCoord));
// Pull depth towards near clipping plane so the gizmo will be drawn on top
// of everything, but its parts will be correctly handled by depth test.
gl_FragDepth = gl_FragCoord.z * 0.001;
}
"#,
),
],
)
+42
View File
@@ -62,6 +62,7 @@ impl AssetPreviewGeneratorsCollection {
this.add(CurveResourceState::type_uuid(), CurvePreview);
this.add(Font::type_uuid(), FontPreview);
this.add(UserInterface::type_uuid(), UserInterfacePreview);
this.add(SurfaceData::type_uuid(), SurfaceDataPreview);
this
}
@@ -403,6 +404,47 @@ impl AssetPreviewGenerator for ModelPreview {
}
}
pub struct SurfaceDataPreview;
impl AssetPreviewGenerator for SurfaceDataPreview {
fn generate_scene(
&mut self,
resource: &UntypedResource,
_resource_manager: &ResourceManager,
scene: &mut Scene,
) -> Handle<Node> {
if let Some(surface) = resource.try_cast::<SurfaceData>() {
MeshBuilder::new(BaseBuilder::new())
.with_surfaces(vec![SurfaceBuilder::new(surface.clone()).build()])
.build(&mut scene.graph)
} else {
Handle::NONE
}
}
fn generate_preview(
&mut self,
resource: &UntypedResource,
engine: &mut Engine,
) -> Option<AssetPreviewTexture> {
let surface = resource.try_cast::<SurfaceData>()?;
let mut scene = Scene::new();
scene.rendering_options.ambient_lighting_color = Color::opaque(180, 180, 180);
MeshBuilder::new(BaseBuilder::new())
.with_surfaces(vec![SurfaceBuilder::new(surface.clone()).build()])
.build(&mut scene.graph);
render_scene_to_texture(engine, &mut scene, Vector2::new(128.0, 128.0))
}
fn simple_icon(
&self,
_resource: &UntypedResource,
_resource_manager: &ResourceManager,
) -> Option<UntypedResource> {
load_image(include_bytes!("../../../resources/model.png"))
}
}
pub struct ShaderPreview;
impl AssetPreviewGenerator for ShaderPreview {
+1 -1
View File
@@ -35,7 +35,7 @@ impl CommandStackViewer {
let redo;
let clear;
let window = WindowBuilder::new(WidgetBuilder::new().with_name("CommandStackPanel"))
.with_title(WindowTitle::Text("Command Stack".to_owned()))
.with_title(WindowTitle::text("Command Stack"))
.with_content(
GridBuilder::new(
WidgetBuilder::new()
+2 -2
View File
@@ -82,7 +82,7 @@ impl Configurator {
let folder_browser = FileSelectorBuilder::new(
WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(400.0))
.open(false)
.with_title(WindowTitle::Text("Select Working Directory".into())),
.with_title(WindowTitle::text("Select Working Directory")),
)
.with_filter(filter)
.build(ctx);
@@ -112,7 +112,7 @@ impl Configurator {
.with_height(250.0)
.with_min_size(Vector2::new(370.0, 250.0)),
)
.with_title(WindowTitle::Text("Configure Editor".into()))
.with_title(WindowTitle::text("Configure Editor"))
.open(false)
.can_close(false)
.with_content(
+99 -79
View File
@@ -1,82 +1,87 @@
use crate::fyrox::gui::font::FontResource;
use crate::fyrox::gui::UiNode;
use crate::fyrox::scene::base::ScriptRecord;
use crate::fyrox::scene::mesh::BatchingMode;
use crate::fyrox::{
asset::{manager::ResourceManager, Resource},
core::{
futures::executor::block_on,
parking_lot::Mutex,
pool::{ErasedHandle, Handle},
},
gui,
gui::inspector::editors::{
bit::BitFieldPropertyEditorDefinition, collection::VecCollectionPropertyEditorDefinition,
enumeration::EnumPropertyEditorDefinition, inherit::InheritablePropertyEditorDefinition,
inspectable::InspectablePropertyEditorDefinition, PropertyEditorDefinitionContainer,
},
gui::UserInterface,
material::{
shader::{Shader, ShaderResource},
MaterialResource,
},
renderer::framework::state::PolygonFillMode,
resource::{
curve::{CurveResource, CurveResourceState},
model::{MaterialSearchOptions, Model, ModelResource},
texture::{
CompressionOptions, MipFilter, TextureMagnificationFilter, TextureMinificationFilter,
TextureResource, TextureWrapMode,
},
},
scene,
scene::{
base::{Base, LevelOfDetail, LodGroup, Mobility, Property, PropertyValue},
camera::{
ColorGradingLut, Exposure, OrthographicProjection, PerspectiveProjection, Projection,
SkyBox,
},
collider::{
BallShape, BitMask, CapsuleShape, ColliderShape, ConeShape, ConvexPolyhedronShape,
CuboidShape, CylinderShape, GeometrySource, HeightfieldShape, InteractionGroups,
SegmentShape, TriangleShape, TrimeshShape,
},
dim2,
graph::physics::CoefficientCombineRule,
joint::*,
light::{
directional::{CsmOptions, FrustumSplitOptions},
BaseLight,
},
mesh::{
surface::{BlendShape, Surface, SurfaceResource},
RenderPath,
},
node::Node,
particle_system::{
emitter::{
base::BaseEmitter, cuboid::CuboidEmitter, cylinder::CylinderEmitter,
sphere::SphereEmitter, Emitter,
},
ParticleSystemRng,
},
ragdoll::Limb,
rigidbody::RigidBodyType,
sound::{
self,
filter::{
AllPassFilterEffect, BandPassFilterEffect, HighPassFilterEffect,
HighShelfFilterEffect, LowPassFilterEffect, LowShelfFilterEffect,
},
reverb::Reverb,
Attenuate, AudioBus, Biquad, DistanceModel, Effect, SoundBuffer, SoundBufferResource,
Status,
},
terrain::{Chunk, Layer},
transform::Transform,
},
};
use crate::{
fyrox::{
asset::{manager::ResourceManager, untyped::UntypedResource, Resource},
core::{
futures::executor::block_on,
parking_lot::Mutex,
pool::{ErasedHandle, Handle},
},
gui::{
self,
font::FontResource,
inspector::editors::{
bit::BitFieldPropertyEditorDefinition,
collection::VecCollectionPropertyEditorDefinition,
enumeration::EnumPropertyEditorDefinition,
inherit::InheritablePropertyEditorDefinition,
inspectable::InspectablePropertyEditorDefinition,
PropertyEditorDefinitionContainer,
},
UiNode, UserInterface,
},
material::{
shader::{Shader, ShaderResource},
MaterialResource,
},
renderer::framework::state::PolygonFillMode,
resource::{
curve::{CurveResource, CurveResourceState},
model::{MaterialSearchOptions, Model, ModelResource},
texture::{
CompressionOptions, MipFilter, TextureMagnificationFilter,
TextureMinificationFilter, TextureResource, TextureWrapMode,
},
},
scene::{
self,
base::{
Base, LevelOfDetail, LodGroup, Mobility, Property, PropertyValue, ScriptRecord,
},
camera::{
ColorGradingLut, Exposure, OrthographicProjection, PerspectiveProjection,
Projection, SkyBox,
},
collider::{
BallShape, BitMask, CapsuleShape, ColliderShape, ConeShape, ConvexPolyhedronShape,
CuboidShape, CylinderShape, GeometrySource, HeightfieldShape, InteractionGroups,
SegmentShape, TriangleShape, TrimeshShape,
},
dim2,
graph::physics::CoefficientCombineRule,
joint::*,
light::{
directional::{CsmOptions, FrustumSplitOptions},
BaseLight,
},
mesh::{
surface::{BlendShape, Surface, SurfaceResource},
BatchingMode, RenderPath,
},
node::Node,
particle_system::{
emitter::{
base::BaseEmitter, cuboid::CuboidEmitter, cylinder::CylinderEmitter,
sphere::SphereEmitter, Emitter,
},
ParticleSystemRng,
},
ragdoll::Limb,
rigidbody::RigidBodyType,
sound::{
self,
filter::{
AllPassFilterEffect, BandPassFilterEffect, HighPassFilterEffect,
HighShelfFilterEffect, LowPassFilterEffect, LowShelfFilterEffect,
},
reverb::Reverb,
Attenuate, AudioBus, Biquad, DistanceModel, Effect, SoundBuffer,
SoundBufferResource, Status,
},
terrain::{Chunk, Layer},
tilemap::{tileset::TileSet, Tile},
transform::Transform,
},
},
inspector::editors::{
animation::{
AnimationContainerPropertyEditorDefinition, AnimationPropertyEditorDefinition,
@@ -93,7 +98,6 @@ use crate::{
},
message::MessageSender,
};
use fyrox::asset::untyped::UntypedResource;
use std::{path::Path, sync::Arc};
pub mod animation;
@@ -313,6 +317,19 @@ pub fn make_property_editors_container(sender: MessageSender) -> PropertyEditorD
>::new());
container.register_inheritable_vec_collection::<Option<UserInterface>>();
container.insert(ResourceFieldPropertyEditorDefinition::<TileSet>::new(
Arc::new(Mutex::new(
|resource_manager: &ResourceManager, path: &Path| {
resource_manager.try_request::<TileSet>(path).map(block_on)
},
)),
sender.clone(),
));
container.insert(InheritablePropertyEditorDefinition::<
Option<Resource<TileSet>>,
>::new());
container.register_inheritable_vec_collection::<Option<TileSet>>();
container.insert(ResourceFieldPropertyEditorDefinition::<Shader>::new(
Arc::new(Mutex::new(
|resource_manager: &ResourceManager, path: &Path| {
@@ -468,5 +485,8 @@ pub fn make_property_editors_container(sender: MessageSender) -> PropertyEditorD
container.register_inheritable_enum::<BatchingMode, _>();
container.register_inheritable_inspectable::<Tile>();
container.register_inheritable_vec_collection::<Tile>();
container
}
+114 -29
View File
@@ -1,24 +1,31 @@
use crate::{
asset::item::AssetItem,
fyrox::{
core::{pool::Handle, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*},
asset::manager::ResourceManager,
core::{
futures::executor::block_on, make_relative_path, pool::Handle, reflect::prelude::*,
type_traits::prelude::*, visitor::prelude::*,
},
graph::BaseSceneGraph,
gui::{
button::{ButtonBuilder, ButtonMessage},
define_widget_deref,
define_constructor, define_widget_deref,
grid::{Column, GridBuilder, Row},
inspector::{
editors::{
PropertyEditorBuildContext, PropertyEditorDefinition, PropertyEditorInstance,
PropertyEditorMessageContext, PropertyEditorTranslationContext,
},
InspectorError, PropertyChanged,
FieldKind, InspectorError, PropertyChanged,
},
message::UiMessage,
text::TextBuilder,
widget::{Widget, WidgetBuilder},
message::{MessageDirection, UiMessage},
text::{TextBuilder, TextMessage},
widget::{Widget, WidgetBuilder, WidgetMessage},
BuildContext, Control, Thickness, UiNode, UserInterface,
},
scene::mesh::surface::SurfaceResource,
scene::mesh::surface::{SurfaceData, SurfaceResource},
},
inspector::EditorEnvironment,
message::MessageSender,
Message,
};
@@ -27,6 +34,15 @@ use std::{
ops::{Deref, DerefMut},
};
#[derive(Debug, PartialEq, Clone)]
pub enum SurfaceDataPropertyEditorMessage {
Value(SurfaceResource),
}
impl SurfaceDataPropertyEditorMessage {
define_constructor!(SurfaceDataPropertyEditorMessage:Value => fn value(SurfaceResource), layout: false);
}
#[derive(Clone, Visit, Reflect, Debug, ComponentProvider, TypeUuidProvider)]
#[type_uuid(id = "8461a183-4fd4-4f74-a4f4-7fd8e84bf423")]
#[allow(dead_code)]
@@ -34,9 +50,13 @@ pub struct SurfaceDataPropertyEditor {
widget: Widget,
view: Handle<UiNode>,
data: SurfaceResource,
text: Handle<UiNode>,
#[visit(skip)]
#[reflect(hidden)]
sender: Option<MessageSender>,
#[visit(skip)]
#[reflect(hidden)]
resource_manager: ResourceManager,
}
define_widget_deref!(SurfaceDataPropertyEditor);
@@ -51,15 +71,58 @@ impl Control for SurfaceDataPropertyEditor {
sender.send(Message::ViewSurfaceData(self.data.clone()));
}
}
} else if let Some(WidgetMessage::Drop(dropped)) = message.data() {
if message.destination() == self.handle() {
if let Some(item) = ui.node(*dropped).cast::<AssetItem>() {
let path = if self
.resource_manager
.state()
.built_in_resources
.contains_key(&item.path)
{
Ok(item.path.clone())
} else {
make_relative_path(&item.path)
};
if let Ok(path) = path {
if let Ok(value) =
block_on(self.resource_manager.request::<SurfaceData>(path))
{
ui.send_message(SurfaceDataPropertyEditorMessage::value(
self.handle(),
MessageDirection::ToWidget,
value,
));
}
}
}
}
} else if let Some(SurfaceDataPropertyEditorMessage::Value(value)) = message.data() {
if message.destination() == self.handle
&& message.direction() == MessageDirection::ToWidget
&& &self.data != value
{
self.data = value.clone();
ui.send_message(message.reverse());
ui.send_message(TextMessage::text(
self.text,
MessageDirection::ToWidget,
surface_data_info(value),
));
}
}
}
}
fn surface_data_info(data: &SurfaceResource) -> String {
let use_count = data.use_count();
let kind = data.kind();
let guard = data.data_ref();
format!(
"Vertices: {}\nTriangles: {}\nUse Count: {}",
"{}\nVertices: {}\nTriangles: {}\nUse Count: {}",
kind,
guard.vertex_buffer.vertex_count(),
guard.geometry_buffer.len(),
use_count
@@ -71,6 +134,7 @@ impl SurfaceDataPropertyEditor {
ctx: &mut BuildContext,
data: SurfaceResource,
sender: MessageSender,
resource_manager: ResourceManager,
) -> Handle<UiNode> {
let view = ButtonBuilder::new(
WidgetBuilder::new()
@@ -83,27 +147,24 @@ impl SurfaceDataPropertyEditor {
.with_text("View...")
.build(ctx);
let text = TextBuilder::new(
WidgetBuilder::new()
.on_row(0)
.on_column(0)
.with_margin(Thickness::uniform(1.0)),
)
.with_text(surface_data_info(&data))
.build(ctx);
let widget = WidgetBuilder::new()
.with_child(
GridBuilder::new(
WidgetBuilder::new()
.with_child(
TextBuilder::new(
WidgetBuilder::new()
.on_row(0)
.on_column(0)
.with_margin(Thickness::uniform(1.0)),
)
.with_text(surface_data_info(&data))
.build(ctx),
)
.with_child(view),
)
.add_column(Column::stretch())
.add_column(Column::auto())
.add_row(Row::auto())
.build(ctx),
GridBuilder::new(WidgetBuilder::new().with_child(text).with_child(view))
.add_column(Column::stretch())
.add_column(Column::auto())
.add_row(Row::auto())
.build(ctx),
)
.with_allow_drop(true)
.build();
let editor = Self {
@@ -111,6 +172,8 @@ impl SurfaceDataPropertyEditor {
data,
view,
sender: Some(sender),
resource_manager,
text,
};
ctx.add_node(UiNode::new(editor))
@@ -138,18 +201,40 @@ impl PropertyEditorDefinition for SurfaceDataPropertyEditorDefinition {
ctx.build_context,
value.clone(),
self.sender.clone(),
ctx.environment
.as_ref()
.unwrap()
.as_any()
.downcast_ref::<EditorEnvironment>()
.map(|e| e.resource_manager.clone())
.unwrap(),
),
})
}
fn create_message(
&self,
_ctx: PropertyEditorMessageContext,
ctx: PropertyEditorMessageContext,
) -> Result<Option<UiMessage>, InspectorError> {
Ok(None)
let value = ctx.property_info.cast_value::<SurfaceResource>()?;
Ok(Some(SurfaceDataPropertyEditorMessage::value(
ctx.instance,
MessageDirection::ToWidget,
value.clone(),
)))
}
fn translate_message(&self, _ctx: PropertyEditorTranslationContext) -> Option<PropertyChanged> {
fn translate_message(&self, ctx: PropertyEditorTranslationContext) -> Option<PropertyChanged> {
if ctx.message.direction() == MessageDirection::FromWidget {
if let Some(SurfaceDataPropertyEditorMessage::Value(value)) = ctx.message.data() {
return Some(PropertyChanged {
owner_type_id: ctx.owner_type_id,
name: ctx.name.to_string(),
value: FieldKind::object(value.clone()),
});
}
}
None
}
}
+84 -116
View File
@@ -1,31 +1,32 @@
use crate::fyrox::{
asset::manager::ResourceManager,
core::{
color::Color,
log::{Log, MessageKind},
pool::ErasedHandle,
pool::Handle,
reflect::prelude::*,
},
engine::SerializationContext,
graph::BaseSceneGraph,
gui::{
button::ButtonMessage,
grid::{Column, GridBuilder, Row},
inspector::{
editors::PropertyEditorDefinitionContainer, InspectorBuilder, InspectorContext,
InspectorEnvironment, InspectorMessage,
},
message::{MessageDirection, UiMessage},
scroll_viewer::ScrollViewerBuilder,
text::{TextBuilder, TextMessage},
widget::WidgetBuilder,
window::{WindowBuilder, WindowTitle},
BuildContext, Thickness, UiNode, UserInterface,
},
};
use crate::{
absm::animation_container_ref,
fyrox::{
asset::manager::ResourceManager,
core::{
color::Color,
log::{Log, MessageKind},
pool::ErasedHandle,
pool::Handle,
reflect::prelude::*,
},
engine::SerializationContext,
graph::BaseSceneGraph,
gui::{
button::ButtonMessage,
grid::{Column, GridBuilder, Row},
inspector::{
editors::PropertyEditorDefinitionContainer, InspectorBuilder, InspectorContext,
InspectorEnvironment, InspectorError, InspectorMessage,
},
message::{MessageDirection, UiMessage},
scroll_viewer::ScrollViewerBuilder,
text::{TextBuilder, TextMessage},
widget::WidgetBuilder,
window::{WindowBuilder, WindowTitle},
BuildContext, Thickness, UiNode, UserInterface,
},
scene::SceneContainer,
},
gui::make_image_button_with_tooltip,
inspector::editors::make_property_editors_container,
load_image,
@@ -78,52 +79,12 @@ pub struct Inspector {
warning_text: Handle<UiNode>,
type_name_text: Handle<UiNode>,
docs_button: Handle<UiNode>,
old_selection: Selection,
}
#[macro_export]
macro_rules! make_command {
($cmd:ty, $handle:expr, $value:expr) => {
Some($crate::scene::commands::Command::new(<$cmd>::new(
$handle,
$value.cast_value().cloned()?,
)))
};
}
#[macro_export]
macro_rules! handle_properties {
($name:expr, $handle:expr, $value:expr, $($prop:path => $cmd:ty),*) => {
match $name {
$($prop => {
$crate::make_command!($cmd, $handle, $value)
})*
_ => None,
}
}
}
#[macro_export]
macro_rules! handle_property_changed {
($args:expr, $handle:expr, $($prop:path => $cmd:ty),*) => {
match $args.value {
FieldKind::Object(ref value) => {
match $args.name.as_ref() {
$($prop => {
$crate::make_command!($cmd, $handle, value)
})*
_ => None,
}
}
_ => None
}
}
}
fn fetch_available_animations(
selection: &Selection,
controller: &dyn SceneController,
engine: &Engine,
scenes: &SceneContainer,
) -> Vec<AnimationDefinition> {
if let Some(ui_scene) = controller.downcast_ref::<UiScene>() {
// TODO: Remove duplicated code.
@@ -145,7 +106,7 @@ fn fetch_available_animations(
if let Some(game_scene) = controller.downcast_ref::<GameScene>() {
if let Some(absm_selection) = selection.as_absm() {
if let Some((_, animation_player)) = animation_container_ref(
&engine.scenes[game_scene.scene].graph,
&scenes[game_scene.scene].graph,
absm_selection.absm_node_handle,
) {
return animation_player
@@ -161,6 +122,21 @@ fn fetch_available_animations(
Default::default()
}
fn print_errors(sync_errors: &[InspectorError]) {
for error in sync_errors {
Log::writeln(
MessageKind::Error,
format!("Failed to sync property. Reason: {:?}", error),
)
}
}
fn is_out_of_sync(sync_errors: &[InspectorError]) -> bool {
sync_errors
.iter()
.any(|err| matches!(err, &InspectorError::OutOfSync))
}
impl Inspector {
pub fn new(ctx: &mut BuildContext, sender: MessageSender) -> Self {
let property_editors = Arc::new(make_property_editors_container(sender));
@@ -249,11 +225,14 @@ impl Inspector {
warning_text,
type_name_text,
docs_button,
old_selection: Default::default(),
}
}
fn sync_to(&mut self, obj: &dyn Reflect, ui: &mut UserInterface) {
fn sync_to(
&mut self,
obj: &dyn Reflect,
ui: &mut UserInterface,
) -> Result<(), Vec<InspectorError>> {
let ctx = ui
.node(self.inspector)
.cast::<fyrox::gui::inspector::Inspector>()
@@ -261,14 +240,7 @@ impl Inspector {
.context()
.clone();
if let Err(sync_errors) = ctx.sync(obj, ui, 0, true, Default::default()) {
for error in sync_errors {
Log::writeln(
MessageKind::Error,
format!("Failed to sync property. Reason: {:?}", error),
)
}
}
ctx.sync(obj, ui, 0, true, Default::default())
}
pub fn sync_to_model(
@@ -278,46 +250,42 @@ impl Inspector {
engine: &mut Engine,
sender: &MessageSender,
) {
if &self.old_selection == editor_selection {
if !editor_selection.is_empty() {
controller.first_selected_entity(editor_selection, &engine.scenes, &mut |entity| {
self.sync_to(entity, engine.user_interfaces.first_mut());
});
} else if editor_selection.is_empty() {
self.clear(engine.user_interfaces.first());
}
} else {
engine
.user_interfaces
.first_mut()
.send_message(WidgetMessage::visibility(
self.warning_text,
MessageDirection::ToWidget,
editor_selection.len() > 1,
));
let mut need_clear = true;
// `first_selected_entity` is not guaranteed to call its callback.
// This flag allows us to determine whether an entity was found by first_selected_entity.
let mut found_entity = false;
let available_animations =
fetch_available_animations(editor_selection, controller, engine);
controller.first_selected_entity(editor_selection, &engine.scenes, &mut |entity| {
found_entity = true;
self.change_context(
entity,
engine.user_interfaces.first_mut(),
engine.resource_manager.clone(),
engine.serialization_context.clone(),
&available_animations,
sender,
)
});
// If `first_selected_entity` found no entity, then clear this inspector.
if !found_entity {
self.clear(engine.user_interfaces.first());
}
let ui = engine.user_interfaces.first_mut();
self.old_selection = editor_selection.clone();
ui.send_message(WidgetMessage::visibility(
self.warning_text,
MessageDirection::ToWidget,
editor_selection.len() > 1,
));
controller.first_selected_entity(editor_selection, &engine.scenes, &mut |entity| {
if let Err(errors) = self.sync_to(entity, ui) {
if is_out_of_sync(&errors) {
let available_animations =
fetch_available_animations(editor_selection, controller, &engine.scenes);
self.change_context(
entity,
ui,
engine.resource_manager.clone(),
engine.serialization_context.clone(),
&available_animations,
sender,
);
need_clear = false;
} else {
print_errors(&errors);
}
} else {
need_clear = false;
}
});
if need_clear {
self.clear(ui);
}
}
@@ -336,6 +336,12 @@ impl MoveGizmo {
Vector3::default()
}
pub fn set_position(&self, scene: &mut Scene, position: Vector3<f32>) {
let graph = &mut scene.graph;
let node = &mut graph[self.origin];
node.local_transform_mut().set_position(position);
}
pub fn sync_transform(&self, scene: &mut Scene, selection: &Selection, scale: Vector3<f32>) {
let graph = &mut scene.graph;
if let Some(selection) = selection.as_graph() {
@@ -353,4 +359,8 @@ impl MoveGizmo {
pub fn set_visible(&self, graph: &mut Graph, visible: bool) {
graph[self.origin].set_visibility(visible);
}
pub fn destroy(self, graph: &mut Graph) {
graph.remove_node(self.origin)
}
}
+31 -23
View File
@@ -253,7 +253,7 @@ pub fn make_save_file_selector(
) -> Handle<UiNode> {
FileSelectorBuilder::new(
WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(400.0))
.with_title(WindowTitle::Text("Save Scene As".into()))
.with_title(WindowTitle::text("Save Scene As"))
.open(false),
)
.with_mode(FileBrowserMode::Save { default_file_name })
@@ -320,7 +320,7 @@ impl SaveSceneConfirmationDialog {
.can_close(false)
.can_minimize(false)
.open(false)
.with_title(WindowTitle::Text("Unsaved changes".to_owned())),
.with_title(WindowTitle::text("Unsaved changes")),
)
.with_buttons(MessageBoxButtons::YesNoCancel)
.build(ctx);
@@ -771,7 +771,7 @@ impl Editor {
.can_close(false)
.can_minimize(false)
.open(false)
.with_title(WindowTitle::Text("Unsaved changes".to_owned())),
.with_title(WindowTitle::text("Unsaved changes")),
)
.with_buttons(MessageBoxButtons::YesNoCancel)
.build(ctx);
@@ -781,7 +781,7 @@ impl Editor {
.can_close(false)
.can_minimize(false)
.open(false)
.with_title(WindowTitle::Text("Validation failed!".to_owned())),
.with_title(WindowTitle::text("Validation failed!")),
)
.with_buttons(MessageBoxButtons::Ok)
.build(ctx);
@@ -983,12 +983,16 @@ impl Editor {
let mut processed = false;
if let Some(scene) = self.scenes.current_scene_entry_mut() {
if let Some(current_interaction_mode) = scene.current_interaction_mode {
processed |= scene
.interaction_modes
.get_mut(&current_interaction_mode)
.unwrap()
.on_hot_key(&hot_key, &mut *scene.controller, engine, &self.settings);
if let Some(current_interaction_mode) = scene
.current_interaction_mode
.and_then(|current_mode| scene.interaction_modes.get_mut(&current_mode))
{
processed |= current_interaction_mode.on_hot_key(
&hot_key,
&mut *scene.controller,
engine,
&self.settings,
);
}
}
@@ -1299,17 +1303,18 @@ impl Editor {
self.navmesh_panel
.handle_message(message, &current_scene_entry.selection);
if let Some(current_im) = current_scene_entry.current_interaction_mode {
current_scene_entry
.interaction_modes
.get_mut(&current_im)
.unwrap()
.handle_ui_message(
message,
&current_scene_entry.selection,
game_scene,
engine,
);
if let Some(interaction_mode) = current_scene_entry
.current_interaction_mode
.and_then(|current_mode| {
current_scene_entry.interaction_modes.get_mut(&current_mode)
})
{
interaction_mode.handle_ui_message(
message,
&current_scene_entry.selection,
game_scene,
engine,
);
}
self.scene_node_context_menu.borrow_mut().handle_ui_message(
@@ -2547,8 +2552,11 @@ impl Editor {
);
}
if let Some(mode) = entry.current_interaction_mode {
entry.interaction_modes.get_mut(&mode).unwrap().update(
if let Some(interaction_mode) = entry
.current_interaction_mode
.and_then(|current_mode| entry.interaction_modes.get_mut(&current_mode))
{
interaction_mode.update(
&entry.selection,
&mut **controller,
&mut self.engine,
+1 -1
View File
@@ -208,7 +208,7 @@ impl LightPanel {
.with_width(300.0)
.with_height(400.0),
)
.with_title(WindowTitle::Text("Light Settings".to_owned()))
.with_title(WindowTitle::text("Light Settings"))
.open(false)
.with_content(
GridBuilder::new(
+1 -1
View File
@@ -102,7 +102,7 @@ impl LogPanel {
let severity_list;
let window = WindowBuilder::new(WidgetBuilder::new().with_name("LogPanel"))
.can_minimize(false)
.with_title(WindowTitle::Text("Message Log".to_owned()))
.with_title(WindowTitle::text("Message Log"))
.with_content(
GridBuilder::new(
WidgetBuilder::new()
+27 -10
View File
@@ -1,32 +1,45 @@
use crate::fyrox::{
core::pool::Handle,
gui::{menu::MenuItemMessage, message::UiMessage, BuildContext, UiNode},
scene::{base::BaseBuilder, dim2::rectangle::RectangleBuilder, node::Node},
use crate::{
fyrox::{
core::pool::Handle,
gui::{menu::MenuItemMessage, message::UiMessage, BuildContext, UiNode},
scene::{
base::BaseBuilder, dim2::rectangle::RectangleBuilder, node::Node,
tilemap::TileMapBuilder,
},
},
menu::create_menu_item,
};
use crate::menu::create_menu_item;
pub struct Dim2Menu {
pub menu: Handle<UiNode>,
create_sprite: Handle<UiNode>,
create_tile_map: Handle<UiNode>,
}
impl Dim2Menu {
pub fn new(ctx: &mut BuildContext) -> Self {
let create_sprite;
let create_tile_map;
let menu = create_menu_item(
"2D",
vec![{
create_sprite = create_menu_item("Rectangle (2D Sprite)", vec![], ctx);
create_sprite
}],
vec![
{
create_sprite = create_menu_item("Rectangle (2D Sprite)", vec![], ctx);
create_sprite
},
{
create_tile_map = create_menu_item("Tile Map", vec![], ctx);
create_tile_map
},
],
ctx,
);
Self {
menu,
create_sprite,
create_tile_map,
}
}
@@ -36,6 +49,10 @@ impl Dim2Menu {
let node =
RectangleBuilder::new(BaseBuilder::new().with_name("Sprite (2D)")).build_node();
Some(node)
} else if message.destination() == self.create_tile_map {
let node =
TileMapBuilder::new(BaseBuilder::new().with_name("Tile Map")).build_node();
Some(node)
} else {
None
}
+2 -2
View File
@@ -73,7 +73,7 @@ impl FileMenu {
let configure_message = MessageBoxBuilder::new(
WindowBuilder::new(WidgetBuilder::new().with_width(250.0).with_height(150.0))
.open(false)
.with_title(WindowTitle::Text("Warning".to_owned())),
.with_title(WindowTitle::text("Warning")),
)
.with_text("Cannot reconfigure editor while scene is open! Close scene first and retry.")
.with_buttons(MessageBoxButtons::Ok)
@@ -141,7 +141,7 @@ impl FileMenu {
let load_file_selector = FileSelectorBuilder::new(
WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(400.0))
.open(false)
.with_title(WindowTitle::Text("Select a Scene To Load".into())),
.with_title(WindowTitle::text("Select a Scene To Load")),
)
.with_filter(make_scene_file_filter())
.build(ctx);
File diff suppressed because it is too large Load Diff
+92
View File
@@ -0,0 +1,92 @@
use crate::{
fyrox::{
core::{algebra::Vector3, pool::Handle},
scene::{collider::ColliderShape, node::Node, Scene},
},
plugins::collider::{
make_handle, try_get_collider_shape, try_get_collider_shape_mut, ShapeGizmoTrait,
ShapeHandleValue,
},
};
pub struct BallShapeGizmo {
radius_handle: Handle<Node>,
}
impl BallShapeGizmo {
pub fn new(root: Handle<Node>, visible: bool, scene: &mut Scene) -> Self {
Self {
radius_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for BallShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
func(self.radius_handle)
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Ball(ball)) = try_get_collider_shape(collider, scene) else {
return None;
};
if handle == self.radius_handle {
Some(Vector3::new(ball.radius, 0.0, 0.0))
} else {
None
}
}
fn handle_major_axis(
&self,
handle: Handle<Node>,
_collider: Handle<Node>,
_scene: &Scene,
) -> Option<Vector3<f32>> {
if handle == self.radius_handle {
Some(Vector3::x())
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Ball(ball)) = try_get_collider_shape(collider, scene) else {
return None;
};
if handle == self.radius_handle {
Some(ShapeHandleValue::Scalar(ball.radius))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
_initial_collider_local_position: Vector3<f32>,
) {
let Some(ColliderShape::Ball(ball)) = try_get_collider_shape_mut(collider, scene) else {
return;
};
if handle == self.radius_handle {
ball.radius = value.into_scalar().max(0.0);
}
}
}
+92
View File
@@ -0,0 +1,92 @@
use crate::{
fyrox::{
core::{algebra::Vector3, pool::Handle},
scene::{dim2::collider::ColliderShape, node::Node, Scene},
},
plugins::collider::{
make_handle, try_get_collider_shape_2d, try_get_collider_shape_mut_2d, ShapeGizmoTrait,
ShapeHandleValue,
},
};
pub struct Ball2DShapeGizmo {
radius_handle: Handle<Node>,
}
impl Ball2DShapeGizmo {
pub fn new(root: Handle<Node>, visible: bool, scene: &mut Scene) -> Self {
Self {
radius_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for Ball2DShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
func(self.radius_handle)
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Ball(ball)) = try_get_collider_shape_2d(collider, scene) else {
return None;
};
if handle == self.radius_handle {
Some(Vector3::new(ball.radius, 0.0, 0.0))
} else {
None
}
}
fn handle_major_axis(
&self,
handle: Handle<Node>,
_collider: Handle<Node>,
_scene: &Scene,
) -> Option<Vector3<f32>> {
if handle == self.radius_handle {
Some(Vector3::x())
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Ball(ball)) = try_get_collider_shape_2d(collider, scene) else {
return None;
};
if handle == self.radius_handle {
Some(ShapeHandleValue::Scalar(ball.radius))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
_initial_collider_local_position: Vector3<f32>,
) {
let Some(ColliderShape::Ball(ball)) = try_get_collider_shape_mut_2d(collider, scene) else {
return;
};
if handle == self.radius_handle {
ball.radius = value.into_scalar().max(0.0);
}
}
}
+126
View File
@@ -0,0 +1,126 @@
use crate::{
fyrox::{
core::{algebra::Vector3, math, pool::Handle},
scene::{collider::ColliderShape, node::Node, Scene},
},
plugins::collider::{
make_handle, try_get_collider_shape, try_get_collider_shape_mut, ShapeGizmoTrait,
ShapeHandleValue,
},
};
pub struct CapsuleShapeGizmo {
radius_handle: Handle<Node>,
begin_handle: Handle<Node>,
end_handle: Handle<Node>,
}
impl CapsuleShapeGizmo {
pub fn new(visible: bool, root: Handle<Node>, scene: &mut Scene) -> Self {
Self {
radius_handle: make_handle(scene, root, visible),
begin_handle: make_handle(scene, root, visible),
end_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for CapsuleShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
for handle in [self.radius_handle, self.begin_handle, self.end_handle] {
func(handle)
}
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Capsule(capsule)) = try_get_collider_shape(collider, scene) else {
return None;
};
if handle == self.radius_handle {
let perp = math::get_arbitrary_line_perpendicular(capsule.begin, capsule.end)
.unwrap_or_else(Vector3::x)
.scale(capsule.radius);
Some(capsule.begin + perp)
} else if handle == self.begin_handle {
Some(capsule.begin)
} else if handle == self.end_handle {
Some(capsule.end)
} else {
None
}
}
fn handle_major_axis(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Capsule(capsule)) = try_get_collider_shape(collider, scene) else {
return None;
};
if handle == self.radius_handle {
Some(
math::get_arbitrary_line_perpendicular(capsule.begin, capsule.end)
.unwrap_or_else(Vector3::x),
)
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Capsule(capsule)) = try_get_collider_shape(collider, scene) else {
return None;
};
if handle == self.radius_handle {
Some(ShapeHandleValue::Scalar(capsule.radius))
} else if handle == self.begin_handle {
Some(ShapeHandleValue::Vector(capsule.begin))
} else if handle == self.end_handle {
Some(ShapeHandleValue::Vector(capsule.end))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
_initial_collider_local_position: Vector3<f32>,
) {
let Some(ColliderShape::Capsule(capsule)) = try_get_collider_shape_mut(collider, scene)
else {
return;
};
if handle == self.radius_handle {
capsule.radius = value.into_scalar().max(0.0);
} else if handle == self.begin_handle {
capsule.begin = value.into_vector();
} else if handle == self.end_handle {
capsule.end = value.into_vector();
}
}
fn is_vector_handle(&self, handle: Handle<Node>) -> bool {
handle == self.begin_handle || handle == self.end_handle
}
}
+135
View File
@@ -0,0 +1,135 @@
use crate::{
fyrox::{
core::{algebra::Vector3, math, pool::Handle},
scene::{dim2::collider::ColliderShape, node::Node, Scene},
},
plugins::collider::{
make_handle, try_get_collider_shape_2d, try_get_collider_shape_mut_2d, ShapeGizmoTrait,
ShapeHandleValue,
},
};
pub struct Capsule2DShapeGizmo {
radius_handle: Handle<Node>,
begin_handle: Handle<Node>,
end_handle: Handle<Node>,
}
impl Capsule2DShapeGizmo {
pub fn new(visible: bool, root: Handle<Node>, scene: &mut Scene) -> Self {
Self {
radius_handle: make_handle(scene, root, visible),
begin_handle: make_handle(scene, root, visible),
end_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for Capsule2DShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
for handle in [self.radius_handle, self.begin_handle, self.end_handle] {
func(handle)
}
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Capsule(capsule)) = try_get_collider_shape_2d(collider, scene)
else {
return None;
};
if handle == self.radius_handle {
let perp = math::get_arbitrary_line_perpendicular(
capsule.begin.to_homogeneous(),
capsule.end.to_homogeneous(),
)
.unwrap_or_else(Vector3::x)
.scale(capsule.radius);
Some(capsule.begin.to_homogeneous() + perp)
} else if handle == self.begin_handle {
Some(capsule.begin.to_homogeneous())
} else if handle == self.end_handle {
Some(capsule.end.to_homogeneous())
} else {
None
}
}
fn handle_major_axis(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Capsule(capsule)) = try_get_collider_shape_2d(collider, scene)
else {
return None;
};
if handle == self.radius_handle {
Some(
math::get_arbitrary_line_perpendicular(
capsule.begin.to_homogeneous(),
capsule.end.to_homogeneous(),
)
.unwrap_or_else(Vector3::x),
)
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Capsule(capsule)) = try_get_collider_shape_2d(collider, scene)
else {
return None;
};
if handle == self.radius_handle {
Some(ShapeHandleValue::Scalar(capsule.radius))
} else if handle == self.begin_handle {
Some(ShapeHandleValue::Vector(capsule.begin.to_homogeneous()))
} else if handle == self.end_handle {
Some(ShapeHandleValue::Vector(capsule.end.to_homogeneous()))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
_initial_collider_local_position: Vector3<f32>,
) {
let Some(ColliderShape::Capsule(capsule)) = try_get_collider_shape_mut_2d(collider, scene)
else {
return;
};
if handle == self.radius_handle {
capsule.radius = value.into_scalar().max(0.0);
} else if handle == self.begin_handle {
capsule.begin = value.into_vector().xy();
} else if handle == self.end_handle {
capsule.end = value.into_vector().xy();
}
}
fn is_vector_handle(&self, handle: Handle<Node>) -> bool {
handle == self.begin_handle || handle == self.end_handle
}
}
+104
View File
@@ -0,0 +1,104 @@
use crate::{
fyrox::{
core::{algebra::Vector3, pool::Handle},
scene::{collider::ColliderShape, node::Node, Scene},
},
plugins::collider::{
make_handle, try_get_collider_shape, try_get_collider_shape_mut, ShapeGizmoTrait,
ShapeHandleValue,
},
};
pub struct ConeShapeGizmo {
radius_handle: Handle<Node>,
half_height_handle: Handle<Node>,
}
impl ConeShapeGizmo {
pub fn new(visible: bool, root: Handle<Node>, scene: &mut Scene) -> Self {
Self {
radius_handle: make_handle(scene, root, visible),
half_height_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for ConeShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
for handle in [self.radius_handle, self.half_height_handle] {
func(handle)
}
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Cone(cone)) = try_get_collider_shape(collider, scene) else {
return None;
};
if handle == self.radius_handle {
Some(Vector3::new(cone.radius, -cone.half_height, 0.0))
} else if handle == self.half_height_handle {
Some(Vector3::new(0.0, cone.half_height, 0.0))
} else {
None
}
}
fn handle_major_axis(
&self,
handle: Handle<Node>,
_collider: Handle<Node>,
_scene: &Scene,
) -> Option<Vector3<f32>> {
if handle == self.radius_handle {
Some(Vector3::x())
} else if handle == self.half_height_handle {
Some(Vector3::y())
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Cone(cone)) = try_get_collider_shape(collider, scene) else {
return None;
};
if handle == self.radius_handle {
Some(ShapeHandleValue::Scalar(cone.radius))
} else if handle == self.half_height_handle {
Some(ShapeHandleValue::Scalar(cone.half_height))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
_initial_collider_local_position: Vector3<f32>,
) {
let Some(ColliderShape::Cone(cone)) = try_get_collider_shape_mut(collider, scene) else {
return;
};
if handle == self.radius_handle {
cone.radius = value.into_scalar().max(0.0);
} else if handle == self.half_height_handle {
cone.half_height = value.into_scalar().max(0.0);
}
}
}
+175
View File
@@ -0,0 +1,175 @@
use crate::{
fyrox::{
core::{algebra::Vector3, pool::Handle},
graph::SceneGraph,
scene::{
collider::{Collider, ColliderShape},
node::Node,
Scene,
},
},
plugins::collider::{make_handle, try_get_collider_shape, ShapeGizmoTrait, ShapeHandleValue},
};
pub struct CuboidShapeGizmo {
pos_x_handle: Handle<Node>,
pos_y_handle: Handle<Node>,
pos_z_handle: Handle<Node>,
neg_x_handle: Handle<Node>,
neg_y_handle: Handle<Node>,
neg_z_handle: Handle<Node>,
}
impl CuboidShapeGizmo {
pub fn new(visible: bool, root: Handle<Node>, scene: &mut Scene) -> Self {
Self {
pos_x_handle: make_handle(scene, root, visible),
pos_y_handle: make_handle(scene, root, visible),
pos_z_handle: make_handle(scene, root, visible),
neg_x_handle: make_handle(scene, root, visible),
neg_y_handle: make_handle(scene, root, visible),
neg_z_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for CuboidShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
for handle in [
self.pos_x_handle,
self.pos_y_handle,
self.pos_z_handle,
self.neg_x_handle,
self.neg_y_handle,
self.neg_z_handle,
] {
func(handle)
}
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Cuboid(cuboid)) = try_get_collider_shape(collider, scene) else {
return None;
};
if handle == self.pos_x_handle {
Some(Vector3::new(cuboid.half_extents.x, 0.0, 0.0))
} else if handle == self.pos_y_handle {
Some(Vector3::new(0.0, cuboid.half_extents.y, 0.0))
} else if handle == self.pos_z_handle {
Some(Vector3::new(0.0, 0.0, cuboid.half_extents.z))
} else if handle == self.neg_x_handle {
Some(Vector3::new(-cuboid.half_extents.x, 0.0, 0.0))
} else if handle == self.neg_y_handle {
Some(Vector3::new(0.0, -cuboid.half_extents.y, 0.0))
} else if handle == self.neg_z_handle {
Some(Vector3::new(0.0, 0.0, -cuboid.half_extents.z))
} else {
None
}
}
fn handle_major_axis(
&self,
handle: Handle<Node>,
_collider: Handle<Node>,
_scene: &Scene,
) -> Option<Vector3<f32>> {
if handle == self.pos_x_handle {
Some(Vector3::x())
} else if handle == self.pos_y_handle {
Some(Vector3::y())
} else if handle == self.pos_z_handle {
Some(Vector3::z())
} else if handle == self.neg_x_handle {
Some(-Vector3::x())
} else if handle == self.neg_y_handle {
Some(-Vector3::y())
} else if handle == self.neg_z_handle {
Some(-Vector3::z())
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Cuboid(cuboid)) = try_get_collider_shape(collider, scene) else {
return None;
};
if handle == self.pos_x_handle {
Some(ShapeHandleValue::Scalar(cuboid.half_extents.x))
} else if handle == self.pos_y_handle {
Some(ShapeHandleValue::Scalar(cuboid.half_extents.y))
} else if handle == self.pos_z_handle {
Some(ShapeHandleValue::Scalar(cuboid.half_extents.z))
} else if handle == self.neg_x_handle {
Some(ShapeHandleValue::Scalar(cuboid.half_extents.x))
} else if handle == self.neg_y_handle {
Some(ShapeHandleValue::Scalar(cuboid.half_extents.y))
} else if handle == self.neg_z_handle {
Some(ShapeHandleValue::Scalar(cuboid.half_extents.z))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
initial_collider_local_position: Vector3<f32>,
) {
let Some(collider) = scene.graph.try_get_mut_of_type::<Collider>(collider) else {
return;
};
let ColliderShape::Cuboid(cuboid) = collider.shape_mut() else {
return;
};
if handle == self.pos_x_handle {
cuboid.half_extents.x = value.into_scalar().max(0.0);
} else if handle == self.pos_y_handle {
cuboid.half_extents.y = value.into_scalar().max(0.0);
} else if handle == self.pos_z_handle {
cuboid.half_extents.z = value.into_scalar().max(0.0);
} else if handle == self.neg_x_handle {
cuboid.half_extents.x = value.into_scalar().max(0.0);
let transform = collider.local_transform_mut();
transform.set_position(Vector3::new(
initial_collider_local_position.x - value.into_scalar() / 2.0,
initial_collider_local_position.y,
initial_collider_local_position.z,
));
} else if handle == self.neg_y_handle {
cuboid.half_extents.y = value.into_scalar().max(0.0);
let transform = collider.local_transform_mut();
transform.set_position(Vector3::new(
initial_collider_local_position.x,
initial_collider_local_position.y - value.into_scalar() / 2.0,
initial_collider_local_position.z,
));
} else if handle == self.neg_z_handle {
cuboid.half_extents.z = value.into_scalar().max(0.0);
let transform = collider.local_transform_mut();
transform.set_position(Vector3::new(
initial_collider_local_position.x,
initial_collider_local_position.y,
initial_collider_local_position.z - value.into_scalar() / 2.0,
));
}
}
}
+149
View File
@@ -0,0 +1,149 @@
use crate::{
fyrox::{
core::{algebra::Vector3, pool::Handle},
graph::SceneGraph,
scene::{
dim2::collider::{Collider, ColliderShape},
node::Node,
Scene,
},
},
plugins::collider::{
make_handle, try_get_collider_shape_2d, ShapeGizmoTrait, ShapeHandleValue,
},
};
pub struct Cuboid2DShapeGizmo {
pos_x_handle: Handle<Node>,
pos_y_handle: Handle<Node>,
neg_x_handle: Handle<Node>,
neg_y_handle: Handle<Node>,
}
impl Cuboid2DShapeGizmo {
pub fn new(visible: bool, root: Handle<Node>, scene: &mut Scene) -> Self {
Self {
pos_x_handle: make_handle(scene, root, visible),
pos_y_handle: make_handle(scene, root, visible),
neg_x_handle: make_handle(scene, root, visible),
neg_y_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for Cuboid2DShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
for handle in [
self.pos_x_handle,
self.pos_y_handle,
self.neg_x_handle,
self.neg_y_handle,
] {
func(handle)
}
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Cuboid(cuboid)) = try_get_collider_shape_2d(collider, scene) else {
return None;
};
if handle == self.pos_x_handle {
Some(Vector3::new(cuboid.half_extents.x, 0.0, 0.0))
} else if handle == self.pos_y_handle {
Some(Vector3::new(0.0, cuboid.half_extents.y, 0.0))
} else if handle == self.neg_x_handle {
Some(Vector3::new(-cuboid.half_extents.x, 0.0, 0.0))
} else if handle == self.neg_y_handle {
Some(Vector3::new(0.0, -cuboid.half_extents.y, 0.0))
} else {
None
}
}
fn handle_major_axis(
&self,
handle: Handle<Node>,
_collider: Handle<Node>,
_scene: &Scene,
) -> Option<Vector3<f32>> {
if handle == self.pos_x_handle {
Some(Vector3::x())
} else if handle == self.pos_y_handle {
Some(Vector3::y())
} else if handle == self.neg_x_handle {
Some(-Vector3::x())
} else if handle == self.neg_y_handle {
Some(-Vector3::y())
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Cuboid(cuboid)) = try_get_collider_shape_2d(collider, scene) else {
return None;
};
if handle == self.pos_x_handle {
Some(ShapeHandleValue::Scalar(cuboid.half_extents.x))
} else if handle == self.pos_y_handle {
Some(ShapeHandleValue::Scalar(cuboid.half_extents.y))
} else if handle == self.neg_x_handle {
Some(ShapeHandleValue::Scalar(cuboid.half_extents.x))
} else if handle == self.neg_y_handle {
Some(ShapeHandleValue::Scalar(cuboid.half_extents.y))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
initial_collider_local_position: Vector3<f32>,
) {
let Some(collider) = scene.graph.try_get_mut_of_type::<Collider>(collider) else {
return;
};
let ColliderShape::Cuboid(cuboid) = collider.shape_mut() else {
return;
};
if handle == self.pos_x_handle {
cuboid.half_extents.x = value.into_scalar().max(0.0);
} else if handle == self.pos_y_handle {
cuboid.half_extents.y = value.into_scalar().max(0.0);
} else if handle == self.neg_x_handle {
cuboid.half_extents.x = value.into_scalar().max(0.0);
let transform = collider.local_transform_mut();
transform.set_position(Vector3::new(
initial_collider_local_position.x - value.into_scalar() / 2.0,
initial_collider_local_position.y,
initial_collider_local_position.z,
));
} else if handle == self.neg_y_handle {
cuboid.half_extents.y = value.into_scalar().max(0.0);
let transform = collider.local_transform_mut();
transform.set_position(Vector3::new(
initial_collider_local_position.x,
initial_collider_local_position.y - value.into_scalar() / 2.0,
initial_collider_local_position.z,
));
}
}
}
+107
View File
@@ -0,0 +1,107 @@
use crate::{
fyrox::{
core::{algebra::Vector3, pool::Handle},
scene::{collider::ColliderShape, node::Node, Scene},
},
plugins::collider::{
make_handle, try_get_collider_shape, try_get_collider_shape_mut, ShapeGizmoTrait,
ShapeHandleValue,
},
};
pub struct CylinderShapeGizmo {
radius_handle: Handle<Node>,
half_height_handle: Handle<Node>,
}
impl CylinderShapeGizmo {
pub fn new(visible: bool, root: Handle<Node>, scene: &mut Scene) -> Self {
Self {
radius_handle: make_handle(scene, root, visible),
half_height_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for CylinderShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
for handle in [self.radius_handle, self.half_height_handle] {
func(handle)
}
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Cylinder(cylinder)) = try_get_collider_shape(collider, scene)
else {
return None;
};
if handle == self.radius_handle {
Some(Vector3::new(cylinder.radius, 0.0, 0.0))
} else if handle == self.half_height_handle {
Some(Vector3::new(0.0, cylinder.half_height, 0.0))
} else {
None
}
}
fn handle_major_axis(
&self,
handle: Handle<Node>,
_collider: Handle<Node>,
_scene: &Scene,
) -> Option<Vector3<f32>> {
if handle == self.radius_handle {
Some(Vector3::x())
} else if handle == self.half_height_handle {
Some(Vector3::y())
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Cylinder(cylinder)) = try_get_collider_shape(collider, scene)
else {
return None;
};
if handle == self.radius_handle {
Some(ShapeHandleValue::Scalar(cylinder.radius))
} else if handle == self.half_height_handle {
Some(ShapeHandleValue::Scalar(cylinder.half_height))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
_initial_collider_local_position: Vector3<f32>,
) {
let Some(ColliderShape::Cylinder(cylinder)) = try_get_collider_shape_mut(collider, scene)
else {
return;
};
if handle == self.radius_handle {
cylinder.radius = value.into_scalar().max(0.0);
} else if handle == self.half_height_handle {
cylinder.half_height = value.into_scalar().max(0.0);
}
}
}
+41
View File
@@ -0,0 +1,41 @@
use crate::{
fyrox::{
core::{algebra::Vector3, pool::Handle},
scene::{node::Node, Scene},
},
plugins::collider::{ShapeGizmoTrait, ShapeHandleValue},
};
pub struct DummyShapeGizmo;
impl ShapeGizmoTrait for DummyShapeGizmo {
fn for_each_handle(&self, _func: &mut dyn FnMut(Handle<Node>)) {}
fn handle_local_position(
&self,
_handle: Handle<Node>,
_collider: Handle<Node>,
_scene: &Scene,
) -> Option<Vector3<f32>> {
None
}
fn value_by_handle(
&self,
_handle: Handle<Node>,
_collider: Handle<Node>,
_scene: &Scene,
) -> Option<ShapeHandleValue> {
None
}
fn set_value_by_handle(
&self,
_handle: Handle<Node>,
_value: ShapeHandleValue,
_collider: Handle<Node>,
_scene: &mut Scene,
_initial_collider_local_position: Vector3<f32>,
) {
}
}
+704
View File
@@ -0,0 +1,704 @@
//! Collider shape editing plugin.
mod ball;
mod ball2d;
mod capsule;
mod capsule2d;
mod cone;
mod cuboid;
mod cuboid2d;
mod cylinder;
mod dummy;
mod segment;
mod segment2d;
mod triangle;
mod triangle2d;
use crate::{
camera::PickingOptions,
command::SetPropertyCommand,
fyrox::{
asset::untyped::ResourceKind,
core::{
algebra::{UnitQuaternion, Vector2, Vector3},
color::Color,
math::{plane::Plane, Matrix4Ext},
pool::Handle,
reflect::Reflect,
type_traits::prelude::*,
Uuid,
},
engine::Engine,
graph::{BaseSceneGraph, SceneGraph, SceneGraphNode},
gui::{BuildContext, UiNode},
material::{
shader::{ShaderResource, ShaderResourceExtension},
Material, MaterialResource,
},
scene::{
base::BaseBuilder, collider::Collider, collider::ColliderShape, dim2, node::Node,
sprite::SpriteBuilder, Scene,
},
},
interaction::{
calculate_gizmo_distance_scaling, gizmo::move_gizmo::MoveGizmo,
make_interaction_mode_button, plane::PlaneKind, InteractionMode,
},
load_texture,
message::MessageSender,
plugin::EditorPlugin,
plugins::collider::{
ball::BallShapeGizmo, ball2d::Ball2DShapeGizmo, capsule::CapsuleShapeGizmo,
capsule2d::Capsule2DShapeGizmo, cone::ConeShapeGizmo, cuboid::CuboidShapeGizmo,
cuboid2d::Cuboid2DShapeGizmo, cylinder::CylinderShapeGizmo, dummy::DummyShapeGizmo,
segment::SegmentShapeGizmo, segment2d::Segment2DShapeGizmo, triangle::TriangleShapeGizmo,
triangle2d::Triangle2DShapeGizmo,
},
scene::{commands::GameSceneContext, controller::SceneController, GameScene, Selection},
settings::Settings,
Editor, Message,
};
use fyrox::scene::sprite::Sprite;
fn try_get_collider_shape(collider: Handle<Node>, scene: &Scene) -> Option<ColliderShape> {
scene
.graph
.try_get_of_type::<Collider>(collider)
.map(|c| c.shape().clone())
}
fn try_get_collider_shape_mut(
collider: Handle<Node>,
scene: &mut Scene,
) -> Option<&mut ColliderShape> {
scene
.graph
.try_get_mut_of_type::<Collider>(collider)
.map(|c| c.shape_mut())
}
fn try_get_collider_shape_2d(
collider: Handle<Node>,
scene: &Scene,
) -> Option<dim2::collider::ColliderShape> {
scene
.graph
.try_get_of_type::<dim2::collider::Collider>(collider)
.map(|c| c.shape().clone())
}
fn try_get_collider_shape_mut_2d(
collider: Handle<Node>,
scene: &mut Scene,
) -> Option<&mut dim2::collider::ColliderShape> {
scene
.graph
.try_get_mut_of_type::<dim2::collider::Collider>(collider)
.map(|c| c.shape_mut())
}
trait ShapeGizmoTrait {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>));
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>>;
fn handle_major_axis(
&self,
_handle: Handle<Node>,
_collider: Handle<Node>,
_scene: &Scene,
) -> Option<Vector3<f32>> {
None
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue>;
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
initial_collider_local_position: Vector3<f32>,
);
fn is_vector_handle(&self, _handle: Handle<Node>) -> bool {
false
}
fn reset_handles(&self, scene: &mut Scene) {
self.for_each_handle(&mut |handle| {
scene.graph[handle].as_sprite_mut().set_color(Color::MAROON);
});
}
fn destroy(self: Box<Self>, scene: &mut Scene) {
self.for_each_handle(&mut |handle| scene.graph.remove_node(handle));
}
fn has_handle(&self, handle: Handle<Node>) -> bool {
let mut has_handle = false;
self.for_each_handle(&mut |other_handle| {
if other_handle == handle {
has_handle = true
}
});
has_handle
}
fn set_visibility(&self, scene: &mut Scene, visibility: bool) {
self.for_each_handle(&mut |handle| {
scene.graph[handle].set_visibility(visibility);
})
}
fn try_sync_to_collider(
&self,
collider: Handle<Node>,
camera: Handle<Node>,
scene: &mut Scene,
) -> bool {
let mut is_ok = true;
let transform = scene.graph[collider].global_transform();
self.for_each_handle(&mut |handle| {
if let Some(local_position) = self.handle_local_position(handle, collider, scene) {
let scale = calculate_gizmo_distance_scaling(&scene.graph, camera, handle);
let node = &mut scene.graph[handle];
node.local_transform_mut()
.set_position(transform.transform_point(&local_position.into()).coords)
.set_scale(scale)
.set_rotation(UnitQuaternion::from_matrix_eps(
&transform.basis(),
f32::EPSILON,
16,
Default::default(),
));
if let Some(sprite) = node.component_mut::<Sprite>() {
sprite.set_size(0.05 * scale.x);
}
} else {
is_ok = false;
}
});
is_ok
}
}
fn make_shape_gizmo(
collider: Handle<Node>,
scene: &mut Scene,
root: Handle<Node>,
visible: bool,
) -> Box<dyn ShapeGizmoTrait> {
if let Some(collider) = scene.graph.try_get_of_type::<Collider>(collider) {
let shape = collider.shape().clone();
use fyrox::scene::collider::ColliderShape;
match shape {
ColliderShape::Ball(_) => Box::new(BallShapeGizmo::new(root, visible, scene)),
ColliderShape::Cylinder(_) => Box::new(CylinderShapeGizmo::new(visible, root, scene)),
ColliderShape::Cone(_) => Box::new(ConeShapeGizmo::new(visible, root, scene)),
ColliderShape::Cuboid(_) => Box::new(CuboidShapeGizmo::new(visible, root, scene)),
ColliderShape::Capsule(_) => Box::new(CapsuleShapeGizmo::new(visible, root, scene)),
ColliderShape::Segment(_) => Box::new(SegmentShapeGizmo::new(root, visible, scene)),
ColliderShape::Triangle(_) => Box::new(TriangleShapeGizmo::new(root, visible, scene)),
ColliderShape::Trimesh(_)
| ColliderShape::Heightfield(_)
| ColliderShape::Polyhedron(_) => Box::new(DummyShapeGizmo),
}
} else if let Some(collider) = scene
.graph
.try_get_of_type::<dim2::collider::Collider>(collider)
{
let shape = collider.shape().clone();
use dim2::collider::ColliderShape;
match shape {
ColliderShape::Ball(_) => Box::new(Ball2DShapeGizmo::new(root, visible, scene)),
ColliderShape::Cuboid(_) => Box::new(Cuboid2DShapeGizmo::new(visible, root, scene)),
ColliderShape::Capsule(_) => Box::new(Capsule2DShapeGizmo::new(visible, root, scene)),
ColliderShape::Segment(_) => Box::new(Segment2DShapeGizmo::new(root, visible, scene)),
ColliderShape::Triangle(_) => Box::new(Triangle2DShapeGizmo::new(root, visible, scene)),
ColliderShape::Trimesh(_) | ColliderShape::Heightfield(_) => Box::new(DummyShapeGizmo),
}
} else {
Box::new(DummyShapeGizmo)
}
}
lazy_static! {
static ref GIZMO_SHADER: ShaderResource = {
ShaderResource::from_str(
include_str!("../../../resources/shaders/sprite_gizmo.shader",),
Default::default(),
)
.unwrap()
};
}
fn make_handle(scene: &mut Scene, root: Handle<Node>, visible: bool) -> Handle<Node> {
let mut material = Material::from_shader(GIZMO_SHADER.clone(), None);
material
.set_texture(
&"diffuseTexture".into(),
load_texture(include_bytes!("../../../resources/circle.png")),
)
.unwrap();
let handle = SpriteBuilder::new(BaseBuilder::new().with_visibility(visible))
.with_material(MaterialResource::new_ok(ResourceKind::Embedded, material))
.with_size(0.05)
.with_color(Color::MAROON)
.build(&mut scene.graph);
scene.graph.link_nodes(handle, root);
handle
}
#[derive(Copy, Clone)]
enum ShapeHandleValue {
Scalar(f32),
Vector(Vector3<f32>),
}
impl ShapeHandleValue {
fn into_scalar(self) -> f32 {
match self {
ShapeHandleValue::Scalar(scalar) => scalar,
ShapeHandleValue::Vector(_) => {
unreachable!()
}
}
}
fn into_vector(self) -> Vector3<f32> {
match self {
ShapeHandleValue::Scalar(_) => unreachable!(),
ShapeHandleValue::Vector(vector) => vector,
}
}
}
#[derive(Clone)]
enum ColliderInitialShape {
TwoD(dim2::collider::ColliderShape),
ThreeD(ColliderShape),
}
struct DragContext {
handle: Handle<Node>,
initial_handle_position: Vector3<f32>,
plane: Plane,
initial_value: ShapeHandleValue,
initial_collider_local_position: Vector3<f32>,
handle_major_axis: Option<Vector3<f32>>,
plane_kind: Option<PlaneKind>,
initial_shape: ColliderInitialShape,
}
#[derive(TypeUuidProvider)]
#[type_uuid(id = "a012dd4c-ce6d-4e7e-8879-fd8eddaa9677")]
pub struct ColliderShapeInteractionMode {
collider: Handle<Node>,
shape_gizmo: Box<dyn ShapeGizmoTrait>,
move_gizmo: MoveGizmo,
drag_context: Option<DragContext>,
selected_handle: Handle<Node>,
message_sender: MessageSender,
}
impl ColliderShapeInteractionMode {
fn set_visibility(
&mut self,
controller: &dyn SceneController,
engine: &mut Engine,
visibility: bool,
) {
let Some(game_scene) = controller.downcast_ref::<GameScene>() else {
return;
};
let scene = &mut engine.scenes[game_scene.scene];
self.shape_gizmo.set_visibility(scene, visibility);
}
}
impl InteractionMode for ColliderShapeInteractionMode {
fn on_left_mouse_button_down(
&mut self,
_editor_selection: &Selection,
controller: &mut dyn SceneController,
engine: &mut Engine,
mouse_position: Vector2<f32>,
_frame_size: Vector2<f32>,
_settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() else {
return;
};
let scene = &mut engine.scenes[game_scene.scene];
if let Some(result) = game_scene.camera_controller.pick(
&scene.graph,
PickingOptions {
cursor_pos: mouse_position,
editor_only: true,
filter: Some(&mut |handle, _| handle != self.move_gizmo.origin),
..Default::default()
},
) {
let initial_position = scene.graph[result.node].global_position();
let camera_view_dir = scene.graph[game_scene.camera_controller.camera]
.look_vector()
.try_normalize(f32::EPSILON)
.unwrap_or_default();
let plane = Plane::from_normal_and_point(&-camera_view_dir, &initial_position)
.unwrap_or_default();
let collider_node = &scene.graph[self.collider];
let initial_collider_local_position = **collider_node.local_transform().position();
let initial_shape = if let Some(collider) = collider_node.component_ref::<Collider>() {
ColliderInitialShape::ThreeD(collider.shape().clone())
} else if let Some(collider_2d) =
collider_node.component_ref::<dim2::collider::Collider>()
{
ColliderInitialShape::TwoD(collider_2d.shape().clone())
} else {
unreachable!();
};
if let Some(handle_value) =
self.shape_gizmo
.value_by_handle(result.node, self.collider, scene)
{
self.selected_handle = result.node;
self.drag_context = Some(DragContext {
handle: result.node,
initial_handle_position: initial_position,
plane,
handle_major_axis: self.shape_gizmo.handle_major_axis(
result.node,
self.collider,
scene,
),
initial_value: handle_value,
initial_collider_local_position,
plane_kind: None,
initial_shape,
})
} else if let Some(plane_kind) =
self.move_gizmo.handle_pick(result.node, &mut scene.graph)
{
if let Some(handle_value) =
self.shape_gizmo
.value_by_handle(self.selected_handle, self.collider, scene)
{
self.drag_context = Some(DragContext {
handle: self.selected_handle,
initial_handle_position: initial_position,
plane,
handle_major_axis: None,
initial_value: handle_value,
initial_collider_local_position,
plane_kind: Some(plane_kind),
initial_shape,
})
}
}
}
}
fn on_left_mouse_button_up(
&mut self,
_editor_selection: &Selection,
controller: &mut dyn SceneController,
engine: &mut Engine,
_mouse_pos: Vector2<f32>,
_frame_size: Vector2<f32>,
_settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() else {
return;
};
let scene = &mut engine.scenes[game_scene.scene];
if let Some(drag_context) = self.drag_context.take() {
let collider = self.collider;
let value = if let (Some(collider), ColliderInitialShape::ThreeD(shape)) = (
scene.graph.try_get_mut_of_type::<Collider>(collider),
drag_context.initial_shape.clone(),
) {
Box::new(std::mem::replace(collider.shape_mut(), shape)) as Box<dyn Reflect>
} else if let (Some(collider), ColliderInitialShape::TwoD(shape)) = (
scene
.graph
.try_get_mut_of_type::<dim2::collider::Collider>(collider),
drag_context.initial_shape.clone(),
) {
Box::new(std::mem::replace(collider.shape_mut(), shape)) as Box<dyn Reflect>
} else {
unreachable!();
};
let command = SetPropertyCommand::new("shape".into(), value, move |ctx| {
ctx.get_mut::<GameSceneContext>()
.scene
.graph
.node_mut(collider)
});
self.message_sender.do_command(command);
}
}
fn on_mouse_move(
&mut self,
mouse_offset: Vector2<f32>,
mouse_position: Vector2<f32>,
_editor_selection: &Selection,
controller: &mut dyn SceneController,
engine: &mut Engine,
frame_size: Vector2<f32>,
_settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() else {
return;
};
let scene = &mut engine.scenes[game_scene.scene];
self.shape_gizmo.reset_handles(scene);
self.move_gizmo.reset_state(&mut scene.graph);
if let Some(result) = game_scene.camera_controller.pick(
&scene.graph,
PickingOptions {
cursor_pos: mouse_position,
editor_only: true,
filter: Some(&mut |handle, _| handle != self.move_gizmo.origin),
..Default::default()
},
) {
if self.shape_gizmo.has_handle(result.node) {
scene.graph[result.node]
.as_sprite_mut()
.set_color(Color::RED);
}
self.move_gizmo.handle_pick(result.node, &mut scene.graph);
}
if let Some(drag_context) = self.drag_context.as_ref() {
match drag_context.initial_value {
ShapeHandleValue::Scalar(initial_value) => {
let camera = scene.graph[game_scene.camera_controller.camera].as_camera();
let ray = camera.make_ray(mouse_position, frame_size);
if let Some(intersection) = ray.plane_intersection_point(&drag_context.plane) {
let inv_transform = scene.graph[self.collider]
.global_transform()
.try_inverse()
.unwrap_or_default();
let local_space_drag_dir = inv_transform.transform_vector(
&(intersection - drag_context.initial_handle_position),
);
let sign = local_space_drag_dir
.dot(&drag_context.handle_major_axis.unwrap_or_default())
.signum();
let delta = sign
* drag_context
.initial_handle_position
.metric_distance(&intersection);
self.shape_gizmo.set_value_by_handle(
drag_context.handle,
ShapeHandleValue::Scalar(initial_value + delta),
self.collider,
scene,
drag_context.initial_collider_local_position,
);
}
}
ShapeHandleValue::Vector(_) => {
if let Some(plane_kind) = drag_context.plane_kind {
let value = self
.shape_gizmo
.value_by_handle(drag_context.handle, self.collider, scene)
.unwrap()
.into_vector();
let global_offset = self.move_gizmo.calculate_offset(
&scene.graph,
game_scene.camera_controller.camera,
mouse_offset,
mouse_position,
frame_size,
plane_kind,
);
let local_offset = scene.graph[self.collider]
.global_transform()
.try_inverse()
.unwrap_or_default()
.transform_vector(&global_offset);
self.shape_gizmo.set_value_by_handle(
drag_context.handle,
ShapeHandleValue::Vector(value + local_offset),
self.collider,
scene,
drag_context.initial_collider_local_position,
);
}
}
}
}
}
fn update(
&mut self,
_editor_selection: &Selection,
controller: &mut dyn SceneController,
engine: &mut Engine,
_settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() else {
return;
};
let scene = &mut engine.scenes[game_scene.scene];
if !self.shape_gizmo.try_sync_to_collider(
self.collider,
game_scene.camera_controller.camera,
scene,
) {
let new_gizmo =
make_shape_gizmo(self.collider, scene, game_scene.editor_objects_root, true);
let old_gizmo = std::mem::replace(&mut self.shape_gizmo, new_gizmo);
old_gizmo.destroy(scene);
}
self.move_gizmo.set_visible(
&mut scene.graph,
self.shape_gizmo.is_vector_handle(self.selected_handle),
);
let scale = calculate_gizmo_distance_scaling(
&scene.graph,
game_scene.camera_controller.camera,
self.move_gizmo.origin,
);
if let Some(handle_local_position) =
self.shape_gizmo
.handle_local_position(self.selected_handle, self.collider, scene)
{
let transform = scene.graph[self.collider].global_transform();
let position = transform
.transform_point(&handle_local_position.into())
.coords;
self.move_gizmo
.transform(&mut scene.graph)
.set_position(position)
.set_scale(scale);
}
}
fn activate(&mut self, controller: &dyn SceneController, engine: &mut Engine) {
self.set_visibility(controller, engine, true)
}
fn deactivate(&mut self, controller: &dyn SceneController, engine: &mut Engine) {
self.set_visibility(controller, engine, false)
}
fn make_button(&mut self, ctx: &mut BuildContext, selected: bool) -> Handle<UiNode> {
make_interaction_mode_button(
ctx,
include_bytes!("../../../resources/triangle.png"),
"Edit Collider Shape",
selected,
)
}
fn uuid(&self) -> Uuid {
Self::type_uuid()
}
}
#[derive(Default)]
pub struct ColliderShapePlugin {}
impl EditorPlugin for ColliderShapePlugin {
fn on_message(&mut self, message: &Message, editor: &mut Editor) {
let Some(entry) = editor.scenes.current_scene_entry_mut() else {
return;
};
let Some(selection) = entry.selection.as_graph() else {
return;
};
let Some(game_scene) = entry.controller.downcast_mut::<GameScene>() else {
return;
};
let scene = &mut editor.engine.scenes[game_scene.scene];
if let Message::SelectionChanged { .. } = message {
if let Some(mode) = entry
.interaction_modes
.remove_typed::<ColliderShapeInteractionMode>()
{
mode.shape_gizmo.destroy(scene);
}
for node_handle in selection.nodes().iter() {
if let Some(collider) = scene.graph.try_get(*node_handle) {
if collider.component_ref::<Collider>().is_none()
&& collider
.component_ref::<dim2::collider::Collider>()
.is_none()
{
continue;
}
let shape_gizmo = make_shape_gizmo(
*node_handle,
scene,
game_scene.editor_objects_root,
false,
);
let move_gizmo = MoveGizmo::new(game_scene, &mut editor.engine);
entry.interaction_modes.add(ColliderShapeInteractionMode {
collider: *node_handle,
shape_gizmo,
move_gizmo,
drag_context: None,
selected_handle: Default::default(),
message_sender: editor.message_sender.clone(),
});
break;
}
}
}
}
}
+94
View File
@@ -0,0 +1,94 @@
use crate::{
fyrox::{
core::{algebra::Vector3, pool::Handle},
scene::{collider::ColliderShape, node::Node, Scene},
},
plugins::collider::{
make_handle, try_get_collider_shape, try_get_collider_shape_mut, ShapeGizmoTrait,
ShapeHandleValue,
},
};
pub struct SegmentShapeGizmo {
begin_handle: Handle<Node>,
end_handle: Handle<Node>,
}
impl SegmentShapeGizmo {
pub fn new(root: Handle<Node>, visible: bool, scene: &mut Scene) -> Self {
Self {
begin_handle: make_handle(scene, root, visible),
end_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for SegmentShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
for handle in [self.begin_handle, self.end_handle] {
func(handle)
}
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Segment(segment)) = try_get_collider_shape(collider, scene) else {
return None;
};
if handle == self.begin_handle {
Some(segment.begin)
} else if handle == self.end_handle {
Some(segment.end)
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Segment(segment)) = try_get_collider_shape(collider, scene) else {
return None;
};
if handle == self.begin_handle {
Some(ShapeHandleValue::Vector(segment.begin))
} else if handle == self.end_handle {
Some(ShapeHandleValue::Vector(segment.end))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
_initial_collider_local_position: Vector3<f32>,
) {
let Some(ColliderShape::Segment(segment)) = try_get_collider_shape_mut(collider, scene)
else {
return;
};
if handle == self.begin_handle {
segment.begin = value.into_vector();
} else if handle == self.end_handle {
segment.end = value.into_vector();
}
}
fn is_vector_handle(&self, handle: Handle<Node>) -> bool {
handle == self.begin_handle || handle == self.end_handle
}
}
+96
View File
@@ -0,0 +1,96 @@
use crate::{
fyrox::{
core::{algebra::Vector3, pool::Handle},
scene::{dim2::collider::ColliderShape, node::Node, Scene},
},
plugins::collider::{
make_handle, try_get_collider_shape_2d, try_get_collider_shape_mut_2d, ShapeGizmoTrait,
ShapeHandleValue,
},
};
pub struct Segment2DShapeGizmo {
begin_handle: Handle<Node>,
end_handle: Handle<Node>,
}
impl Segment2DShapeGizmo {
pub fn new(root: Handle<Node>, visible: bool, scene: &mut Scene) -> Self {
Self {
begin_handle: make_handle(scene, root, visible),
end_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for Segment2DShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
for handle in [self.begin_handle, self.end_handle] {
func(handle)
}
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Segment(segment)) = try_get_collider_shape_2d(collider, scene)
else {
return None;
};
if handle == self.begin_handle {
Some(segment.begin.to_homogeneous())
} else if handle == self.end_handle {
Some(segment.end.to_homogeneous())
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Segment(segment)) = try_get_collider_shape_2d(collider, scene)
else {
return None;
};
if handle == self.begin_handle {
Some(ShapeHandleValue::Vector(segment.begin.to_homogeneous()))
} else if handle == self.end_handle {
Some(ShapeHandleValue::Vector(segment.end.to_homogeneous()))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
_initial_collider_local_position: Vector3<f32>,
) {
let Some(ColliderShape::Segment(segment)) = try_get_collider_shape_mut_2d(collider, scene)
else {
return;
};
if handle == self.begin_handle {
segment.begin = value.into_vector().xy();
} else if handle == self.end_handle {
segment.end = value.into_vector().xy();
}
}
fn is_vector_handle(&self, handle: Handle<Node>) -> bool {
handle == self.begin_handle || handle == self.end_handle
}
}
+104
View File
@@ -0,0 +1,104 @@
use crate::{
fyrox::{
core::{algebra::Vector3, pool::Handle},
scene::{collider::ColliderShape, node::Node, Scene},
},
plugins::collider::{
make_handle, try_get_collider_shape, try_get_collider_shape_mut, ShapeGizmoTrait,
ShapeHandleValue,
},
};
pub struct TriangleShapeGizmo {
a_handle: Handle<Node>,
b_handle: Handle<Node>,
c_handle: Handle<Node>,
}
impl TriangleShapeGizmo {
pub fn new(root: Handle<Node>, visible: bool, scene: &mut Scene) -> Self {
Self {
a_handle: make_handle(scene, root, visible),
b_handle: make_handle(scene, root, visible),
c_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for TriangleShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
for handle in [self.a_handle, self.b_handle, self.c_handle] {
func(handle)
}
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Triangle(triangle)) = try_get_collider_shape(collider, scene)
else {
return None;
};
if handle == self.a_handle {
Some(triangle.a)
} else if handle == self.b_handle {
Some(triangle.b)
} else if handle == self.c_handle {
Some(triangle.c)
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Triangle(triangle)) = try_get_collider_shape(collider, scene)
else {
return None;
};
if handle == self.a_handle {
Some(ShapeHandleValue::Vector(triangle.a))
} else if handle == self.b_handle {
Some(ShapeHandleValue::Vector(triangle.b))
} else if handle == self.c_handle {
Some(ShapeHandleValue::Vector(triangle.c))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
_initial_collider_local_position: Vector3<f32>,
) {
let Some(ColliderShape::Triangle(triangle)) = try_get_collider_shape_mut(collider, scene)
else {
return;
};
if handle == self.a_handle {
triangle.a = value.into_vector();
} else if handle == self.b_handle {
triangle.b = value.into_vector();
} else if handle == self.c_handle {
triangle.c = value.into_vector();
}
}
fn is_vector_handle(&self, handle: Handle<Node>) -> bool {
handle == self.a_handle || handle == self.b_handle || handle == self.c_handle
}
}
+105
View File
@@ -0,0 +1,105 @@
use crate::{
fyrox::{
core::{algebra::Vector3, pool::Handle},
scene::{dim2::collider::ColliderShape, node::Node, Scene},
},
plugins::collider::{
make_handle, try_get_collider_shape_2d, try_get_collider_shape_mut_2d, ShapeGizmoTrait,
ShapeHandleValue,
},
};
pub struct Triangle2DShapeGizmo {
a_handle: Handle<Node>,
b_handle: Handle<Node>,
c_handle: Handle<Node>,
}
impl Triangle2DShapeGizmo {
pub fn new(root: Handle<Node>, visible: bool, scene: &mut Scene) -> Self {
Self {
a_handle: make_handle(scene, root, visible),
b_handle: make_handle(scene, root, visible),
c_handle: make_handle(scene, root, visible),
}
}
}
impl ShapeGizmoTrait for Triangle2DShapeGizmo {
fn for_each_handle(&self, func: &mut dyn FnMut(Handle<Node>)) {
for handle in [self.a_handle, self.b_handle, self.c_handle] {
func(handle)
}
}
fn handle_local_position(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<Vector3<f32>> {
let Some(ColliderShape::Triangle(triangle)) = try_get_collider_shape_2d(collider, scene)
else {
return None;
};
if handle == self.a_handle {
Some(triangle.a.to_homogeneous())
} else if handle == self.b_handle {
Some(triangle.b.to_homogeneous())
} else if handle == self.c_handle {
Some(triangle.c.to_homogeneous())
} else {
None
}
}
fn value_by_handle(
&self,
handle: Handle<Node>,
collider: Handle<Node>,
scene: &Scene,
) -> Option<ShapeHandleValue> {
let Some(ColliderShape::Triangle(triangle)) = try_get_collider_shape_2d(collider, scene)
else {
return None;
};
if handle == self.a_handle {
Some(ShapeHandleValue::Vector(triangle.a.to_homogeneous()))
} else if handle == self.b_handle {
Some(ShapeHandleValue::Vector(triangle.b.to_homogeneous()))
} else if handle == self.c_handle {
Some(ShapeHandleValue::Vector(triangle.c.to_homogeneous()))
} else {
None
}
}
fn set_value_by_handle(
&self,
handle: Handle<Node>,
value: ShapeHandleValue,
collider: Handle<Node>,
scene: &mut Scene,
_initial_collider_local_position: Vector3<f32>,
) {
let Some(ColliderShape::Triangle(triangle)) =
try_get_collider_shape_mut_2d(collider, scene)
else {
return;
};
if handle == self.a_handle {
triangle.a = value.into_vector().xy();
} else if handle == self.b_handle {
triangle.b = value.into_vector().xy();
} else if handle == self.c_handle {
triangle.c = value.into_vector().xy();
}
}
fn is_vector_handle(&self, handle: Handle<Node>) -> bool {
handle == self.a_handle || handle == self.b_handle || handle == self.c_handle
}
}
+10 -10
View File
@@ -153,21 +153,21 @@ impl EditorSceneEntry {
pub fn set_interaction_mode(&mut self, engine: &mut Engine, mode: Option<Uuid>) {
if self.current_interaction_mode != mode {
// Deactivate current first.
if let Some(current_mode) = self.current_interaction_mode {
self.interaction_modes
.get_mut(&current_mode)
.unwrap()
.deactivate(&*self.controller, engine);
if let Some(interaction_mode) = self
.current_interaction_mode
.and_then(|current_mode| self.interaction_modes.get_mut(&current_mode))
{
interaction_mode.deactivate(&*self.controller, engine);
}
self.current_interaction_mode = mode;
// Activate new.
if let Some(current_mode) = self.current_interaction_mode {
self.interaction_modes
.get_mut(&current_mode)
.unwrap()
.activate(&*self.controller, engine);
if let Some(interaction_mode) = self
.current_interaction_mode
.and_then(|current_mode| self.interaction_modes.get_mut(&current_mode))
{
interaction_mode.activate(&*self.controller, engine);
}
}
}
+14 -25
View File
@@ -237,41 +237,30 @@ impl GameScene {
pure_scene
}
#[allow(clippy::redundant_clone)] // false positive
pub fn save(
&mut self,
path: &Path,
settings: &Settings,
engine: &mut Engine,
) -> Result<String, String> {
// Validate first.
let valid = true;
let mut reason = "Scene is not saved, because validation failed:\n".to_owned();
let mut pure_scene = self.make_purified_scene(engine);
if valid {
let mut pure_scene = self.make_purified_scene(engine);
let mut visitor = Visitor::new();
pure_scene.save("Scene", &mut visitor).unwrap();
let mut visitor = Visitor::new();
pure_scene.save("Scene", &mut visitor).unwrap();
if let Err(e) = visitor.save_binary(path) {
Err(format!("Failed to save scene! Reason: {}", e))
} else {
if settings.debugging.save_scene_in_text_form {
let text = visitor.save_text();
let mut path = path.to_path_buf();
path.set_extension("txt");
if let Ok(mut file) = File::create(path) {
Log::verify(file.write_all(text.as_bytes()));
}
}
Ok(format!("Scene {} was successfully saved!", path.display()))
}
if let Err(e) = visitor.save_binary(path) {
Err(format!("Failed to save scene! Reason: {}", e))
} else {
use std::fmt::Write;
writeln!(&mut reason, "\nPlease fix errors and try again.").unwrap();
if settings.debugging.save_scene_in_text_form {
let text = visitor.save_text();
let mut path = path.to_path_buf();
path.set_extension("txt");
if let Ok(mut file) = File::create(path) {
Log::verify(file.write_all(text.as_bytes()));
}
}
Err(reason)
Ok(format!("Scene {} was successfully saved!", path.display()))
}
}
+1 -1
View File
@@ -1029,7 +1029,7 @@ impl SceneViewer {
ui.send_message(WindowMessage::title(
self.window,
MessageDirection::ToWidget,
WindowTitle::Text(title),
WindowTitle::text(title),
));
}
+1 -1
View File
@@ -257,7 +257,7 @@ impl SettingsWindow {
let window = WindowBuilder::new(WidgetBuilder::new().with_width(500.0).with_height(600.0))
.open(false)
.with_title(WindowTitle::Text("Settings".to_owned()))
.with_title(WindowTitle::text("Settings"))
.with_content(
GridBuilder::new(
WidgetBuilder::new()
+2 -4
View File
@@ -70,7 +70,7 @@ impl PathFixer {
let scene_selector = FileSelectorBuilder::new(
WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(400.0))
.open(false)
.with_title(WindowTitle::Text("Select a scene for diagnostics".into())),
.with_title(WindowTitle::text("Select a scene for diagnostics")),
)
.with_filter(make_scene_file_filter())
.build(ctx);
@@ -78,9 +78,7 @@ impl PathFixer {
let new_path_selector = FileSelectorBuilder::new(
WindowBuilder::new(WidgetBuilder::new().with_width(300.0).with_height(400.0))
.open(false)
.with_title(WindowTitle::Text(
"Select a new path to the resource".into(),
)),
.with_title(WindowTitle::text("Select a new path to the resource")),
)
.build(ctx);
+1
View File
@@ -191,6 +191,7 @@ pub struct Animation<T: EntityId> {
#[reflect(hidden)]
#[visit(skip)]
events: VecDeque<AnimationEvent>,
#[visit(optional)]
max_event_capacity: usize,
}
+1
View File
@@ -168,6 +168,7 @@ where
#[reflect(hidden)]
#[visit(skip)]
events: VecDeque<Event>,
#[visit(optional)]
max_event_capacity: usize,
}
+34 -1
View File
@@ -37,6 +37,7 @@ pub struct Executor {
desired_update_rate: f32,
headless: bool,
throttle_threshold: f32,
throttle_frame_interval: usize,
}
impl Deref for Executor {
@@ -88,6 +89,7 @@ impl Executor {
desired_update_rate: Self::DEFAULT_UPDATE_RATE,
headless: false,
throttle_threshold: 2.0 * Self::DEFAULT_TIME_STEP,
throttle_frame_interval: 5,
}
}
@@ -125,6 +127,12 @@ impl Executor {
/// could be useful to prevent potential hang up of the game if its logic or rendering takes too
/// much time at each frame. The default value is two default time steps (33.3(3) milliseconds
/// or 0.0333(3) seconds).
///
/// ## Important notes
///
/// Physics could suffer from variable time step which may result in objects falling through the
/// ground and some other nasty things. Throttle threshold should be at reasonably high levels
/// (usually 2x-3x of the fixed time step).
pub fn set_throttle_threshold(&mut self, threshold: f32) {
self.throttle_threshold = threshold.max(0.001);
}
@@ -134,6 +142,21 @@ impl Executor {
self.throttle_threshold
}
/// Sets the amount of frames (consecutive) that will be allowed to have lag spikes and the engine
/// won't modify time step for internal update calls during such interval. This setting allows the
/// engine to ignore small lag spikes and do not fast-forward game logic using variable time step.
/// Variable time step could be bad for physics, which may result in objects falling through the
/// ground, etc. Default is 5 frames.
pub fn set_throttle_frame_interval(&mut self, interval: usize) {
self.throttle_frame_interval = interval;
}
/// Returns current throttle frame interval. See [`Self::set_throttle_frame_interval`] docs for
/// more info.
pub fn throttle_frame_interval(&self) -> usize {
self.throttle_frame_interval
}
/// Sets the desired update rate in frames per second.
pub fn set_desired_update_rate(&mut self, update_rate: f32) {
self.desired_update_rate = update_rate.abs();
@@ -158,6 +181,7 @@ impl Executor {
let event_loop = self.event_loop;
let headless = self.headless;
let throttle_threshold = self.throttle_threshold;
let throttle_frame_interval = self.throttle_frame_interval;
let args = Args::try_parse().unwrap_or_default();
@@ -166,6 +190,8 @@ impl Executor {
let mut previous = Instant::now();
let fixed_time_step = 1.0 / self.desired_update_rate;
let mut lag = 0.0;
let mut frame_counter = 0usize;
let mut last_throttle_frame_number = 0usize;
run_executor(event_loop, move |event, window_target| {
window_target.set_control_flow(ControlFlow::Wait);
@@ -217,13 +243,18 @@ impl Executor {
// Update rate stabilization loop.
while lag >= fixed_time_step {
let time_step;
if lag >= throttle_threshold {
if lag >= throttle_threshold
&& (frame_counter - last_throttle_frame_number
>= throttle_frame_interval)
{
// Modify the delta time to let the game internals to fast-forward the
// logic by the current lag.
time_step = lag;
// Reset the lag to exit early from the loop, thus preventing its
// potential infinite increase, that in its turn could hang up the game.
lag = 0.0;
last_throttle_frame_number = frame_counter;
} else {
time_step = fixed_time_step;
}
@@ -263,6 +294,8 @@ impl Executor {
);
engine.render().unwrap();
frame_counter += 1;
}
_ => (),
}
+21
View File
@@ -99,6 +99,9 @@ use std::{
use crate::plugin::dynamic::DynamicPlugin;
use crate::plugin::{DynamicPluginState, PluginContainer};
use crate::scene::mesh::surface;
use crate::scene::mesh::surface::{SurfaceData, SurfaceDataLoader};
use crate::scene::tilemap::tileset::{TileSet, TileSetLoader};
use fyrox_core::futures::future::join_all;
use fyrox_core::notify;
use fyrox_core::notify::{EventKind, RecursiveMode, Watcher};
@@ -1201,6 +1204,20 @@ pub(crate) fn initialize_resource_manager_loaders(
);
}
for material in [
surface::CUBE.clone(),
surface::QUAD.clone(),
surface::CYLINDER.clone(),
surface::SPHERE.clone(),
surface::CONE.clone(),
surface::TORUS.clone(),
] {
state.built_in_resources.insert(
material.kind().path_owned().unwrap(),
material.clone().into_untyped(),
);
}
state.constructors_container.add::<Texture>();
state.constructors_container.add::<Shader>();
state.constructors_container.add::<Model>();
@@ -1210,6 +1227,8 @@ pub(crate) fn initialize_resource_manager_loaders(
state.constructors_container.add::<Material>();
state.constructors_container.add::<Font>();
state.constructors_container.add::<UserInterface>();
state.constructors_container.add::<SurfaceData>();
state.constructors_container.add::<TileSet>();
let loaders = &mut state.loaders;
loaders.set(model_loader);
@@ -1229,6 +1248,8 @@ pub(crate) fn initialize_resource_manager_loaders(
loaders.set(UserInterfaceLoader {
resource_manager: resource_manager.clone(),
});
loaders.set(SurfaceDataLoader {});
loaders.set(TileSetLoader);
}
fn try_copy_library(source_lib_path: &Path, lib_path: &Path) -> Result<(), String> {
+1 -1
View File
@@ -355,7 +355,7 @@ impl RenderDataBundleStorageTrait for RenderDataBundleStorage {
// Create temporary surface data (valid for one frame).
let data = SurfaceResource::new_ok(
ResourceKind::Embedded,
SurfaceData::new(vertex_buffer, triangle_buffer, true),
SurfaceData::new(vertex_buffer, triangle_buffer),
);
self.bundle_map.insert(key, self.bundles.len());
+5 -5
View File
@@ -18,7 +18,7 @@ use std::{cell::RefCell, rc::Rc};
pub(crate) struct TextureRenderData {
pub gpu_texture: Rc<RefCell<GpuTexture>>,
pub data_hash: u64,
pub modifications_counter: u64,
}
#[derive(Default)]
@@ -41,7 +41,7 @@ fn create_gpu_texture(
)
.map(|gpu_texture| TextureRenderData {
gpu_texture: Rc::new(RefCell::new(gpu_texture)),
data_hash: texture.data_hash(),
modifications_counter: texture.modifications_count(),
})
}
@@ -87,8 +87,8 @@ impl TextureCache {
// Check if some value has changed in resource.
// Data might change from last frame, so we have to check it and upload new if so.
let data_hash = texture.data_hash();
if entry.data_hash != data_hash {
let modifications_count = texture.modifications_count();
if entry.modifications_counter != modifications_count {
let mut gpu_texture = entry.gpu_texture.borrow_mut();
if let Err(e) = gpu_texture.bind_mut(state, 0).set_data(
texture.kind().into(),
@@ -104,7 +104,7 @@ impl TextureCache {
),
)
} else {
entry.data_hash = data_hash;
entry.modifications_counter = modifications_count;
}
}
-1
View File
@@ -162,7 +162,6 @@ impl DeferredLightRenderer {
TriangleDefinition([20, 21, 22]),
TriangleDefinition([20, 22, 23]),
]),
true,
),
GeometryBufferKind::StaticDraw,
state,
+2 -2
View File
@@ -1444,7 +1444,7 @@ impl Renderer {
.unwrap()
.texture
.clone(),
data_hash: 0,
modifications_counter: 0,
},
render_target.data_ref().cache_index.clone(),
TimeToLive(f32::INFINITY),
@@ -1587,7 +1587,7 @@ impl Renderer {
self.texture_cache.map.spawn(
TextureRenderData {
gpu_texture: scene_associated_data.ldr_scene_frame_texture(),
data_hash: 0,
modifications_counter: 0,
},
rt.data_ref().cache_index.clone(),
TimeToLive(f32::INFINITY),
+4 -4
View File
@@ -237,8 +237,8 @@ enum FbxMeshBuilder {
impl FbxMeshBuilder {
fn build(self) -> SurfaceData {
match self {
FbxMeshBuilder::Static(builder) => SurfaceData::from_raw_mesh(builder.build(), false),
FbxMeshBuilder::Animated(builder) => SurfaceData::from_raw_mesh(builder.build(), false),
FbxMeshBuilder::Static(builder) => SurfaceData::from_raw_mesh(builder.build()),
FbxMeshBuilder::Animated(builder) => SurfaceData::from_raw_mesh(builder.build()),
}
}
}
@@ -279,7 +279,7 @@ async fn create_surfaces(
surface_data.blend_shapes_container =
make_blend_shapes_container(&surface_data.vertex_buffer, data.blend_shapes);
let mut surface = Surface::new(SurfaceResource::new_ok(
ResourceKind::Embedded,
ResourceKind::External(model_path.to_path_buf()),
surface_data,
));
surface.vertex_weights = data.skin_data;
@@ -291,7 +291,7 @@ async fn create_surfaces(
surface_data.blend_shapes_container =
make_blend_shapes_container(&surface_data.vertex_buffer, data.blend_shapes);
let mut surface = Surface::new(SurfaceResource::new_ok(
ResourceKind::Embedded,
ResourceKind::External(model_path.to_path_buf()),
surface_data,
));
surface.vertex_weights = data.skin_data;
+8 -3
View File
@@ -415,7 +415,7 @@ fn import_meshes(
let mut stats = GeometryStatistics::default();
for node in gltf.nodes() {
if let Some(mesh) = node.mesh() {
result.push(import_mesh(mesh, mats, bufs, &mut stats)?);
result.push(import_mesh(mesh, mats, bufs, path, &mut stats)?);
}
}
if cfg!(feature = "mesh_analysis") {
@@ -453,6 +453,7 @@ fn import_mesh(
mesh: gltf::Mesh,
mats: &[MaterialResource],
bufs: &[Vec<u8>],
path: &Path,
stats: &mut GeometryStatistics,
) -> Result<MeshData> {
#[cfg(feature = "gltf_blend_shapes")]
@@ -462,7 +463,7 @@ fn import_mesh(
let mut surfs: Vec<Surface> = Vec::with_capacity(mesh.primitives().len());
let mut blend_shapes: Option<Vec<BlendShape>> = None;
for prim in mesh.primitives() {
if let Some((surf, shapes)) = import_surface(prim, &morph_info, mats, bufs, stats)? {
if let Some((surf, shapes)) = import_surface(prim, &morph_info, mats, bufs, path, stats)? {
surfs.push(surf);
blend_shapes.get_or_insert(shapes);
}
@@ -525,6 +526,7 @@ fn import_surface(
morph_info: &BlendShapeInfoContainer,
mats: &[MaterialResource],
bufs: &[Vec<u8>],
path: &Path,
stats: &mut GeometryStatistics,
) -> Result<Option<(Surface, Vec<BlendShape>)>> {
if let Some(data) = build_surface_data(&prim, morph_info, bufs, stats)? {
@@ -532,7 +534,10 @@ fn import_surface(
if let Some(shape_con) = data.blend_shapes_container.as_ref() {
blend_shapes.clone_from(&shape_con.blend_shapes)
}
let mut surf = Surface::new(SurfaceResource::new_ok(ResourceKind::Embedded, data));
let mut surf = Surface::new(SurfaceResource::new_ok(
ResourceKind::External(path.to_path_buf()),
data,
));
if let Some(mat_index) = prim.material().index() {
surf.set_material(
mats.get(mat_index)
+2 -2
View File
@@ -197,11 +197,11 @@ pub fn build_surface_data(
let morphs: Vec<InputBlendShapeData> = build_morph_data(primitive, morph_info, buffers)?;
let mut surf = if !morphs.is_empty() {
let shapes = mesh::surface::BlendShapesContainer::from_lists(&vs, morphs.as_slice());
let mut surf = SurfaceData::new(vs, tris, false);
let mut surf = SurfaceData::new(vs, tris);
surf.blend_shapes_container = Some(shapes);
surf
} else {
SurfaceData::new(vs, tris, false)
SurfaceData::new(vs, tris)
};
let has_tex = primitive.get(&Semantic::TexCoords(0)).is_some();
let has_norm = primitive.get(&Semantic::Normals).is_some();
+15 -10
View File
@@ -256,7 +256,7 @@ pub struct Texture {
t_wrap_mode: TextureWrapMode,
mip_count: u32,
anisotropy: f32,
data_hash: u64,
modifications_counter: u64,
is_render_target: bool,
#[doc(hidden)]
#[reflect(hidden)]
@@ -372,7 +372,7 @@ impl Default for Texture {
t_wrap_mode: TextureWrapMode::Repeat,
mip_count: 1,
anisotropy: 16.0,
data_hash: 0,
modifications_counter: 0,
is_render_target: false,
cache_index: Default::default(),
}
@@ -625,7 +625,7 @@ impl TextureResourceExtension for TextureResource {
t_wrap_mode: TextureWrapMode::Repeat,
mip_count: 1,
anisotropy: 1.0,
data_hash: 0,
modifications_counter: 0,
is_render_target: true,
cache_index: Default::default(),
},
@@ -1364,7 +1364,7 @@ impl Texture {
Ok(Self {
pixel_kind,
data_hash: data_hash(&bytes),
modifications_counter: 0,
minification_filter: import_options.minification_filter,
magnification_filter: import_options.magnification_filter,
s_wrap_mode: import_options.s_wrap_mode,
@@ -1513,7 +1513,7 @@ impl Texture {
Ok(Self {
pixel_kind: final_pixel_kind,
kind: TextureKind::Rectangle { width, height },
data_hash: data_hash(&bytes),
modifications_counter: 0,
bytes: bytes.into(),
mip_count,
minification_filter: import_options.minification_filter,
@@ -1557,7 +1557,7 @@ impl Texture {
} else {
Some(Self {
kind,
data_hash: data_hash(&bytes),
modifications_counter: 0,
bytes: bytes.into(),
pixel_kind,
..Default::default()
@@ -1615,9 +1615,14 @@ impl Texture {
self.kind
}
/// Returns current data hash. Hash is guaranteed to be in actual state.
pub fn data_hash(&self) -> u64 {
self.data_hash
/// Returns total amount of modifications done on the texture.
pub fn modifications_count(&self) -> u64 {
self.modifications_counter
}
/// Calculates current data hash.
pub fn calculate_data_hash(&self) -> u64 {
data_hash(&self.bytes.0)
}
/// Returns current pixel kind.
@@ -1714,7 +1719,7 @@ pub struct TextureDataRefMut<'a> {
impl<'a> Drop for TextureDataRefMut<'a> {
fn drop(&mut self) {
self.texture.data_hash = data_hash(&self.texture.bytes);
self.texture.modifications_counter += 1;
}
}
-2
View File
@@ -201,7 +201,6 @@ impl RenderDataBundleStorageTrait for BatchContainer {
VertexBuffer::new_with_layout(layout, 0, BytesStorage::with_capacity(4096))
.unwrap(),
TriangleBuffer::new(Vec::with_capacity(4096)),
false,
),
),
material: material.clone(),
@@ -238,7 +237,6 @@ impl RenderDataBundleStorageTrait for BatchContainer {
SurfaceData::new(
src_data.vertex_buffer.clone_empty(4096),
TriangleBuffer::new(Vec::with_capacity(4096)),
false,
),
),
material: material.clone(),
+99 -33
View File
@@ -2,7 +2,13 @@
//! See [`Surface`] docs for more info and usage examples.
use crate::{
asset::{untyped::ResourceKind, Resource, ResourceData},
asset::{
io::ResourceIo,
loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
state::LoadError,
untyped::ResourceKind,
Resource, ResourceData,
},
core::{
algebra::{Matrix4, Point3, Vector2, Vector3, Vector4},
hash_combine,
@@ -33,7 +39,14 @@ use crate::{
};
use fxhash::{FxHashMap, FxHasher};
use half::f16;
use std::{any::Any, error::Error, hash::Hasher, path::Path, sync::Arc};
use lazy_static::lazy_static;
use std::{
any::Any,
error::Error,
hash::Hasher,
path::{Path, PathBuf},
sync::Arc,
};
/// A target shape for blending.
#[derive(Debug, Clone, Visit, Reflect, PartialEq)]
@@ -198,9 +211,6 @@ pub struct SurfaceData {
pub geometry_buffer: TriangleBuffer,
/// A container for blend shapes.
pub blend_shapes_container: Option<BlendShapesContainer>,
// If true - indicates that surface was generated and does not have reference
// resource. Procedural data will be serialized.
is_embedded: bool,
#[reflect(hidden)]
pub(crate) cache_index: Arc<AtomicIndex>,
}
@@ -229,15 +239,12 @@ impl ResourceData for SurfaceData {
}
impl SurfaceData {
/// Creates new data source using given vertices and indices. `is_procedural` flags affects serialization - when it
/// is `true` the content of the vertex and triangle buffers will be serialized. It is useful if you need to save
/// surfaces with procedural content.
pub fn new(vertex_buffer: VertexBuffer, triangles: TriangleBuffer, is_embedded: bool) -> Self {
/// Creates new data source using given vertices and indices.
pub fn new(vertex_buffer: VertexBuffer, triangles: TriangleBuffer) -> Self {
Self {
vertex_buffer,
geometry_buffer: triangles,
blend_shapes_container: None,
is_embedded,
cache_index: Arc::new(AtomicIndex::unassigned()),
}
}
@@ -273,7 +280,7 @@ impl SurfaceData {
/// Converts raw mesh into "renderable" mesh. It is useful to build procedural meshes. See [`RawMesh`] docs for more
/// info.
pub fn from_raw_mesh<T>(raw: RawMesh<T>, is_embedded: bool) -> Self
pub fn from_raw_mesh<T>(raw: RawMesh<T>) -> Self
where
T: VertexTrait,
{
@@ -281,7 +288,6 @@ impl SurfaceData {
vertex_buffer: VertexBuffer::new(raw.vertices.len(), raw.vertices).unwrap(),
geometry_buffer: TriangleBuffer::new(raw.triangles),
blend_shapes_container: Default::default(),
is_embedded,
cache_index: Arc::new(AtomicIndex::unassigned()),
}
}
@@ -407,7 +413,6 @@ impl SurfaceData {
Self::new(
VertexBuffer::new(vertices.len(), vertices).unwrap(),
TriangleBuffer::new(triangles),
true,
)
}
@@ -446,7 +451,6 @@ impl SurfaceData {
Self::new(
VertexBuffer::new(vertices.len(), vertices).unwrap(),
TriangleBuffer::new(triangles),
true,
)
}
@@ -485,7 +489,6 @@ impl SurfaceData {
TriangleDefinition([0, 1, 2]),
TriangleDefinition([0, 2, 3]),
]),
true,
);
data.calculate_tangents().unwrap();
data.transform_geometry(transform).unwrap();
@@ -590,7 +593,7 @@ impl SurfaceData {
}
}
let mut data = Self::from_raw_mesh(builder.build(), true);
let mut data = Self::from_raw_mesh(builder.build());
data.calculate_tangents().unwrap();
data.transform_geometry(transform).unwrap();
data
@@ -668,7 +671,7 @@ impl SurfaceData {
));
}
let mut data = Self::from_raw_mesh(builder.build(), true);
let mut data = Self::from_raw_mesh(builder.build());
data.calculate_tangents().unwrap();
data.transform_geometry(transform).unwrap();
data
@@ -725,7 +728,6 @@ impl SurfaceData {
let mut data = Self::new(
VertexBuffer::new(vertices.len(), vertices).unwrap(),
TriangleBuffer::new(triangles),
true,
);
data.calculate_tangents().unwrap();
data.transform_geometry(transform).unwrap();
@@ -840,7 +842,7 @@ impl SurfaceData {
));
}
let mut data = Self::from_raw_mesh(builder.build(), true);
let mut data = Self::from_raw_mesh(builder.build());
data.calculate_tangents().unwrap();
data.transform_geometry(transform).unwrap();
data
@@ -1019,7 +1021,6 @@ impl SurfaceData {
let mut data = Self::new(
VertexBuffer::new(vertices.len(), vertices).unwrap(),
TriangleBuffer::new(triangles),
true,
);
data.calculate_tangents().unwrap();
data.transform_geometry(&transform).unwrap();
@@ -1040,24 +1041,14 @@ impl SurfaceData {
self.geometry_buffer.modify().clear();
self.vertex_buffer.modify().clear();
}
/// Marks surface's content as procedural (created from code) or not. Content of procedural surfaces will
/// be serialized. It is useful if you need to save procedural surfaces to disk or some other storage.
pub fn set_embedded(&mut self, is_embedded: bool) {
self.is_embedded = is_embedded;
}
}
impl Visit for SurfaceData {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region(name)?;
self.is_embedded.visit("IsProcedural", &mut region)?;
if self.is_embedded {
self.vertex_buffer.visit("VertexBuffer", &mut region)?;
self.geometry_buffer.visit("GeometryBuffer", &mut region)?
}
self.vertex_buffer.visit("VertexBuffer", &mut region)?;
self.geometry_buffer.visit("GeometryBuffer", &mut region)?;
Ok(())
}
@@ -1207,7 +1198,7 @@ impl SurfaceResourceExtension for SurfaceResource {
///
/// let triangle_buffer = TriangleBuffer::new(vec![TriangleDefinition([0, 1, 2])]);
///
/// let data = SurfaceData::new(vertex_buffer, triangle_buffer, true);
/// let data = SurfaceData::new(vertex_buffer, triangle_buffer);
///
/// SurfaceBuilder::new(SurfaceResource::new_ok(ResourceKind::Embedded, data)).build()
/// }
@@ -1438,3 +1429,78 @@ impl SurfaceBuilder {
}
}
}
/// Default surface resource loader.
pub struct SurfaceDataLoader {}
impl ResourceLoader for SurfaceDataLoader {
fn extensions(&self) -> &[&str] {
&["surface"]
}
fn data_type_uuid(&self) -> Uuid {
<SurfaceData as TypeUuidProvider>::type_uuid()
}
fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
Box::pin(async move {
let io = io.as_ref();
let data = io.load_file(&path).await.map_err(LoadError::new)?;
let mut visitor = Visitor::load_from_memory(&data).map_err(LoadError::new)?;
let mut surface_data = SurfaceData::default();
surface_data
.visit("SurfaceData", &mut visitor)
.map_err(LoadError::new)?;
Ok(LoaderPayload::new(surface_data))
})
}
}
lazy_static! {
/// Cube surface resource.
pub static ref CUBE: SurfaceResource = SurfaceResource::new_ok(
"__CubeSurface".into(),
SurfaceData::make_cube(Matrix4::identity()),
);
}
lazy_static! {
/// Quad surface resource.
pub static ref QUAD: SurfaceResource = SurfaceResource::new_ok(
"__QuadSurface".into(),
SurfaceData::make_quad(&Matrix4::identity()),
);
}
lazy_static! {
/// Cylinder surface resource.
pub static ref CYLINDER: SurfaceResource = SurfaceResource::new_ok(
"__CylinderSurface".into(),
SurfaceData::make_cylinder(32, 1.0, 1.0, true, &Matrix4::identity()),
);
}
lazy_static! {
/// Sphere surface resource.
pub static ref SPHERE: SurfaceResource = SurfaceResource::new_ok(
"__SphereSurface".into(),
SurfaceData::make_sphere(32, 32, 1.0, &Matrix4::identity()),
);
}
lazy_static! {
/// Cone surface resource.
pub static ref CONE: SurfaceResource = SurfaceResource::new_ok(
"__ConeSurface".into(),
SurfaceData::make_cone(32, 1.0, 1.0, &Matrix4::identity()),
);
}
lazy_static! {
/// Torus surface resource.
pub static ref TORUS: SurfaceResource = SurfaceResource::new_ok(
"__TorusSurface".into(),
SurfaceData::make_torus(1.0, 0.25,32, 32, &Matrix4::identity()),
);
}
+1
View File
@@ -25,6 +25,7 @@ pub mod rigidbody;
pub mod sound;
pub mod sprite;
pub mod terrain;
pub mod tilemap;
pub mod transform;
use crate::{
+2
View File
@@ -1,5 +1,6 @@
//! A special container that is able to create nodes by their type UUID.
use crate::scene::tilemap::TileMap;
use crate::{
core::{
parking_lot::{Mutex, MutexGuard},
@@ -69,6 +70,7 @@ impl NodeConstructorContainer {
container.add::<AnimationBlendingStateMachine>();
container.add::<NavigationalMesh>();
container.add::<Ragdoll>();
container.add::<TileMap>();
container
}
-1
View File
@@ -44,7 +44,6 @@ impl TerrainGeometry {
let mut surface_data = SurfaceData::new(
VertexBuffer::new::<StaticVertex>(0, vec![]).unwrap(),
TriangleBuffer::default(),
false,
);
let mut vertex_buffer_mut = surface_data.vertex_buffer.modify();
+104
View File
@@ -0,0 +1,104 @@
#![allow(missing_docs)] // TODO
pub mod tileset;
use crate::{
core::{
algebra::Vector2, math::aabb::AxisAlignedBoundingBox, pool::Handle, reflect::prelude::*,
type_traits::prelude::*, variable::InheritableVariable, visitor::prelude::*,
},
graph::BaseSceneGraph,
renderer::bundle::RenderContext,
scene::{
base::Base, base::BaseBuilder, graph::Graph, node::Node, node::NodeTrait,
node::RdcControlFlow, tilemap::tileset::TileSet, tilemap::tileset::TileSetResource,
},
};
use std::ops::{Deref, DerefMut};
#[derive(Clone, Reflect, Default, Debug, PartialEq, Visit, ComponentProvider, TypeUuidProvider)]
#[type_uuid(id = "e429ca1b-a311-46c3-b580-d5a2f49db7e2")]
pub struct Tile {
position: Vector2<i32>,
}
#[derive(Clone, Reflect, Default, Debug, Visit, ComponentProvider, TypeUuidProvider)]
#[type_uuid(id = "aa9a3385-a4af-4faf-a69a-8d3af1a3aa67")]
pub struct TileMap {
base: Base,
tile_set: InheritableVariable<Option<TileSetResource>>,
tiles: InheritableVariable<Vec<Tile>>,
}
impl Deref for TileMap {
type Target = Base;
fn deref(&self) -> &Self::Target {
&self.base
}
}
impl DerefMut for TileMap {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.base
}
}
impl NodeTrait for TileMap {
crate::impl_query_component!();
fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
AxisAlignedBoundingBox::unit()
}
fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
self.local_bounding_box()
.transform(&self.global_transform())
}
fn id(&self) -> Uuid {
Self::type_uuid()
}
fn collect_render_data(&self, _ctx: &mut RenderContext) -> RdcControlFlow {
RdcControlFlow::Continue
}
}
pub struct TileMapBuilder {
base_builder: BaseBuilder,
tile_set: Option<TileSet>,
tiles: Vec<Tile>,
}
impl TileMapBuilder {
pub fn new(base_builder: BaseBuilder) -> Self {
Self {
base_builder,
tile_set: None,
tiles: Default::default(),
}
}
pub fn with_tile_set(mut self, tile_set: TileSet) -> Self {
self.tile_set = Some(tile_set);
self
}
pub fn with_tiles(mut self, tiles: Vec<Tile>) -> Self {
self.tiles = tiles;
self
}
pub fn build_node(self) -> Node {
Node::new(TileMap {
base: self.base_builder.build_base(),
tile_set: Default::default(),
tiles: Default::default(),
})
}
pub fn build(self, graph: &mut Graph) -> Handle<Node> {
graph.add_node(self.build_node())
}
}
+154
View File
@@ -0,0 +1,154 @@
use crate::{
asset::{
io::ResourceIo,
loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
state::LoadError,
Resource, ResourceData,
},
core::{
io::FileLoadError, math::Rect, reflect::prelude::*, type_traits::prelude::*,
visitor::prelude::*,
},
resource::texture::TextureResource,
};
use std::{
any::Any,
error::Error,
fmt::{Display, Formatter},
path::{Path, PathBuf},
sync::Arc,
};
use strum_macros::{AsRefStr, EnumString, VariantNames};
/// An error that may occur during tile set resource loading.
#[derive(Debug)]
pub enum TileSetResourceError {
/// An i/o error has occurred.
Io(FileLoadError),
/// An error that may occur due to version incompatibilities.
Visit(VisitError),
}
impl Display for TileSetResourceError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(v) => {
write!(f, "A file load error has occurred {v:?}")
}
Self::Visit(v) => {
write!(
f,
"An error that may occur due to version incompatibilities. {v:?}"
)
}
}
}
}
impl From<FileLoadError> for TileSetResourceError {
fn from(e: FileLoadError) -> Self {
Self::Io(e)
}
}
impl From<VisitError> for TileSetResourceError {
fn from(e: VisitError) -> Self {
Self::Visit(e)
}
}
#[derive(
Copy,
Clone,
Hash,
PartialEq,
Eq,
Debug,
Default,
Visit,
Reflect,
AsRefStr,
EnumString,
VariantNames,
TypeUuidProvider,
)]
#[type_uuid(id = "04a44fec-394f-4497-97d5-fe9e6f915831")]
pub enum TileCollider {
None,
#[default]
Rectangle,
Mesh,
}
#[derive(Clone, Default, Debug, Reflect, Visit)]
pub struct TileDefinition {
texture: TextureResource,
uv_rect: Rect<f32>,
collider: TileCollider,
}
#[derive(Clone, Default, Debug, Reflect, Visit, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "7b7e057b-a41e-4150-ab3b-0ae99f4024f0")]
pub struct TileSet {
tiles: Vec<TileDefinition>,
}
impl TileSet {
/// Load a tile set resource from the specific file path.
pub async fn from_file(path: &Path, io: &dyn ResourceIo) -> Result<Self, TileSetResourceError> {
let bytes = io.load_file(path).await?;
let mut visitor = Visitor::load_from_memory(&bytes)?;
let mut tile_set = TileSet::default();
tile_set.visit("TileSet", &mut visitor)?;
Ok(tile_set)
}
}
impl ResourceData for TileSet {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn type_uuid(&self) -> Uuid {
<Self as TypeUuidProvider>::type_uuid()
}
fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
let mut visitor = Visitor::new();
self.visit("TileSet", &mut visitor)?;
visitor.save_binary(path)?;
Ok(())
}
fn can_be_saved(&self) -> bool {
true
}
}
pub type TileSetResource = Resource<TileSet>;
pub struct TileSetLoader;
impl ResourceLoader for TileSetLoader {
fn extensions(&self) -> &[&str] {
&["tileset"]
}
fn data_type_uuid(&self) -> Uuid {
<TileSet as TypeUuidProvider>::type_uuid()
}
fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
Box::pin(async move {
let tile_set = TileSet::from_file(&path, io.as_ref())
.await
.map_err(LoadError::new)?;
Ok(LoaderPayload::new(tile_set))
})
}
}
+15
View File
@@ -545,6 +545,21 @@ where
closest_index
}
#[inline]
pub fn get_arbitrary_line_perpendicular(
begin: Vector3<f32>,
end: Vector3<f32>,
) -> Option<Vector3<f32>> {
let dir = (end - begin).try_normalize(f32::EPSILON)?;
for axis in [Vector3::z(), Vector3::y(), Vector3::x()] {
let perp = dir.cross(&axis);
if perp.norm_squared().ne(&0.0) {
return Some(perp);
}
}
None
}
/// Returns a tuple of (point index; triangle index) closest to the given point.
#[inline]
pub fn get_closest_point_triangle_set<P>(
+6
View File
@@ -77,6 +77,12 @@ pub struct ResourceManager {
state: Arc<Mutex<ResourceManagerState>>,
}
impl Debug for ResourceManager {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "ResourceManager")
}
}
/// An error that may occur during texture registration.
#[derive(Debug)]
pub enum ResourceRegistrationError {
+21 -1
View File
@@ -223,6 +223,8 @@ pub enum ButtonContent {
text: String,
/// Optional font of the button. If [`None`], the default font will be used.
font: Option<FontResource>,
/// Font size of the text. Default is 14.0
size: f32,
},
/// Arbitrary widget handle. It could be any widget handle, for example a handle of [`crate::image::Image`]
/// widget.
@@ -235,6 +237,7 @@ impl ButtonContent {
Self::Text {
text: s.as_ref().to_owned(),
font: None,
size: 14.0,
}
}
@@ -243,6 +246,16 @@ impl ButtonContent {
Self::Text {
text: s.as_ref().to_owned(),
font: Some(font),
size: 14.0,
}
}
/// Creates [`ButtonContent::Text`] with custom font and size.
pub fn text_with_font_size<S: AsRef<str>>(s: S, font: FontResource, size: f32) -> Self {
Self::Text {
text: s.as_ref().to_owned(),
font: Some(font),
size,
}
}
@@ -253,11 +266,12 @@ impl ButtonContent {
fn build(&self, ctx: &mut BuildContext) -> Handle<UiNode> {
match self {
Self::Text { text, font } => TextBuilder::new(WidgetBuilder::new())
Self::Text { text, font, size } => TextBuilder::new(WidgetBuilder::new())
.with_text(text)
.with_horizontal_text_alignment(HorizontalAlignment::Center)
.with_vertical_text_alignment(VerticalAlignment::Center)
.with_font(font.clone().unwrap_or_else(|| ctx.default_font()))
.with_font_size(*size)
.build(ctx),
Self::Node(node) => *node,
}
@@ -297,6 +311,12 @@ impl ButtonBuilder {
self
}
/// Sets the content of the button to be [`ButtonContent::Text`] (text with a custom font and size).
pub fn with_text_and_font_size(mut self, text: &str, font: FontResource, size: f32) -> Self {
self.content = Some(ButtonContent::text_with_font_size(text, font, size));
self
}
/// Sets the content of the button to be [`ButtonContent::Node`] (arbitrary widget handle).
pub fn with_content(mut self, node: Handle<UiNode>) -> Self {
self.content = Some(ButtonContent::Node(node));
+11
View File
@@ -483,6 +483,7 @@ pub const HEADER_MARGIN: Thickness = Thickness {
pub enum InspectorError {
/// An error occurred due to reflection when some value did not have its expected type.
CastError(CastError),
/// The object type has changed and the inspector context is no longer valid.
OutOfSync,
/// An error message produced by some editor with specialized details unique to that editor.
/// For example, an array editor might complain if there is no editor definition for the type
@@ -570,6 +571,8 @@ pub struct InspectorContext {
/// editor widgets. This identifies sync messages and prevents the Inspector from trying to translate them,
/// and thereby it prevents sync messages from potentially creating an infinite message loop.
pub sync_flag: u64,
/// Type id of the object for which the context was created.
pub object_type_id: TypeId,
}
impl PartialEq for InspectorContext {
@@ -589,6 +592,7 @@ impl Default for InspectorContext {
),
environment: None,
sync_flag: 0,
object_type_id: ().type_id(),
}
}
}
@@ -937,6 +941,7 @@ impl InspectorContext {
property_definitions: definition_container,
sync_flag,
environment,
object_type_id: object.type_id(),
}
}
@@ -959,6 +964,10 @@ impl InspectorContext {
generate_property_string_values: bool,
filter: PropertyFilter,
) -> Result<(), Vec<InspectorError>> {
if object.type_id() != self.object_type_id {
return Err(vec![InspectorError::OutOfSync]);
}
let mut sync_errors = Vec::new();
object.fields_info(&mut |fields_info| {
@@ -994,6 +1003,8 @@ impl InspectorContext {
}
Err(e) => sync_errors.push(e),
}
} else {
sync_errors.push(InspectorError::OutOfSync);
}
}
}
+1 -1
View File
@@ -239,7 +239,7 @@ impl Control for MessageBox {
ui.send_message(WindowMessage::title(
self.handle(),
MessageDirection::ToWidget,
WindowTitle::Text(title.clone()),
WindowTitle::text(title.clone()),
));
}
+19
View File
@@ -3,6 +3,7 @@
#![warn(missing_docs)]
use crate::font::FontResource;
use crate::{
border::BorderBuilder,
brush::Brush,
@@ -443,6 +444,8 @@ pub struct ScrollBarBuilder {
body: Option<Handle<UiNode>>,
show_value: bool,
value_precision: usize,
font: Option<FontResource>,
font_size: f32,
}
impl ScrollBarBuilder {
@@ -461,6 +464,8 @@ impl ScrollBarBuilder {
body: None,
show_value: false,
value_precision: 3,
font: None,
font_size: 14.0,
}
}
@@ -530,6 +535,18 @@ impl ScrollBarBuilder {
self
}
/// Sets the desired font.
pub fn with_font(mut self, font: FontResource) -> Self {
self.font = Some(font);
self
}
/// Sets the desired font size.
pub fn with_font_size(mut self, size: f32) -> Self {
self.font_size = size;
self
}
/// Creates new scroll bar instance and adds it to the user interface.
pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
let orientation = self.orientation.unwrap_or(Orientation::Horizontal);
@@ -615,6 +632,8 @@ impl ScrollBarBuilder {
Orientation::Vertical => 1,
}),
)
.with_font(self.font.unwrap_or_else(|| ctx.default_font()))
.with_font_size(self.font_size)
.with_text(format!("{:.1$}", value, self.value_precision))
.build(ctx);
+78 -7
View File
@@ -1,6 +1,7 @@
//! The Window widget provides a standard window that can contain another widget. See [`Window`] docs
//! for more info and usage examples.
use crate::font::FontResource;
use crate::{
border::BorderBuilder,
brush::Brush,
@@ -858,7 +859,11 @@ impl Control for Window {
}
WindowMessage::Title(title) => {
match title {
WindowTitle::Text(text) => {
WindowTitle::Text {
text,
font,
font_size,
} => {
if ui.try_get_of_type::<Text>(self.title).is_some() {
// Just modify existing text, this is much faster than
// re-create text everytime.
@@ -867,12 +872,33 @@ impl Control for Window {
MessageDirection::ToWidget,
text.clone(),
));
if let Some(font) = font {
ui.send_message(TextMessage::font(
self.title,
MessageDirection::ToWidget,
font.clone(),
))
}
if let Some(font_size) = font_size {
ui.send_message(TextMessage::font_size(
self.title,
MessageDirection::ToWidget,
*font_size,
));
}
} else {
ui.send_message(WidgetMessage::remove(
self.title,
MessageDirection::ToWidget,
));
self.title = make_text_title(&mut ui.build_ctx(), text);
let font =
font.clone().unwrap_or_else(|| ui.default_font.clone());
self.title = make_text_title(
&mut ui.build_ctx(),
text,
font,
(*font_size).unwrap_or(14.0),
);
ui.send_message(WidgetMessage::link(
self.title,
MessageDirection::ToWidget,
@@ -979,16 +1005,45 @@ pub struct WindowBuilder {
///
/// If you need more flexibility (i.e. put a picture near text) then [`WindowTitle::Node`] option
/// is for you: it allows to put any UI node hierarchy you want to.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq)]
pub enum WindowTitle {
Text(String),
Text {
/// Actual text of the title.
text: String,
/// Optional font, if [`None`], then the default font will be used.
font: Option<FontResource>,
/// Optional size of the text. Default is [`None`] (in this case default size will be used).
font_size: Option<f32>,
},
Node(Handle<UiNode>),
}
impl WindowTitle {
/// A shortcut to create [`WindowTitle::Text`]
pub fn text<P: AsRef<str>>(text: P) -> Self {
WindowTitle::Text(text.as_ref().to_owned())
WindowTitle::Text {
text: text.as_ref().to_owned(),
font: None,
font_size: None,
}
}
/// A shortcut to create [`WindowTitle::Text`] with custom font.
pub fn text_with_font<P: AsRef<str>>(text: P, font: FontResource) -> Self {
WindowTitle::Text {
text: text.as_ref().to_owned(),
font: Some(font),
font_size: None,
}
}
/// A shortcut to create [`WindowTitle::Text`] with custom font and size.
pub fn text_with_font_size<P: AsRef<str>>(text: P, font: FontResource, size: f32) -> Self {
WindowTitle::Text {
text: text.as_ref().to_owned(),
font: Some(font),
font_size: Some(size),
}
}
/// A shortcut to create [`WindowTitle::Node`]
@@ -997,13 +1052,20 @@ impl WindowTitle {
}
}
fn make_text_title(ctx: &mut BuildContext, text: &str) -> Handle<UiNode> {
fn make_text_title(
ctx: &mut BuildContext,
text: &str,
font: FontResource,
size: f32,
) -> Handle<UiNode> {
TextBuilder::new(
WidgetBuilder::new()
.with_margin(Thickness::left(5.0))
.on_row(0)
.on_column(0),
)
.with_font_size(size)
.with_font(font)
.with_vertical_text_alignment(VerticalAlignment::Center)
.with_horizontal_text_alignment(HorizontalAlignment::Left)
.with_text(text)
@@ -1225,7 +1287,16 @@ impl WindowBuilder {
None => Handle::NONE,
Some(window_title) => match window_title {
WindowTitle::Node(node) => node,
WindowTitle::Text(text) => make_text_title(ctx, &text),
WindowTitle::Text {
text,
font,
font_size,
} => make_text_title(
ctx,
&text,
font.unwrap_or_else(|| ctx.default_font()),
font_size.unwrap_or(14.0),
),
},
};
title