diff --git a/SeeSharp.Blazor/FlipViewer.razor b/SeeSharp.Blazor/FlipViewer.razor index f2f2294e..00df6534 100644 --- a/SeeSharp.Blazor/FlipViewer.razor +++ b/SeeSharp.Blazor/FlipViewer.razor @@ -19,13 +19,28 @@ ( int X, int Y, - bool CtrlKey + + string key, + bool isPressed, + + int mouseButton, + bool isButton, + + int deltaY, + int selectedIndex, + string registryKey ) { } [Parameter] public EventCallback OnClick { get; set; } + [Parameter] + public EventCallback OnWheel { get; set; } + [Parameter] + public EventCallback OnMouseOver { get; set; } + [Parameter] + public EventCallback OnKey { get; set; } protected override async Task OnParametersSetAsync() { @@ -54,13 +69,38 @@ public struct _OnFlipClickArgs { - [JsonInclude] public bool ctrlKey; + [JsonInclude] public string key; + [JsonInclude] public bool isPressed; + [JsonInclude] public int mouseButtom; + [JsonInclude] public bool isButtom; + [JsonInclude] public int deltaY; + [JsonInclude] public int selectedIndex; + [JsonInclude] public string registryKey; + } + + [JSInvokable] + public void _OnFlipClick(int buttom) + { + OnClick.InvokeAsync(new(X: -1, Y: -1, key: "", isPressed: false, mouseButton: buttom, isButton: false, deltaY: 0, selectedIndex: -1, registryKey: "")).Wait(); + } + + // IMPORTANT: There is a bool for altKey, but on the React side a wheel event only is fired when alt is pressed, so altKey is more or less redundant + [JSInvokable] + public void _OnFlipWheel(int deltaY) + { + OnWheel.InvokeAsync(new(X: -1, Y: -1, key: "", isPressed: false, mouseButton: -1, isButton: false, deltaY: deltaY, selectedIndex: -1, registryKey: "")).Wait(); + } + + [JSInvokable] + public void _OnFlipMouseOver(int x, int y) + { + OnMouseOver.InvokeAsync(new(X: x, Y: y, key: "", isPressed: false, mouseButton: -1, isButton: false, deltaY: 0, selectedIndex: -1, registryKey: "")).Wait(); } [JSInvokable] - public void _OnFlipClick(int x, int y, _OnFlipClickArgs eventArgs) + public void _OnFlipKey(int selectedIdx, string keyStr, string keyPressed, bool isPressed) { - OnClick.InvokeAsync(new(x, y, eventArgs.ctrlKey)).Wait(); + OnKey.InvokeAsync(new(X: -1, Y: -1, key: keyPressed, isPressed: isPressed, mouseButton: -1, isButton: false, deltaY: 0, selectedIndex: selectedIdx, registryKey: keyStr)).Wait(); } protected override async Task OnAfterRenderAsync(bool firstRender) @@ -68,7 +108,18 @@ // Need to wait with invoking the JS code until the HTML got added to the DOM on the client side if (flipJson != null) { - await JSRuntime.InvokeVoidAsync("makeFlipBook", flipJson, DotNetObjectReference.Create(this), nameof(_OnFlipClick)); + await JSRuntime.InvokeVoidAsync( + "makeFlipBook", + flipJson, + DotNetObjectReference.Create(this), + nameof(_OnFlipClick), + DotNetObjectReference.Create(this), + nameof(_OnFlipWheel), + DotNetObjectReference.Create(this), + nameof(_OnFlipMouseOver), + DotNetObjectReference.Create(this), + nameof(_OnFlipKey) + ); flipJson = null; } } diff --git a/SeeSharp.Blazor/Scripts.cs b/SeeSharp.Blazor/Scripts.cs index 1abe35d9..c58fda48 100644 --- a/SeeSharp.Blazor/Scripts.cs +++ b/SeeSharp.Blazor/Scripts.cs @@ -1,12 +1,12 @@ +using Microsoft.AspNetCore.StaticAssets; using Microsoft.JSInterop; +using System.Net.Http.Headers; using System.Reflection; namespace SeeSharp.Blazor; -public static class Scripts -{ - static string ReadResourceText(string filename) - { +public static class Scripts { + static string ReadResourceText(string filename) { var assembly = typeof(Scripts).GetTypeInfo().Assembly; var stream = assembly.GetManifestResourceStream("SeeSharp.Blazor." + filename) ?? throw new FileNotFoundException("resource file not found", filename); @@ -20,14 +20,32 @@ static string ReadResourceText(string filename) $$""" """; @@ -59,7 +77,17 @@ function makeFlipBook(jsonArgs, onClickObj, onClickMethodName) { """; - public static readonly string AllScripts = FlipBookScript + DownloadScript + WidgetScripts; + public static readonly string UpdateImageScript = + $$""" + + """; + + public static readonly string AllScripts = FlipBookScript + DownloadScript + WidgetScripts + UpdateImageScript; /// /// Downloads a stream to the client with the given file name. Requires that diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/BaseExperiment.razor.cs b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/BaseExperiment.razor.cs new file mode 100644 index 00000000..aaaa4f70 --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/BaseExperiment.razor.cs @@ -0,0 +1,168 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using SeeSharp.Blazor; + +namespace SeeSharp.Blazor.Template.Pages; + +public struct ListenerState { + public ListenerState() { } + + /// + /// The index of the selected image of the current selected flipbook (selected by clicking on it) + /// + public int selectedIndex = 0; + + /// + /// Number between 0 and NumSamples. Can be used if data is stored from different iterations + /// + public int currIteration = 0; + + // Add here all action keys for your functionalities + // Attention: any key press disables the default html scrolling! + public string actionKey1 = "a"; + public string actionKey2 = "d"; + public bool actionKey1Pressed = false; + public bool actionKey2Pressed = false; + + public int currX = 0; + public int currY = 0; + + /// + /// The key of the current flipbook + /// + public string currFlipKey = ""; +} + +/// +/// Differences between event type so update methods for flipbooks can ignore events +/// +public enum FiredType { + Click = 0, + Move = 1, + Wheel = 2, + KeyDown = 4, + KeyUp = 8, +} + +public partial class BaseExperiment : ComponentBase { + protected const int Width = 1280; + protected const int Height = 720; + protected const int FlipWidth = 660; + protected const int FlipHeight = 580; + protected const int MaxDepth = 10; + public int NumSamples = 2; + + /// + /// Registers all Flipbooks + /// Key will be the stringified key of a Flipbook which is set by Flipbook.SetKey + /// A Flipbook key is an array of ints + /// + protected Dictionary registry = new Dictionary(); + protected ListenerState state = new ListenerState(); + public static string Reverse(string s) { + char[] charArray = s.ToCharArray(); + Array.Reverse(charArray); + return new string(charArray); + } + + // SurfacePoint? selected; + // void OnFlipClick(FlipViewer.OnClickEventArgs args) { + // if (args.CtrlKey) { + // selected = scene.RayCast(new(args.X, args.Y)); + // } + // } + + /// + /// Is fired when clicked on an image in the flipbook + /// + /// ListenerState from HMTL side + public virtual void OnFlipClick(FlipViewer.OnClickEventArgs args) { + updateFlipbook(FiredType.Click); + } + + /// + /// Is fired when the mouse wheel state changes over an image. + /// This gets only called when the alt key is pressed (from HTML side) + /// + /// ListenerState from HMTL side. + public virtual void OnFlipWheel(FlipViewer.OnClickEventArgs args) { + if (state.actionKey1Pressed) { + // scrolled up + if (args.deltaY < 0) { + if (state.currIteration < NumSamples - 1) { + state.currIteration++; + updateFlipbook(FiredType.Wheel); + } + } + // scrolled down + if (args.deltaY >= 0) { + if (state.currIteration > 0) { + state.currIteration--; + updateFlipbook(FiredType.Wheel); + } + } + } + } + + /// + /// Is fired when mouse position changes over the selected flipbook + /// + /// ListenerState from HMTL side. + public virtual void OnFlipMouseOver(FlipViewer.OnClickEventArgs args) { + if (state.currX == args.X && state.currY == args.Y) + return; + + if (args.X >= 0 && args.X <= Width - 1) + state.currX = args.X; + if (args.Y >= 0 && args.Y <= Height - 1) + state.currY = args.Y; + + updateFlipbook(FiredType.Move); + } + + /// + /// Is fired when key is pressed or released + /// + /// ListenerState from HMTL side. + public virtual void OnFlipKey(FlipViewer.OnClickEventArgs args) { + if (args.key == state.actionKey1) + state.actionKey1Pressed = args.isPressed; + + if (args.key == state.actionKey2) + state.actionKey2Pressed = args.isPressed; + + state.currFlipKey = args.registryKey; + state.selectedIndex = args.selectedIndex; + + if (args.key == state.actionKey1 && !args.isPressed) + state.currIteration = 0; + + if (args.isPressed) + updateFlipbook(FiredType.KeyDown); + else + updateFlipbook(FiredType.KeyUp); + } + + /// + /// Catches fired events and forward events to selected flipbooks + /// + /// Fired type + public virtual void updateFlipbook(FiredType fired) { + if (String.IsNullOrEmpty(state.currFlipKey)) + return; + } + + /// + /// Runs the experiment when "..." is pressed on the HTML + /// + public virtual void RunExperiment() { + } + + /// + /// Is executed when Download button is pressed on the HTML + /// + /// + public virtual async Task OnDownloadClick() { + } +} \ No newline at end of file diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor index 76543c49..3ed66a10 100644 --- a/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor @@ -4,6 +4,8 @@ @inject IJSRuntime JS +@inherits SeeSharp.Blazor.Template.Pages.BaseExperiment + @page "/Experiment"

Example experiment

@@ -33,7 +35,7 @@
- @if (selected.HasValue && selected.Value) + @* @if (selected.HasValue && selected.Value) { @@ -41,8 +43,8 @@
Mesh@(selected.Value.Mesh.Name)
Distance@(selected.Value.Distance)
Position@(selected.Value.Position)
- } - + } *@ + @* *@
} } @@ -63,12 +65,12 @@ bool resultsAvailable = false; ElementReference runButton; - SimpleImageIO.FlipBook flip; - async Task OnSceneLoaded(SceneFromFile sceneFromFile) { await Task.Run(() => scene = sceneFromFile.MakeScene()); + flip = null; + resultsAvailable = false; readyToRun = true; sceneJustLoaded = true; diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor.cs b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor.cs index 109cc8db..802e15c2 100644 --- a/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor.cs +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor.cs @@ -1,30 +1,35 @@ using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; using SeeSharp.Blazor; namespace SeeSharp.Blazor.Template.Pages; -public partial class Experiment : ComponentBase -{ - const int Width = 1280; - const int Height = 720; - const int MaxDepth = 10; - - int NumSamples = 2; +public partial class Experiment : BaseExperiment { + public SimpleImageIO.FlipBook flip; long renderTimePT, renderTimeVCM; - void RunExperiment() - { - flip = new FlipBook(660, 580) + //Methods + PathTracer pathTracer; + VertexConnectionAndMerging vcm; + + /// + /// Initializes all flipbooks + /// + void InitFlipbooks() { + flip = new FlipBook(FlipWidth, FlipHeight) .SetZoom(FlipBook.InitialZoom.FillWidth) .SetToneMapper(FlipBook.InitialTMO.Exposure(scene.RecommendedExposure)) .SetToolVisibility(false); + } + + public override void RunExperiment() { + InitFlipbooks(); scene.FrameBuffer = new(Width, Height, ""); scene.Prepare(); - PathTracer pathTracer = new() - { + pathTracer = new() { TotalSpp = NumSamples, MaxDepth = MaxDepth, }; @@ -33,8 +38,7 @@ void RunExperiment() renderTimePT = scene.FrameBuffer.RenderTimeMs; scene.FrameBuffer = new(Width, Height, ""); - VertexConnectionAndMerging vcm = new() - { + vcm = new() { NumIterations = NumSamples, MaxDepth = MaxDepth, }; @@ -43,18 +47,11 @@ void RunExperiment() renderTimeVCM = scene.FrameBuffer.RenderTimeMs; } - SurfacePoint? selected; - - void OnFlipClick(FlipViewer.OnClickEventArgs args) - { - if (args.CtrlKey) - { - selected = scene.RayCast(new(args.X, args.Y)); - } - } - - async Task OnDownloadClick() - { + /// + /// Safes HTML of experiment + /// + /// + public override async Task OnDownloadClick() { HtmlReport report = new(); report.AddMarkdown(""" # Example experiment diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Program.cs b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Program.cs index 01ab1f6f..9245b9cb 100644 --- a/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Program.cs +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Program.cs @@ -9,8 +9,11 @@ var app = builder.Build(); -if (!app.Environment.IsDevelopment()) -{ +// Register scene folder +var scenesPath = "Scenes"; +SceneRegistry.AddSource(scenesPath); + +if (!app.Environment.IsDevelopment()) { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.Template/wwwroot/css/site.css b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/wwwroot/css/site.css index ddc98cca..edac4442 100644 --- a/SeeSharp.Templates/content/SeeSharp.Blazor.Template/wwwroot/css/site.css +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/wwwroot/css/site.css @@ -59,6 +59,7 @@ button { flex-direction: column; float: left; margin-right: 1em; + height: 10000px; /*Sets height of parameter window on the left site. Only visual benefits*/ } .experiment-results { @@ -68,6 +69,14 @@ button { align-items: flex-start; } +.compare { + display: flex; + gap: 10px; + margin-bottom: 10px; + flex-wrap: wrap; + align-items: flex-start; +} + table { border-collapse: collapse; } diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/.template.config/template.json b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/.template.config/template.json new file mode 100644 index 00000000..c5ff81fd --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/.template.config/template.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "Pascal Grittmann", + "classifications": [ "Web/Blazor/SeeSharp" ], + "identity": "SeeSharp.Blazor.Template", + "name": "SeeSharp Blazor Experiment", + "shortName": "seesharp-blazor", + "sourceName":"SeeSharp.Blazor.Template", + "tags": { + "language": "C#", + "type": "project" + }, + "sources": [ + { + "modifiers": [ + { + "exclude": [ + "Results/**", + "Scenes/*/*.blend1" + ], + "copyOnly": [ + "Scenes/**" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/App.razor b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/App.razor new file mode 100644 index 00000000..6fd3ed1b --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Imports.cs b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Imports.cs new file mode 100644 index 00000000..b6c33e5a --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Imports.cs @@ -0,0 +1,32 @@ +global using System; +global using System.Collections.Concurrent; +global using System.Collections.Generic; +global using System.Diagnostics; +global using System.IO; +global using System.Linq; +global using System.Numerics; +global using System.Text.Json; +global using System.Text.Json.Serialization; +global using System.Threading; +global using System.Threading.Tasks; + +global using TinyEmbree; +global using SimpleImageIO; + +global using SeeSharp; +global using SeeSharp.Cameras; +global using SeeSharp.Common; +global using SeeSharp.Experiments; +global using SeeSharp.Geometry; +global using SeeSharp.Images; +global using SeeSharp.Integrators; +global using SeeSharp.Integrators.Bidir; +global using SeeSharp.Integrators.Common; +global using SeeSharp.Integrators.Util; +global using SeeSharp.Sampling; +global using SeeSharp.Shading; +global using SeeSharp.Shading.Background; +global using SeeSharp.Shading.Emitters; +global using SeeSharp.Shading.Materials; + +global using SeeSharp.Blazor; diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/MainLayout.razor b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/MainLayout.razor new file mode 100644 index 00000000..a5af3489 --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/MainLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +
@Body
diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/BaseExperiment.razor.cs b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/BaseExperiment.razor.cs new file mode 100644 index 00000000..750c1c64 --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/BaseExperiment.razor.cs @@ -0,0 +1,168 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using SeeSharp.Blazor; + +namespace SeeSharp.Blazor.Template.Pages; + +public struct ListenerState { + public ListenerState() { } + + /// + /// The index of the selected image of the current selected flipbook (selected by clicking on it) + /// + public int selectedIndex = 0; + + /// + /// Number between 0 and NumSamples. Can be used if data is stored from different iterations + /// + public int currIteration = 0; + + // Add here all action keys for your functionalities + // Attention: any key press disables the default html scrolling! + public string actionKey1 = "a"; + public string actionKey2 = "d"; + public bool actionKey1Pressed = false; + public bool actionKey2Pressed = false; + + public int currX = 0; + public int currY = 0; + + /// + /// The key of the current flipbook + /// + public string currFlipKey = ""; +} + +/// +/// Differences between event type so update methods for flipbooks can ignore events +/// +public enum FiredType { + Click = 0, + Move = 1, + Wheel = 2, + KeyDown = 4, + KeyUp = 8, +} + +public partial class BaseExperiment : ComponentBase { + protected const int Width = 1280; + protected const int Height = 720; + protected const int FlipWidth = 660; + protected const int FlipHeight = 580; + protected const int MaxDepth = 10; + public int NumSamples = 2; + + /// + /// Registers all Flipbooks + /// Key will be the stringified key of a Flipbook which is set by Flipbook.SetKey + /// A Flipbook key is an array of ints + /// + protected Dictionary registry = new Dictionary(); + protected ListenerState state = new ListenerState(); + public static string Reverse(string s) { + char[] charArray = s.ToCharArray(); + Array.Reverse(charArray); + return new string(charArray); + } + + // SurfacePoint? selected; + // void OnFlipClick(FlipViewer.OnClickEventArgs args) { + // if (args.CtrlKey) { + // selected = scene.RayCast(new(args.X, args.Y)); + // } + // } + + /// + /// Is fired when clicked on an image in the flipbook + /// + /// ListenerState from HMTL side + public virtual void OnFlipClick(FlipViewer.OnClickEventArgs args) { + updateFlipbook(FiredType.Click); + } + + /// + /// Is fired when the mouse wheel state changes over an image. + /// This gets only called when the alt key is pressed (from HTML side) + /// + /// ListenerState from HMTL side. + public virtual void OnFlipWheel(FlipViewer.OnClickEventArgs args) { + if (state.actionKey1Pressed) { + // scrolled up + if (args.deltaY < 0) { + if (state.currIteration < NumSamples - 1) { + state.currIteration++; + updateFlipbook(FiredType.Wheel); + } + } + // scrolled down + if (args.deltaY >= 0) { + if (state.currIteration > 0) { + state.currIteration--; + updateFlipbook(FiredType.Wheel); + } + } + } + } + + /// + /// Is fired when mouse position changes over the selected flipbook + /// + /// ListenerState from HMTL side. + public virtual void OnFlipMouseOver(FlipViewer.OnClickEventArgs args) { + if (state.currX == args.X && state.currY == args.Y) + return; + + if (args.X >= 0 && args.X <= Width - 1) + state.currX = args.X; + if (args.Y >= 0 && args.Y <= Height - 1) + state.currY = args.Y; + + updateFlipbook(FiredType.Move); + } + + /// + /// Is fired when key is pressed or released + /// + /// ListenerState from HMTL side. + public virtual void OnFlipKey(FlipViewer.OnClickEventArgs args) { + if (args.key == state.actionKey1) + state.actionKey1Pressed = args.isPressed; + + if (args.key == state.actionKey2) + state.actionKey2Pressed = args.isPressed; + + state.currFlipKey = args.registryKey; + state.selectedIndex = args.selectedIndex; + + if (args.key == state.actionKey1 && !args.isPressed) + state.currIteration = 0; + + if (args.isPressed) + updateFlipbook(FiredType.KeyDown); + else + updateFlipbook(FiredType.KeyUp); + } + + /// + /// Catches fired events and forward events to selected flipbooks + /// + /// Fired type + public virtual void updateFlipbook(FiredType fired) { + if (String.IsNullOrEmpty(state.currFlipKey)) + return; + } + + /// + /// Runs the experiment when "..." is pressed on the HTML + /// + public virtual void RunExperiment() { + } + + /// + /// Is executed when Download button is pressed on the HTML + /// + /// + public virtual async Task OnDownloadClick() { + } +} \ No newline at end of file diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/Experiment.razor b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/Experiment.razor new file mode 100644 index 00000000..85eedce1 --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/Experiment.razor @@ -0,0 +1,105 @@ +@using SeeSharp.Experiments +@using SeeSharp +@using SeeSharp.Blazor + +@inject IJSRuntime JS + +@inherits SeeSharp.Blazor.Template.Pages.BaseExperiment + +@page "/Experiment" + +

Example experiment

+ +
+
+ +
+ +
+ +
+
+ @if (readyToRun) + { +

+ } + + + +
+ + @if (!running) + { + @if (resultsAvailable) + { +
+ + + @* @if (selected.HasValue && selected.Value) + { + + + + + +
Mesh@(selected.Value.Mesh.Name)
Material@(selected.Value.Mesh.Material.Name) (roughness: @(selected.Value.Mesh.Material.GetRoughness(selected.Value)), transmissive: @(selected.Value.Mesh.Material.IsTransmissive(selected.Value)))
Distance@(selected.Value.Distance)
Position@(selected.Value.Position)
+ } *@ + @* *@ +
+

Compare Example (PT, VCM)

+
+ + +
+ } + } + else + { +

Rendering...

+ } +
+ + + +@code { + SceneSelector sceneSelector; + Scene scene; + bool readyToRun = false; + bool running = false; + bool sceneJustLoaded = false; + bool resultsAvailable = false; + ElementReference runButton; + + async Task OnSceneLoaded(SceneFromFile sceneFromFile) + { + await Task.Run(() => scene = sceneFromFile.MakeScene()); + + flip = null; + compare = (null, null); + + resultsAvailable = false; + readyToRun = true; + sceneJustLoaded = true; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (readyToRun && sceneJustLoaded) + { + await runButton.FocusAsync(); + } + + sceneJustLoaded = false; + } + + async Task OnRunClick() + { + readyToRun = false; + resultsAvailable = false; + running = true; + await Task.Run(() => RunExperiment()); + readyToRun = true; + running = false; + resultsAvailable = true; + } +} \ No newline at end of file diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/Experiment.razor.cs b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/Experiment.razor.cs new file mode 100644 index 00000000..494f9fb9 --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/Experiment.razor.cs @@ -0,0 +1,182 @@ +namespace SeeSharp.Blazor.Template.Pages; +using Microsoft.JSInterop; + +// Only here for the example to show some possibilities +struct ExampleImageGenerator { + public Image rndImage(float strength, int width, int height, bool colored) { + Image image = new Image(width, height, 3); + RNG rng = new RNG(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (colored) { + image.SetPixelChannel(x, y, 0, rng.NextFloat(0.5f - strength, 0.5f + strength)); + image.SetPixelChannel(x, y, 1, rng.NextFloat(0.5f - strength, 0.5f + strength)); + image.SetPixelChannel(x, y, 2, rng.NextFloat(0.5f - strength, 0.5f + strength)); + } else { + float value = rng.NextFloat(0.5f - strength, 0.5f + strength); + + image.SetPixelChannel(x, y, 0, value); + image.SetPixelChannel(x, y, 1, value); + image.SetPixelChannel(x, y, 2, value); + } + + } + } + + return image; + } +} + +public partial class Experiment : BaseExperiment { + // Define all flip books here + public SimpleImageIO.FlipBook flip; + public (SimpleImageIO.FlipBook, SimpleImageIO.FlipBook) compare; + + long renderTimePT, renderTimeVCM; + + //Methods + PathTracer pathTracer; + VertexConnectionAndMerging vcm; + ExampleImageGenerator imgGen; + + RgbImage ptImage; + RgbImage vcmImage; + + /// + /// Initializes all flipbooks + /// + void InitFlipbooks() { + // create new Flipbook w/o key + flip = new FlipBook(FlipWidth, FlipHeight) + .SetZoom(FlipBook.InitialZoom.FillWidth) + .SetToneMapper(FlipBook.InitialTMO.Exposure(scene.RecommendedExposure)) + .SetToolVisibility(false); + + // create Flipbooks with keys + compare = ( + new FlipBook(FlipWidth, FlipHeight) + .SetZoom(FlipBook.InitialZoom.FillWidth) + .SetToneMapper(FlipBook.InitialTMO.Exposure(scene.RecommendedExposure)) + .SetGroupName("compare") + .SetToolVisibility(false) + .SetID("1,0"), + new FlipBook(FlipWidth, FlipHeight) + .SetZoom(FlipBook.InitialZoom.FillWidth) + .SetToneMapper(FlipBook.InitialTMO.Exposure(scene.RecommendedExposure)) + .SetGroupName("compare") + .SetToolVisibility(false) + .SetID("0,1") + ); + registry.Add(compare.Item1.ID, compare.Item1); + registry.Add(compare.Item2.ID, compare.Item2); + } + + /// + /// Sets all intial images of flipbooks with extra functions (ex: compare) + /// + void FlipBookSetBaseImages() { + compare.Item1.Add($"PathTracer", ptImage); + compare.Item2.Add($"VCM", vcmImage); + } + + public override void RunExperiment() { + InitFlipbooks(); + + scene.FrameBuffer = new(Width, Height, ""); + scene.Prepare(); + + pathTracer = new() { + TotalSpp = NumSamples, + MaxDepth = MaxDepth, + }; + pathTracer.Render(scene); + flip.Add($"PT", scene.FrameBuffer.Image); + renderTimePT = scene.FrameBuffer.RenderTimeMs; + ptImage = scene.FrameBuffer.Image; + + scene.FrameBuffer = new(Width, Height, ""); + vcm = new() { + NumIterations = NumSamples, + MaxDepth = MaxDepth, + }; + vcm.Render(scene); + flip.Add($"VCM", scene.FrameBuffer.Image); + renderTimeVCM = scene.FrameBuffer.RenderTimeMs; + vcmImage = scene.FrameBuffer.Image; + + FlipBookSetBaseImages(); + } + + /// + /// Catches fired events and forward events to selected flipbooks + /// + /// Fired type + public override void updateFlipbook(FiredType fired) { + if (String.IsNullOrEmpty(state.currFlipKey)) + return; + + switch (state.currFlipKey) { + case "1,0": + case "0,1": { + updateCompare(fired); + break; + } + default: + break; + } + } + + /// + /// Example method that updates the flipbook pair. + /// When Alt key is pressed, the image change to random noise images + /// When Ctrl key is pressed, the colored and black/white images swap + /// + /// Fired type + /// + async Task updateCompare(FiredType fired) { + // Disable event types that you want to ignore + if (fired == FiredType.Click || fired == FiredType.Move) + return; + + FlipBook flipBook = registry[state.currFlipKey]; + // TODO: some iteration over all pairs with the same number ex: (1,0,0,...) -> (0,1,0,0,..) + // this is a fast solution (and maybe good enough) for now -> flip the string to get the other flipbook + FlipBook flipBookOther = registry[Reverse(state.currFlipKey)]; + + Image updateImage = flipBook.GetImage(state.selectedIndex); + Image updateImageOther = flipBookOther.GetImage(state.selectedIndex); + + if (state.actionKey1Pressed) { + bool colored = true; + + if (state.actionKey2Pressed) + colored = !colored; + + if (state.currIteration == 0) { + updateImage = imgGen.rndImage(0.2f, Width, Height, !colored); + updateImageOther = imgGen.rndImage(0.2f, Width, Height, colored); + } else if (state.currIteration == 1) { + updateImage = imgGen.rndImage(0.4f, Width, Height, !colored); + updateImageOther = imgGen.rndImage(0.4f, Width, Height, colored); + } + + FlipBook.GeneratedCode code = flipBook.ReplaceImage(updateImage, state.selectedIndex); + JS.InvokeVoidAsync("updateImage", code.Data); + code = flipBookOther.ReplaceImage(updateImageOther, state.selectedIndex); + JS.InvokeVoidAsync("updateImage", code.Data); + + Console.WriteLine("Compare updated"); + } else { + updateImage = ptImage; + updateImageOther = vcmImage; + + FlipBook.GeneratedCode code = flipBook.ReplaceImage(updateImage, state.selectedIndex); + JS.InvokeVoidAsync("updateImage", code.Data); + code = flipBookOther.ReplaceImage(updateImageOther, state.selectedIndex); + JS.InvokeVoidAsync("updateImage", code.Data); + + Console.WriteLine("Compare reset"); + } + } +} \ No newline at end of file diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/Index.razor b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/Index.razor new file mode 100644 index 00000000..59e43710 --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/Index.razor @@ -0,0 +1,39 @@ +@page "/" + +@using System.Reflection +@using System.Text.RegularExpressions + + +
+ +
+ + +@code { + /// Enumerates all .razor components in this folder + public IEnumerable<(string Name, string Url)> GetExperimentPages() + { + var routableComponents = Assembly + .GetExecutingAssembly() + .ExportedTypes + .Where(t => t.IsSubclassOf(typeof(ComponentBase))) + .Where(c => c + .GetCustomAttributes(inherit: true) + .OfType() + .Count() > 0); + + foreach (var routableComponent in routableComponents) + { + string name = routableComponent.ToString().Replace("SeeSharp.Blazor.Template.Pages.", string.Empty); + if (name != "Index") + yield return (name, name); + } + } +} diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/_Host.cshtml b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/_Host.cshtml new file mode 100644 index 00000000..b2f07105 --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Pages/_Host.cshtml @@ -0,0 +1,34 @@ +@page "/" +@using Microsoft.AspNetCore.Components.Web +@namespace SeeSharp.Blazor.Template.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + + + + + + @Html.Raw(SeeSharp.Blazor.Scripts.AllScripts) + + + + + +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Program.cs b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Program.cs new file mode 100644 index 00000000..9245b9cb --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/Program.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; + +ProgressBar.Silent = true; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); + +var app = builder.Build(); + +// Register scene folder +var scenesPath = "Scenes"; +SceneRegistry.AddSource(scenesPath); + +if (!app.Environment.IsDevelopment()) { + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseStaticFiles(); + +app.UseRouting(); + +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); + +app.Run(); \ No newline at end of file diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/SeeSharp.Blazor.Template.csproj b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/SeeSharp.Blazor.Template.csproj new file mode 100644 index 00000000..ecf433ba --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/SeeSharp.Blazor.Template.csproj @@ -0,0 +1,17 @@ + + + + Exe + net9.0 + enable + + + + + + + + + + + diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/_Imports.razor b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/_Imports.razor new file mode 100644 index 00000000..05428637 --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/_Imports.razor @@ -0,0 +1,6 @@ +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.JSInterop +@using SeeSharp.Blazor.Template + +@using SeeSharp.Blazor diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/appsettings.Development.json b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/appsettings.Development.json new file mode 100644 index 00000000..770d3e93 --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/appsettings.json b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/wwwroot/css/site.css b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/wwwroot/css/site.css new file mode 100644 index 00000000..edac4442 --- /dev/null +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.TemplateExample/wwwroot/css/site.css @@ -0,0 +1,95 @@ +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 3.5rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +html { + font-family: system-ui; +} + +button { + background-color: #a4e1f2; + border-style: none; + /* border-width: 2px; + border-color: #245e6f; */ + color: black; + font-size: medium; + padding-left: 8px; + padding-right: 8px; + padding-bottom: 4px; + padding-top: 4px; +} + button:hover { + background-color: #c9eff4; + cursor: pointer; + } + button:disabled { + background-color: #e5f1f5; + color: #96b4bd; + border-color: #96b4bd; + } + +.experiment-settings { + display: flex; + gap: 0.25em; + flex-direction: column; + float: left; + margin-right: 1em; + height: 10000px; /*Sets height of parameter window on the left site. Only visual benefits*/ +} + +.experiment-results { + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: flex-start; +} + +.compare { + display: flex; + gap: 10px; + margin-bottom: 10px; + flex-wrap: wrap; + align-items: flex-start; +} + +table { + border-collapse: collapse; +} +td, th { + border: none; + padding: 4px; +} +tr:hover { background-color: #e7f2f1; } +th { + padding-top: 6px; + padding-bottom: 6px; + text-align: left; + background-color: #4a96af; + color: white; + font-size: smaller; +} \ No newline at end of file