Skip to content

Commit 4c1ff8f

Browse files
committed
toggle visibility per section
1 parent 4aee7f5 commit 4c1ff8f

7 files changed

Lines changed: 130 additions & 27 deletions

File tree

Editor/Widgets/MaterialEditorWidget.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,15 @@ protected override void DrawContents(EditorManager editor)
7373
for (var slot = 0; slot < materials.Length; slot++)
7474
{
7575
if (materials[slot] is not { } section) continue;
76-
DrawMaterial(section, slot, selectionChanged, section.SectionId == selected?.SectionId);
76+
DrawMaterial(primitive, section, slot, selectionChanged, section.SectionId == selected?.SectionId);
7777
}
7878
}
7979

80-
private void DrawMaterial(MaterialSection section, int slot, bool selectionChanged, bool isSelected)
80+
private void DrawMaterial(IPrimitiveComponent primitive, MaterialSection section, int slot, bool selectionChanged, bool isSelected)
8181
{
8282
if (selectionChanged) ImGui.SetNextItemOpen(isSelected, ImGuiCond.Always);
8383
var open = ImGui.CollapsingHeader($"{slot}: {section.MaterialDataContainer?.Name ?? Settings.NoName}###Material{slot}", ImGuiTreeNodeFlags.AllowOverlap);
84-
GetHeaderButtons(section, slot).Draw(ImGui.GetItemRectMin(), ImGui.GetItemRectSize());
84+
GetHeaderButtons(primitive, section, slot).Draw(ImGui.GetItemRectMin(), ImGui.GetItemRectSize());
8585

8686
if (selectionChanged && isSelected) ImGui.SetScrollHereY(0.5f);
8787
if (!open) return;
@@ -110,11 +110,14 @@ private void DrawMaterial(MaterialSection section, int slot, bool selectionChang
110110
ImGui.PopID();
111111
}
112112

113-
private HeaderButtons GetHeaderButtons(MaterialSection section, int slot)
113+
private HeaderButtons GetHeaderButtons(IPrimitiveComponent primitive, MaterialSection section, int slot)
114114
{
115115
if (_headerButtons.TryGetValue(section.SectionId, out var existing)) return existing;
116116

117117
var buttons = new HeaderButtons($"Material{slot}")
118+
.Add(() => section.IsVisible ? Settings.EyeIcon : Settings.EyeSlashIcon, () => "Toggle Visibility\nHide every section drawn with this material",
119+
() => primitive.SetMaterialVisibility(section.Index, !section.IsVisible), null,
120+
() => section.IsVisible ? null : Settings.RedColor)
118121
.Add(() => Settings.RightLeftIcon, () => "Swap\nPick another material the scene has already loaded",
119122
() => MaterialPickerModal.Instance.Open(entry => SwapMaterial(section, entry.Key)),
120123
() => section.MaterialDataContainer is MaterialDataContainer)

Snooper/Core/Containers/Resources/IndirectResources.cs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@ public ResourcesMetadata Add(PrimitiveComponent<TVertex, TInstanceData, TPerMate
7979
const uint currentLod = 0u;
8080
var drawAllocations = new DrawBufferAllocation[descriptor.Lods[currentLod].Sections.Length];
8181

82-
var instanceCount = component.IsVisible ? (uint)instanceAllocation.Length : 0;
8382
var bufferType = component.IsOpaque ? CommandBufferType.Opaque : CommandBufferType.Transparent;
8483
var buffer = _commands.GetBuffer(bufferType);
8584
for (var i = 0u; i < drawAllocations.Length; i++)
8685
{
8786
var section = descriptor.Lods[currentLod].Sections[i];
87+
var instanceCount = component.IsMaterialVisible(section.MaterialIndex) ? (uint)instanceAllocation.Length : 0u;
8888
var command = new DrawElementsIndirectCommand(section, instanceCount, geometryHandle, (uint)instanceAllocation.StartIndex);
8989
var draw = new PerDrawStatic(
9090
geometryHandle,
@@ -119,11 +119,29 @@ public void Update(PrimitiveComponent<TVertex, TInstanceData, TPerMaterialData>
119119
component.MarkClean(DirtyFlags.Opacity);
120120
}
121121

122+
if (component.IsDirty(DirtyFlags.Visibility))
123+
{
124+
foreach (var draw in metadata.DrawAllocations)
125+
{
126+
var data = component.IsMaterialVisible(draw.MaterialIndex) ? (uint)metadata.InstanceAllocation.Length : 0u;
127+
var buffer = _commands.GetBuffer(draw.BufferType);
128+
buffer.Commands.UpdateCustom(draw.Allocation.Command, data, DrawElementsIndirectCommand.InstanceCountOffset);
129+
buffer.StaticData.UpdateCustom(draw.Allocation.Static, data, PerDrawStatic.OriginalInstanceCountOffset);
130+
}
131+
132+
component.MarkClean(DirtyFlags.Visibility);
133+
}
134+
122135
if (component.IsDirty(DirtyFlags.Outline))
123136
{
124137
if (component.IsOutlined)
138+
{
125139
foreach (var draw in metadata.DrawAllocations)
140+
{
141+
if (!component.IsMaterialVisible(draw.MaterialIndex)) continue;
126142
_commands.Transfer(draw.Allocation, draw.BufferType, CommandBufferType.Mask);
143+
}
144+
}
127145

128146
component.MarkClean(DirtyFlags.Outline);
129147
}
@@ -139,19 +157,6 @@ public void Update(PrimitiveComponent<TVertex, TInstanceData, TPerMaterialData>
139157
_instanceData.QueueUpdate(metadata.InstanceAllocation, component.GetPerInstanceData());
140158
component.MarkClean(DirtyFlags.InstanceData);
141159
}
142-
143-
if (component.IsDirty(DirtyFlags.Visibility))
144-
{
145-
var originalInstanceCount = component.IsVisible ? (uint)metadata.InstanceAllocation.Length : 0u;
146-
foreach (var draw in metadata.DrawAllocations)
147-
{
148-
var buffer = _commands.GetBuffer(draw.BufferType);
149-
buffer.Commands.UpdateCustom(draw.Allocation.Command, originalInstanceCount, DrawElementsIndirectCommand.InstanceCountOffset);
150-
buffer.StaticData.UpdateCustom(draw.Allocation.Static, originalInstanceCount, PerDrawStatic.OriginalInstanceCountOffset);
151-
}
152-
153-
component.MarkClean(DirtyFlags.Visibility);
154-
}
155160
}
156161

157162
public void Update(MaterialSection material)

Snooper/Rendering/Components/Descriptors/MaterialSection.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44

55
namespace Snooper.Rendering.Components.Descriptors;
66

7-
public class MaterialSection(uint index)
7+
public class MaterialSection(uint index, bool isVisible = true)
88
{
99
private static int _nextId;
1010
public readonly int SectionId = Interlocked.Increment(ref _nextId);
1111

1212
public readonly uint Index = index;
1313

1414
public BufferAllocation? Allocation { get; internal set; } // set when added to the material data buffer
15+
public bool IsVisible { get; internal set; } = isVisible;
1516

1617
internal string? CacheKey
1718
{

Snooper/Rendering/Components/Mesh/MeshComponent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ protected override void BeginPlay(ActorManager scene)
131131
for (var i = 0u; i < _materials.Length; i++)
132132
{
133133
var index = i;
134-
var section = new MaterialSection(index);
134+
var section = new MaterialSection(index, IsVisible);
135135
Materials[index] = section;
136136

137137
scene.ThreadManager.Enqueue(() =>

Snooper/Rendering/Components/Primitive/PrimitiveComponent.cs

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public interface IPrimitiveComponent
2323
public bool IsOpaque { get; }
2424
public bool IsVisible { get; set; }
2525

26+
public void SetMaterialVisibility(uint materialIndex, bool visible);
27+
2628
internal MaterialSection? SelectedMaterial { get; }
2729
}
2830

@@ -64,7 +66,7 @@ public bool IsVisible
6466
if (field == value) return;
6567

6668
field = value;
67-
MarkDirty(DirtyFlags.Visibility);
69+
SetMaterialsVisible(value);
6870
}
6971
} = true;
7072

@@ -213,6 +215,30 @@ public void SnapToGround()
213215
SetLocalTransform(transform);
214216
}
215217

218+
public bool IsMaterialVisible(uint materialIndex) => materialIndex >= Materials.Length || Materials[materialIndex] is not { IsVisible: false };
219+
public void SetMaterialVisibility(uint materialIndex, bool visible)
220+
{
221+
if (materialIndex >= Materials.Length || Materials[materialIndex] is not { } material) return;
222+
if (material.IsVisible == visible) return;
223+
224+
material.IsVisible = visible;
225+
MarkVisibilityDirty();
226+
}
227+
private void SetMaterialsVisible(bool visible)
228+
{
229+
if (Materials is { } materials)
230+
{
231+
foreach (var material in materials)
232+
{
233+
material?.IsVisible = visible;
234+
}
235+
}
236+
237+
MarkVisibilityDirty();
238+
}
239+
240+
private void MarkVisibilityDirty() => MarkDirty(DirtyFlags.Visibility | (IsOutlined ? DirtyFlags.Outline : DirtyFlags.None));
241+
216242
protected override void BeginPlay(ActorManager scene)
217243
{
218244
base.BeginPlay(scene);
@@ -224,12 +250,21 @@ protected override void BeginPlay(ActorManager scene)
224250

225251
private const string HeaderLabel = "Mesh";
226252
private HeaderButtons HeaderButtons => field ??= new HeaderButtons(HeaderLabel)
227-
.Add(() => IsVisible ? Settings.EyeIcon : Settings.EyeSlashIcon, () => "Toggle Visibility",
253+
.Add(() => MaterialVisibilityIcon, () => "Toggle Visibility",
228254
() => { IsVisible = !IsVisible; }, null,
229-
() => IsVisible ? null : Settings.RedColor)
255+
() => MaterialVisibilityColor)
230256
.Add("\uf0c5", "Copy Path", () => ImGui.SetClipboardText(Descriptor.Path))
231257
.Add("\uf05a", "Primitive Info", () => ImGui.OpenPopup("##PrimitiveInfo"));
232258

259+
private PropertyToggleButton[] SectionButtons => field ??=
260+
[
261+
new PropertyToggleButton(
262+
() => MaterialVisibilityIcon,
263+
() => ImGui.OpenPopup(SectionVisibilityPopup),
264+
() => "Section Visibility",
265+
textColor: () => MaterialVisibilityColor)
266+
];
267+
233268
private PropertyToggleButton[] MaterialButtons => field ??=
234269
[
235270
new PropertyToggleButton(
@@ -319,7 +354,8 @@ public override void DrawControls()
319354
EditorUI.Text("Draw Distance", $"Min: {DrawDistance.X}, Max: {DrawDistance.Y}");
320355
}
321356

322-
EditorUI.Property($"Sections ({lod.Sections.Length})");
357+
EditorUI.PropertyWithToggle($"Sections ({lod.Sections.Length})", SectionButtons);
358+
DrawSectionVisibilityPopup(lod);
323359
var selected = lod.Sections[_sectionIndex];
324360
_materialIndex = (int) selected.MaterialIndex;
325361

@@ -342,7 +378,7 @@ public override void DrawControls()
342378
ImGui.EndCombo();
343379
}
344380

345-
EditorUI.Caption($"Material {selected.MaterialIndex}, {(selected.CastShadow && CastShadow ? "casts shadow" : "no shadow")}");
381+
EditorUI.Caption($"Material {selected.MaterialIndex}, {(selected.CastShadow && CastShadow ? "casts shadow" : "no shadow")}{(SelectedMaterial is { IsVisible: false } ? ", hidden" : "")}");
346382
ImGui.Spacing();
347383
ImGui.EndGroup();
348384

@@ -368,6 +404,64 @@ public override void DrawControls()
368404
});
369405
}
370406

407+
private const string SectionVisibilityPopup = "##SectionVisibility";
408+
private bool HasHiddenMaterial => Array.Exists(Materials, x => x is { IsVisible: false });
409+
private bool HasVisibleMaterial => Array.Exists(Materials, x => x is not { IsVisible: false });
410+
private string MaterialVisibilityIcon => HasVisibleMaterial ? HasHiddenMaterial ? Settings.EyeLowVisionIcon : Settings.EyeIcon : Settings.EyeSlashIcon;
411+
private Vector4? MaterialVisibilityColor => HasVisibleMaterial ? HasHiddenMaterial ? Settings.OrangeColor : null : Settings.RedColor;
412+
413+
private void DrawSectionVisibilityPopup(LodDescriptor<TVertex> lod)
414+
{
415+
if (!ImGui.BeginPopup(SectionVisibilityPopup)) return;
416+
417+
var sections = lod.Sections;
418+
var frameHeight = ImGui.GetFrameHeight();
419+
420+
var nameWidth = 0f;
421+
for (var i = 0; i < sections.Length; i++)
422+
{
423+
nameWidth = MathF.Max(nameWidth, ImGui.CalcTextSize($"{i}: {sections[i].Name}").X);
424+
}
425+
426+
var style = ImGui.GetStyle();
427+
var width = Math.Clamp(frameHeight + style.ItemSpacing.X + nameWidth + style.ScrollbarSize, frameHeight * 8f, frameHeight * 16f);
428+
429+
var anyVisible = HasVisibleMaterial;
430+
if (EditorUI.IconButton(MaterialVisibilityIcon, anyVisible ? "Hide All" : "Show All", textColor: MaterialVisibilityColor))
431+
{
432+
SetMaterialsVisible(!anyVisible);
433+
}
434+
ImGui.SameLine();
435+
ImGui.AlignTextToFramePadding();
436+
ImGui.TextDisabled($"{sections.Length} Section{(sections.Length != 1 ? "s" : "")}");
437+
ImGui.Separator();
438+
439+
var height = Math.Min(sections.Length, 5) * ImGui.GetFrameHeightWithSpacing();
440+
if (ImGui.BeginChild("##SectionList", new Vector2(width, height)))
441+
{
442+
for (var i = 0; i < sections.Length; i++)
443+
{
444+
var section = sections[i];
445+
var material = section.MaterialIndex < Materials.Length ? Materials[section.MaterialIndex] : null;
446+
var isVisible = material is not { IsVisible: false };
447+
448+
ImGui.PushID(i);
449+
if (EditorUI.IconButton(isVisible ? Settings.EyeIcon : Settings.EyeSlashIcon, null, material != null, isVisible ? null : Settings.RedColor) && material != null)
450+
{
451+
SetMaterialVisibility(section.MaterialIndex, !isVisible);
452+
}
453+
ImGui.SameLine();
454+
ImGui.AlignTextToFramePadding();
455+
if (isVisible) ImGui.TextUnformatted($"{i}: {section.Name}");
456+
else ImGui.TextDisabled($"{i}: {section.Name}");
457+
ImGui.PopID();
458+
}
459+
}
460+
ImGui.EndChild();
461+
462+
ImGui.EndPopup();
463+
}
464+
371465
private void DrawInfoPopup()
372466
{
373467
var viewport = ImGui.GetMainViewport();

Snooper/Settings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public static class Settings
6363
public const string PowerOffIcon = "\uf011";
6464
public const string PaletteIcon = "\uf53f";
6565
public const string EyeIcon = "\uf06e";
66+
public const string EyeLowVisionIcon = "\uf2a8";
6667
public const string CameraIcon = "\uf030";
6768
public const string BookIcon = "\uf02d";
6869
public const string KeyboardIcon = "\uf11c";

Snooper/UI/EditorUI.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ public static void PropertyWithToggle(string label, params PropertyToggleButton[
214214
ImGui.TableNextRow();
215215
ImGui.TableSetColumnIndex(0);
216216
ImGui.AlignTextToFramePadding();
217+
ImGui.TextUnformatted(label);
217218

218219
var visibleCount = 0;
219220
foreach (var button in buttons)
@@ -228,8 +229,6 @@ public static void PropertyWithToggle(string label, params PropertyToggleButton[
228229
var labelEndX = startX + ImGui.CalcTextSize(label).X + style.ItemSpacing.X;
229230
var firstBtnX = startX + colW - frameHeight * visibleCount;
230231

231-
ImGui.TextUnformatted(label);
232-
233232
PushIconButtonStyle();
234233

235234
var slot = 0;

0 commit comments

Comments
 (0)