Add brush face controls
Some checks are pending
CI / Format, lint, test, build (push) Waiting to run
Some checks are pending
CI / Format, lint, test, build (push) Waiting to run
This commit is contained in:
parent
02beeb7085
commit
d847706b8f
@ -33,6 +33,7 @@ use crate::ui::theme::{
|
|||||||
WIDGET_BG,
|
WIDGET_BG,
|
||||||
};
|
};
|
||||||
use crate::ui::widgets::{icon_button_small, phosphor_icon, phosphor_icon_text};
|
use crate::ui::widgets::{icon_button_small, phosphor_icon, phosphor_icon_text};
|
||||||
|
use crate::viewport::brush_edit::{BrushElementKey, BrushElementSelection};
|
||||||
|
|
||||||
use super::component_registry::{
|
use super::component_registry::{
|
||||||
EditorComponentCategory, EditorComponentDescriptor, EditorComponentRegistry,
|
EditorComponentCategory, EditorComponentDescriptor, EditorComponentRegistry,
|
||||||
@ -2440,6 +2441,7 @@ fn brush_editor_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) {
|
|||||||
changed |= ui.checkbox(&mut brush.receive_shadows, "Receive").changed();
|
changed |= ui.checkbox(&mut brush.receive_shadows, "Receive").changed();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
changed |= selected_brush_face_controls(world, ui, entity, &mut brush);
|
||||||
if ui.button("Reset Cube Brush").clicked() {
|
if ui.button("Reset Cube Brush").clicked() {
|
||||||
reset_cube = true;
|
reset_cube = true;
|
||||||
}
|
}
|
||||||
@ -2455,6 +2457,117 @@ fn brush_editor_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn selected_brush_face_controls(
|
||||||
|
world: &World,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
entity: Entity,
|
||||||
|
brush: &mut BrushDesc,
|
||||||
|
) -> bool {
|
||||||
|
let Some(selection) = world.get_resource::<BrushElementSelection>() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if selection.brush != Some(entity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let selected_faces: Vec<_> = selection
|
||||||
|
.elements
|
||||||
|
.iter()
|
||||||
|
.filter_map(|element| match element {
|
||||||
|
BrushElementKey::Face { face } => Some(face.clone()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if selected_faces.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut changed = false;
|
||||||
|
ui.add_space(6.0);
|
||||||
|
ui.separator();
|
||||||
|
ui.label(
|
||||||
|
egui::RichText::new(format!("{} selected face(s)", selected_faces.len())).color(TEXT_DIM),
|
||||||
|
);
|
||||||
|
|
||||||
|
for face_id in selected_faces {
|
||||||
|
let Some(face) = brush.faces.iter_mut().find(|face| face.id == face_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let label = if face.id.0.trim().is_empty() {
|
||||||
|
"Face".to_string()
|
||||||
|
} else {
|
||||||
|
face.id.0.clone()
|
||||||
|
};
|
||||||
|
egui::CollapsingHeader::new(label)
|
||||||
|
.default_open(true)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
property_row(ui, "UV offset", |ui| {
|
||||||
|
changed |= ui
|
||||||
|
.add(egui::DragValue::new(&mut face.uv_offset.x).speed(0.05))
|
||||||
|
.changed();
|
||||||
|
changed |= ui
|
||||||
|
.add(egui::DragValue::new(&mut face.uv_offset.y).speed(0.05))
|
||||||
|
.changed();
|
||||||
|
});
|
||||||
|
property_row(ui, "UV scale", |ui| {
|
||||||
|
changed |= ui
|
||||||
|
.add(egui::DragValue::new(&mut face.uv_scale.x).speed(0.05))
|
||||||
|
.changed();
|
||||||
|
changed |= ui
|
||||||
|
.add(egui::DragValue::new(&mut face.uv_scale.y).speed(0.05))
|
||||||
|
.changed();
|
||||||
|
});
|
||||||
|
property_row(ui, "UV rotation", |ui| {
|
||||||
|
changed |= ui
|
||||||
|
.add(egui::DragValue::new(&mut face.uv_rotation).speed(1.0))
|
||||||
|
.changed();
|
||||||
|
});
|
||||||
|
changed |= editor_asset_ref_option_ui(ui, "Material", &mut face.material);
|
||||||
|
changed |= editor_asset_ref_option_ui(ui, "Texture", &mut face.texture);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
changed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn editor_asset_ref_option_ui(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
label: &str,
|
||||||
|
value: &mut Option<EditorAssetRef>,
|
||||||
|
) -> bool {
|
||||||
|
let mut changed = false;
|
||||||
|
property_row(ui, label, |ui| {
|
||||||
|
let mut enabled = value.is_some();
|
||||||
|
if ui.checkbox(&mut enabled, "").changed() {
|
||||||
|
if enabled {
|
||||||
|
*value = Some(EditorAssetRef::default());
|
||||||
|
} else {
|
||||||
|
*value = None;
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if let Some(reference) = value.as_mut() {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.set_min_width(MIN_INLINE_CONTROL_WIDTH);
|
||||||
|
changed |= ui
|
||||||
|
.text_edit_singleline(&mut reference.label)
|
||||||
|
.on_hover_text("Cached display label")
|
||||||
|
.changed();
|
||||||
|
changed |= ui
|
||||||
|
.text_edit_singleline(&mut reference.asset_id)
|
||||||
|
.on_hover_text("Asset registry ID")
|
||||||
|
.changed();
|
||||||
|
changed |= ui
|
||||||
|
.text_edit_singleline(&mut reference.sub_asset_id)
|
||||||
|
.on_hover_text("Imported sub-asset ID")
|
||||||
|
.changed();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ui.label(egui::RichText::new("(none)").color(TEXT_DIM));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
changed
|
||||||
|
}
|
||||||
|
|
||||||
fn primitive_editor_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) {
|
fn primitive_editor_ui(world: &mut World, ui: &mut egui::Ui, entity: Entity) {
|
||||||
let Some(mut primitive) = world.get::<Primitive>(entity).cloned() else {
|
let Some(mut primitive) = world.get::<Primitive>(entity).cloned() else {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -17,6 +17,8 @@ With a brush actor selected, `1`/`2`/`3`/`4` enter vertex, edge, face, and clip
|
|||||||
|
|
||||||
Vertex, edge, and face selections can be dragged on the viewport floor plane. The editor moves matching duplicated corner vertices across all brush faces so simple cube/prism brushes stay welded, recomputes face planes, and commits one undoable brush edit when the drag releases. Clip mode currently provides element visualization/selection only; split application is future CSG work.
|
Vertex, edge, and face selections can be dragged on the viewport floor plane. The editor moves matching duplicated corner vertices across all brush faces so simple cube/prism brushes stay welded, recomputes face planes, and commits one undoable brush edit when the drag releases. Clip mode currently provides element visualization/selection only; split application is future CSG work.
|
||||||
|
|
||||||
|
When one or more faces are selected in Face mode, the Brush inspector shows selected-face controls for UV offset, UV scale, UV rotation, material ref, and texture ref. These fields edit the persisted `BrushFaceDesc` data through undoable brush updates. The current controls are data-oriented; Asset Browser picker/drop polish and per-face material batching in hydration are still follow-up work.
|
||||||
|
|
||||||
## Boolean Operations
|
## Boolean Operations
|
||||||
|
|
||||||
The command palette exposes `brush.subtract`, `brush.intersect`, and `brush.merge_convex` for selected brush actors. The current implementation is a conservative bounds-based MVP intended for cuboid/prism blockout brushes:
|
The command palette exposes `brush.subtract`, `brush.intersect`, and `brush.merge_convex` for selected brush actors. The current implementation is a conservative bounds-based MVP intended for cuboid/prism blockout brushes:
|
||||||
@ -32,3 +34,4 @@ These operations commit through history but are not full arbitrary-face CSG yet.
|
|||||||
- Only convex authored faces are supported by the mesh builder.
|
- Only convex authored faces are supported by the mesh builder.
|
||||||
- Subtractive brush markers are stored but not automatically evaluated.
|
- Subtractive brush markers are stored but not automatically evaluated.
|
||||||
- Clip split application and arbitrary-face CSG remain future roadmap work.
|
- Clip split application and arbitrary-face CSG remain future roadmap work.
|
||||||
|
- Per-face material and texture refs persist, but generated brush meshes still hydrate through a single material batch.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user