diff --git a/GlitchyEditor/content/Shaders/myEffect.hlsl b/GlitchyEditor/content/Shaders/myEffect.hlsl index c8b5697..8e8797f 100644 --- a/GlitchyEditor/content/Shaders/myEffect.hlsl +++ b/GlitchyEditor/content/Shaders/myEffect.hlsl @@ -23,13 +23,22 @@ cbuffer SceneConstants cbuffer ObjectConstants { float4x4 Transform; + /** + * \brief Inverted and transposed transform matrix. + * \remarks This matrix is used in order to correctly transform normal vectors. + */ + float3x3 Transform_InvT; } cbuffer Constants { + #EditorVariable{ Name = "AlbedoColor"; Preview = "Albedo Color"; Type="Color" } float4 AlbedoColor = float4(1.0, 1.0, 1.0, 1.0); + #EditorVariable{ Name = "NormalScaling"; Preview = "Normal Scaling" } float2 NormalScaling = float2(1.0, 1.0); + #EditorVariable{ Name = "MetallicFactor"; Preview = "Metallic Factor"; Min = 0.0f; Max = 1.0f } float MetallicFactor = 1.0; + #EditorVariable{ Name = "RoughnessFactor"; Preview = "Rougness Factor"; Min = 0.0f; Max = 1.0f } float RoughnessFactor = 1.0; // float AmbientFactor = 1.0; } @@ -62,7 +71,7 @@ PS_IN VS(VS_IN input) output.Position = mul(ViewProjection, worldPosition); output.WorldPosition = worldPosition.xyz / worldPosition.w; - output.Normal = mul(input.Normal, (float3x3)Transform); + output.Normal = mul(Transform_InvT, input.Normal); output.Tangent = mul((float3x3)Transform, input.Tangent); // TODO: output.Handedness = input.Tangent.w @@ -91,7 +100,7 @@ PS_OUT PS(PS_IN input) // TODO: float3 bitangent = input.Handedness * cross(normal, tangent); float3 bitangent = -cross(normal, tangent); - float3x3 tangentTransform = float3x3(tangent, bitangent, normal); + //float3x3 tangentTransform = float3x3(tangent, bitangent, normal); //tangentTransform = transpose(tangentTransform); float4 texAlbedo = AlbedoTexture.Sample(AlbedoSampler, input.TexCoord); diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index 2b97f63..eacfd74 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -3,6 +3,8 @@ using GlitchyEngine.World; using System; using GlitchyEngine.Math; using System.Collections; +using GlitchyEngine.Renderer; +using GlitchyEngine; namespace GlitchyEditor.EditWindows { @@ -58,6 +60,7 @@ namespace GlitchyEditor.EditWindows ShowComponentEditor("Transform", entity, => ShowTransformComponentEditor); ShowComponentEditor("Camera", entity, => ShowCameraComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu); + ShowComponentEditor("Mesh Renderer", entity, => ShowMeshRendererComponentEditor, => ShowComponentContextMenu); ShowAddComponentButton(entity); } @@ -246,6 +249,106 @@ namespace GlitchyEditor.EditWindows ImGui.ColorEdit4("Color", ref spriteRendererComponent.Color); } + private static void ShowMeshRendererComponentEditor(Entity entity, MeshRendererComponent* meshRendererComponent) + { + // TODO: Editing material options obviously shouldn't be part of the meshrenderer-ui + + Material material = meshRendererComponent.Material; + + Effect effect = material.Effect; + + bool TryGetValue(Dictionary parameters, String name, out Variant value) + { + if (parameters.TryGetValue(name, let param)) + { + value = param; + return true; + } + + value = ?; + + return false; + } + + for (let texture in effect.Textures) + { + ImGui.Text(texture.key); + } + + for (let (name, arguments) in effect.[Friend]_variableDescriptions) + { + let variable = effect.Variables[name]; + + bool hasPreviewName = TryGetValue(arguments, "Preview", var previewName); + + StringView displayName = hasPreviewName ? previewName.Get() : name; + + bool hasPreviewType = TryGetValue(arguments, "Type", var previewType); + + if (hasPreviewType && previewType.Get() == "Color") + { + Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1); + + if (variable.Columns == 3) + { + material.GetVariable(variable.Name, var value); + if (ImGui.ColorEdit3(displayName.Ptr, *(float[3]*)&value)) + material.SetVariable(variable.Name, value); + } + else if (variable.Columns == 4) + { + material.GetVariable(variable.Name, var value); + if (ImGui.ColorEdit4(displayName.Ptr, *(float[4]*)&value)) + material.SetVariable(variable.Name, value); + } + } + else if (variable.Type == .Float && variable.Rows == 1) + { + bool hasMin = TryGetValue(arguments, "Min", var min); + bool hasMax = TryGetValue(arguments, "Max", var max); + + for (int r < variable.Rows) + { + switch (variable.Columns) + { + case 1: + material.GetVariable(variable.Name, var value); + + float[1] minV = hasMin ? min.Get() : .(float.MinValue); + float[1] maxV = hasMax ? max.Get() : .(float.MaxValue); + + if (ImGui.EditVector<1>(displayName, ref *(float[1]*)&value, .(), 0.1f, 100.0f, minV, maxV)) + material.SetVariable(variable.Name, value); + case 2: + material.GetVariable(variable.Name, var value); + + Vector2 minV = hasMin ? min.Get() : .(float.MinValue); + Vector2 maxV = hasMax ? max.Get() : .(float.MaxValue); + + if (ImGui.EditVector2(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV)) + material.SetVariable(variable.Name, value); + case 3: + material.GetVariable(variable.Name, var value); + + Vector3 minV = hasMin ? min.Get() : .(float.MinValue); + Vector3 maxV = hasMax ? max.Get() : .(float.MaxValue); + + if (ImGui.EditVector3(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV)) + material.SetVariable(variable.Name, value); + case 4: + material.GetVariable(variable.Name, var value); + + Vector4 minV = hasMin ? min.Get() : .(float.MinValue); + Vector4 maxV = hasMax ? max.Get() : .(float.MaxValue); + + if (ImGui.EditVector4(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV)) + material.SetVariable(variable.Name, value); + } + } + } + } + } + private static void ShowAddComponentButton(Entity entity) { static char8[128] searchBuffer = .(); diff --git a/GlitchyEngine/src/ImGui/ImGuiExtension.bf b/GlitchyEngine/src/ImGui/ImGuiExtension.bf index cafe8aa..7bde94d 100644 --- a/GlitchyEngine/src/ImGui/ImGuiExtension.bf +++ b/GlitchyEngine/src/ImGui/ImGuiExtension.bf @@ -40,9 +40,35 @@ namespace ImGui /// Releases references that accumulated calls like ImGui::Image protected internal static extern void CleanupFrame(); - /// Control to edit a vector 3 with drag functionality and reset buttons - public static bool EditVector3(StringView label, ref Vector3 value, Vector3 resetValues = .Zero, float dragSpeed = 0.1f, float columnWidth = 100f) + /// Control to edit a vector 2 with drag functionality and reset buttons + public static bool EditVector2(StringView label, ref Vector2 value, Vector2 resetValues = .Zero, float dragSpeed = 0.1f, float columnWidth = 100f, Vector2 minValue = .Zero, Vector2 maxValue = .Zero) { + return EditVector<2>(label, ref *(float[2]*)&value, (float[2])resetValues, dragSpeed, columnWidth, (float[2])minValue, (float[2])maxValue); + } + + /// Control to edit a vector 3 with drag functionality and reset buttons + public static bool EditVector3(StringView label, ref Vector3 value, Vector3 resetValues = .Zero, float dragSpeed = 0.1f, float columnWidth = 100f, Vector3 minValue = .Zero, Vector3 maxValue = .Zero) + { + return EditVector<3>(label, ref *(float[3]*)&value, (float[3])resetValues, dragSpeed, columnWidth, (float[3])minValue, (float[3])maxValue); + } + + /// Control to edit a vector 4 with drag functionality and reset buttons + public static bool EditVector4(StringView label, ref Vector4 value, Vector4 resetValues = .Zero, float dragSpeed = 0.1f, float columnWidth = 100f, Vector4 minValue = .Zero, Vector4 maxValue = .Zero) + { + return EditVector<4>(label, ref *(float[4]*)&value, (float[4])resetValues, dragSpeed, columnWidth, (float[4])minValue, (float[4])maxValue); + } + + public static bool EditVector(StringView label, ref float[NumComponents] value, float[NumComponents] resetValues = .(), float dragSpeed = 0.1f, float columnWidth = 100f, float[NumComponents] minValue = .(), float[NumComponents] maxValue = .()) where NumComponents : const int32 + { + const String[?] componentNames = .("X", "Y", "Z", "W"); + const String[?] componentIds = .("##X", "##Y", "##Z", "##W"); + + (Color Default, Color Hovered, Color Active)[?] ButtonColors = .( + (Color(230, 25, 45), Color(150, 25, 45), Color(230, 90, 90)), + (Color(50, 190, 15), Color(50, 120, 15), Color(116, 190, 99)), + (Color(55, 55, 230), Color(55, 55, 150), Color(90, 90, 230)), + (Color(230, 25, 45), Color(230, 25, 45), Color(230, 25, 45))); + bool changed = false; PushID(label); @@ -56,69 +82,40 @@ namespace ImGui NextColumn(); - PushMultiItemsWidths(3, CalcItemWidth()); - PushStyleVar(.ItemSpacing, Vec2.Zero); - defer PopStyleVar(); - + PushMultiItemsWidths(NumComponents, CalcItemWidth()); + float lineHeight = GetFont().FontSize + GetStyle().FramePadding.y * 2.0f; ImGui.Vec2 buttonSize = .(lineHeight + 3.0f, lineHeight); - PushStyleColor(.Button, Color(230, 25, 45).Value); - PushStyleColor(.ButtonHovered, Color(150, 25, 45).Value); - PushStyleColor(.ButtonActive, Color(230, 120, 130).Value); - - if (Button("X", buttonSize)) + PushStyleVar(.ItemSpacing, Vec2.Zero); + + for (int i < NumComponents) { - value.X = resetValues.X; - changed = true; + if (i > 0) + { + SameLine(); + } + + PushStyleColor(.Button, ButtonColors[i].Default.Value); + PushStyleColor(.ButtonHovered, ButtonColors[i].Hovered.Value); + PushStyleColor(.ButtonActive, ButtonColors[i].Active.Value); + + if (Button(componentNames[i], buttonSize)) + { + value[i] = resetValues[i]; + changed = true; + } + + SameLine(); + + if (DragFloat(componentIds[i], &value[i], dragSpeed, minValue[i], maxValue[i])) + changed = true; + + PopItemWidth(); + PopStyleColor(3); } - SameLine(); - - if (DragFloat("##X", &value.X, dragSpeed)) - changed = true; - - PopItemWidth(); - SameLine(); - - PopStyleColor(3); - PushStyleColor(.Button, Color(50, 190, 15).Value); - PushStyleColor(.ButtonHovered, Color(50, 120, 15).Value); - PushStyleColor(.ButtonActive, Color(116, 190, 99).Value); - - if (Button("Y", buttonSize)) - { - value.Y = resetValues.Y; - changed = true; - } - - SameLine(); - - if (DragFloat("##Y", &value.Y, dragSpeed)) - changed = true; - - PopItemWidth(); - SameLine(); - - PopStyleColor(3); - PushStyleColor(.Button, Color(55, 55, 230).Value); - PushStyleColor(.ButtonHovered, Color(55, 55, 150).Value); - PushStyleColor(.ButtonActive, Color(90, 90, 230).Value); - - if (Button("Z", buttonSize)) - { - value.Z = resetValues.Z; - changed = true; - } - - SameLine(); - - if (DragFloat("##Z", &value.Z, dragSpeed)) - changed = true; - - PopItemWidth(); - - PopStyleColor(3); + PopStyleVar(); return changed; } diff --git a/GlitchyEngine/src/Math/Vector2.bf b/GlitchyEngine/src/Math/Vector2.bf index f2eff59..4577296 100644 --- a/GlitchyEngine/src/Math/Vector2.bf +++ b/GlitchyEngine/src/Math/Vector2.bf @@ -282,12 +282,16 @@ namespace GlitchyEngine.Math public override void ToString(String strBuffer) => strBuffer.AppendF("X:{0} Y:{1}", X, Y); - [Inline] - public static explicit operator Self(float value) => Self(value); - public bool Equals(Vector2 v, float epsilon = Math.[Friend]sMachineEpsilonFloat) { return (Math.Abs(v.X - X) < epsilon) && (Math.Abs(v.Y - Y) < epsilon); } + + [Inline] + public static explicit operator Self(float value) => Self(value); + + [Inline] +#unwarn + public static explicit operator float[2](Vector2 value) => *(float[2]*)&value; } } diff --git a/GlitchyEngine/src/Math/Vector3.bf b/GlitchyEngine/src/Math/Vector3.bf index 6848a70..20b2a14 100644 --- a/GlitchyEngine/src/Math/Vector3.bf +++ b/GlitchyEngine/src/Math/Vector3.bf @@ -339,5 +339,9 @@ namespace GlitchyEngine.Math [Inline] public static explicit operator Self(float value) => Self(value); + + [Inline] +#unwarn + public static explicit operator float[3](Vector3 value) => *(float[3]*)&value; } } diff --git a/GlitchyEngine/src/Math/Vector4.bf b/GlitchyEngine/src/Math/Vector4.bf index 0774578..ffb493d 100644 --- a/GlitchyEngine/src/Math/Vector4.bf +++ b/GlitchyEngine/src/Math/Vector4.bf @@ -320,5 +320,9 @@ namespace GlitchyEngine.Math [Inline] public static explicit operator Self(float value) => Self(value); + + [Inline] +#unwarn + public static explicit operator float[4](Vector4 value) => *(float[4]*)&value; } } diff --git a/GlitchyEngine/src/Renderer/BufferVariable.bf b/GlitchyEngine/src/Renderer/BufferVariable.bf index b0bdb3b..2d06747 100644 --- a/GlitchyEngine/src/Renderer/BufferVariable.bf +++ b/GlitchyEngine/src/Renderer/BufferVariable.bf @@ -29,6 +29,9 @@ namespace GlitchyEngine.Renderer public bool IsUsed => _isUsed; + public uint32 Columns => _columns; + public uint32 Rows => _rows; + /** * Gets a pointer to the start of the variable in the constant buffers backing data. */ diff --git a/GlitchyEngine/src/Renderer/Effect.bf b/GlitchyEngine/src/Renderer/Effect.bf index 504547a..33ac255 100644 --- a/GlitchyEngine/src/Renderer/Effect.bf +++ b/GlitchyEngine/src/Renderer/Effect.bf @@ -101,6 +101,25 @@ namespace GlitchyEngine.Renderer internal PixelShader _ps ~ _?.ReleaseRef(); protected String _name ~ delete _; + typealias VariableDesc = Dictionary>; + + protected VariableDesc _variableDescriptions ~ { + for (var (key, value) in _) + { + delete key; + + for (var (entryKey, entry) in value) + { + delete entryKey; + entry.Dispose(); + } + + delete value; + } + + delete _; + }; + BufferCollection _bufferCollection ~ delete _; BufferVariableCollection _variables ~ delete _; @@ -167,7 +186,9 @@ namespace GlitchyEngine.Renderer String vsName = scope String(); String psName = scope String(); - ProcessFile(filename, fileContent, vsName, psName); + _variableDescriptions = new VariableDesc(); + + ProcessFile(filename, fileContent, vsName, psName, _variableDescriptions); Compile(fileContent, filename, vsName, psName); @@ -282,15 +303,22 @@ namespace GlitchyEngine.Renderer } const String effectKeyword = "#effect"; + + private static void CommentLine(StringView code, int commentPosition) + { + code[commentPosition] = '/'; + code[commentPosition + 1] = '/'; + } /** * Loads the effect file and extracts the names of the vertex- and pixel-shader. * @param filename The path of the effect file. * @param fileContent The preprocessed effect file. - * @param vsName The string that will receive the vertex shader entry point. - * @param psName The string that will receive the pixel shader entry point. + * @param outVsName The string that will receive the vertex shader entry point. + * @param outPsName The string that will receive the pixel shader entry point. + * @param outVarDescs The dictionary that will contain the Variable descriptions. */ - private static void ProcessFile(String filename, String fileContent, String vsName, String psName) + private static void ProcessFile(String filename, String fileContent, String outVsName, String outPsName, VariableDesc outVarDescs) { Debug.Profiler.ProfileResourceFunction!(); @@ -298,6 +326,8 @@ namespace GlitchyEngine.Renderer // append line ending just in case the file doesn't end with one. fileContent.Append('\n'); + ProcessEditorVariables(filename, fileContent, outVarDescs); + int effectIndex = fileContent.IndexOf(effectKeyword, true); Log.EngineLogger.Assert(effectIndex >= 0, "Could not find #effect preprocessor directive."); @@ -337,16 +367,173 @@ namespace GlitchyEngine.Renderer switch(paramName) { case "VS", "VertexShader": - vsName.Append(paramValue); + outVsName.Append(paramValue); case "PS", "PixelShader": - psName.Append(paramValue); + outPsName.Append(paramValue); default: Log.EngineLogger.Assert(false, scope $"Unknown parameter name \"{paramName}\"."); } } // remove preprocessor directive from string so that the compiler wont try process it - fileContent.Remove(effectIndex, lineEndIndex - effectIndex); + CommentLine(fileContent, effectIndex); + } + + private static void ProcessEditorVariables(StringView fileName, StringView code, VariableDesc outVarDescs) + { + int index = 0; + + while(true) + { + index = code.IndexOf("#EditorVariable", index); + + if (index == -1) + break; + + CommentLine(code, index); + + index = code.IndexOf('{', index); + + Log.EngineLogger.AssertDebug(index != -1, "Missing '{'."); + + index++; + + int endIndex = code.IndexOf('}', index); + + Log.EngineLogger.AssertDebug(endIndex != -1, "Missing '}'."); + + StringView variableInfo = StringView(code, index, endIndex - index); + + Log.ClientLogger.Info($"Found variable: \"{variableInfo}\""); + + Dictionary parameters = new .(); + + String variableName = null; + + for (StringView argument in variableInfo.Split(';', .RemoveEmptyEntries)) + { + int equalsIndex = argument.IndexOf('='); + + if (equalsIndex == -1) + { + Log.EngineLogger.Error($"Missing value for Argument \"{argument..Trim()}\"."); + } + else + { + StringView argName = argument.Substring(0, equalsIndex)..Trim(); + StringView argValue = argument.Substring(equalsIndex + 1)..Trim(); + + if (argValue.StartsWith('"') && argValue.EndsWith('"')) + { + argValue = argValue[1...^2]; + } + + if (argName == "Name") + variableName = new String(argValue); + else + { + Variant value; + + if (argName == "Min" || argName == "Max") + { + value = ParseVariableValue(argValue); + } + else + { + value = Variant.Create(new String(argValue), true); + } + + parameters.Add(new String(argName), value); + } + } + } + + Log.EngineLogger.AssertDebug(variableName != null, "Missing argument \"Name\" int variable description."); + + outVarDescs.Add(variableName, parameters); + + index = endIndex; + } + } + + private static Variant ParseVariableValue(StringView valueString) + { + if (valueString[0].IsDigit || valueString[0] == '-') + { + var valueString; + + if (valueString.EndsWith('f')) + valueString.Length--; + + var result = float.Parse(valueString); + + Log.EngineLogger.AssertDebug(result case .Ok); + + if (result case .Ok(let value)) + return Variant.Create(value); + } + else if (valueString.StartsWith("float")) + { + int index = 5; + + int numComponents = valueString[index++] - '0'; + + while (valueString[index] != '(') + { + Log.EngineLogger.AssertDebug(valueString[index].IsWhiteSpace, "Expected '('."); + + index++; + } + + Log.EngineLogger.AssertDebug(numComponents >= 2 && numComponents <= 4, scope $"Unsupported component count {numComponents}. Value must be between 2 and 4"); + + float[] floats = scope float[numComponents]; + + for (int i < numComponents) + { + while (true) + { + char8 c = valueString[++index]; + + if (c.IsDigit || c == '.' || c == '-') + break; + } + + int start = index; + + while (true) + { + char8 c = valueString[++index]; + + if (!c.IsDigit && c != '.') + break; + } + + int end = index; + + StringView numberView = .(valueString, start, end - start); + + var result = float.Parse(numberView); + + if (result case .Ok(let value)) + { + floats[i] = value; + } + } + + if (numComponents == 2) + return Variant.Create(*(Vector2*)floats.Ptr); + else if (numComponents == 3) + return Variant.Create(*(Vector3*)floats.Ptr); + else if (numComponents == 4) + return Variant.Create(*(Vector4*)floats.Ptr); + } + else + { + Log.EngineLogger.Error($"Unsupported variable value: \"{valueString}\""); + } + + return Variant.Create(0.0f); } //protected extern void Compile(String code, String fileName, String vsEntry, String psEntry); diff --git a/GlitchyEngine/src/Renderer/Renderer.bf b/GlitchyEngine/src/Renderer/Renderer.bf index 4e3c61d..033100e 100644 --- a/GlitchyEngine/src/Renderer/Renderer.bf +++ b/GlitchyEngine/src/Renderer/Renderer.bf @@ -311,7 +311,11 @@ namespace GlitchyEngine.Renderer Debug.Profiler.ProfileRendererScope!("SetVariables"); entry.Material.SetVariable("ViewProjection", _sceneConstants.ViewProjection); + entry.Material.SetVariable("Transform", entry.Transform); + + Matrix3x3 mat = (Matrix3x3)(entry.Transform).Invert().Transpose(); + entry.Material.SetVariable("Transform_InvT", mat); } entry.Material.Bind(_context); diff --git a/GlitchyEngine/vendor/directx b/GlitchyEngine/vendor/directx index d1a21b6..fd75a31 160000 --- a/GlitchyEngine/vendor/directx +++ b/GlitchyEngine/vendor/directx @@ -1 +1 @@ -Subproject commit d1a21b6b88515cbb6130a637a2f4e0eb2cd690a9 +Subproject commit fd75a312d4356a3d95d05a5364f9aa9d5edef5e5