mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
Merge branch 'FyroxEngine:master' into terrain
This commit is contained in:
@@ -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;
|
||||
}
|
||||
"#,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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(¤t_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(¤t_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, ¤t_scene_entry.selection);
|
||||
|
||||
if let Some(current_im) = current_scene_entry.current_interaction_mode {
|
||||
current_scene_entry
|
||||
.interaction_modes
|
||||
.get_mut(¤t_im)
|
||||
.unwrap()
|
||||
.handle_ui_message(
|
||||
message,
|
||||
¤t_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(¤t_mode)
|
||||
})
|
||||
{
|
||||
interaction_mode.handle_ui_message(
|
||||
message,
|
||||
¤t_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(¤t_mode))
|
||||
{
|
||||
interaction_mode.update(
|
||||
&entry.selection,
|
||||
&mut **controller,
|
||||
&mut self.engine,
|
||||
|
||||
+1
-1
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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(¤t_mode)
|
||||
.unwrap()
|
||||
.deactivate(&*self.controller, engine);
|
||||
if let Some(interaction_mode) = self
|
||||
.current_interaction_mode
|
||||
.and_then(|current_mode| self.interaction_modes.get_mut(¤t_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(¤t_mode)
|
||||
.unwrap()
|
||||
.activate(&*self.controller, engine);
|
||||
if let Some(interaction_mode) = self
|
||||
.current_interaction_mode
|
||||
.and_then(|current_mode| self.interaction_modes.get_mut(¤t_mode))
|
||||
{
|
||||
interaction_mode.activate(&*self.controller, engine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-25
@@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1029,7 +1029,7 @@ impl SceneViewer {
|
||||
ui.send_message(WindowMessage::title(
|
||||
self.window,
|
||||
MessageDirection::ToWidget,
|
||||
WindowTitle::Text(title),
|
||||
WindowTitle::text(title),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -191,6 +191,7 @@ pub struct Animation<T: EntityId> {
|
||||
#[reflect(hidden)]
|
||||
#[visit(skip)]
|
||||
events: VecDeque<AnimationEvent>,
|
||||
#[visit(optional)]
|
||||
max_event_capacity: usize,
|
||||
}
|
||||
|
||||
|
||||
@@ -168,6 +168,7 @@ where
|
||||
#[reflect(hidden)]
|
||||
#[visit(skip)]
|
||||
events: VecDeque<Event>,
|
||||
#[visit(optional)]
|
||||
max_event_capacity: usize,
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,6 @@ impl DeferredLightRenderer {
|
||||
TriangleDefinition([20, 21, 22]),
|
||||
TriangleDefinition([20, 22, 23]),
|
||||
]),
|
||||
true,
|
||||
),
|
||||
GeometryBufferKind::StaticDraw,
|
||||
state,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ pub mod rigidbody;
|
||||
pub mod sound;
|
||||
pub mod sprite;
|
||||
pub mod terrain;
|
||||
pub mod tilemap;
|
||||
pub mod transform;
|
||||
|
||||
use crate::{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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>(
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user