From e3764ef2d07147779a41c29c9f72bd273b9e17bb Mon Sep 17 00:00:00 2001 From: Asval Date: Sun, 16 Mar 2025 02:01:19 +0100 Subject: [PATCH] bump c4p + spline mesh preview in umaps --- CUE4Parse | 2 +- FModel/FModel.csproj | 2 + FModel/MainWindow.xaml.cs | 5 +- FModel/Resources/default.vert | 12 ++ FModel/Resources/outline.vert | 11 ++ FModel/Resources/picking.vert | 10 + FModel/Resources/spline.vert | 216 +++++++++++++++++++++ FModel/Views/Snooper/Models/SplineModel.cs | 109 +++++++++++ FModel/Views/Snooper/Models/UModel.cs | 37 +++- FModel/Views/Snooper/Renderer.cs | 28 ++- FModel/Views/Snooper/Shading/Shader.cs | 9 +- 11 files changed, 417 insertions(+), 24 deletions(-) create mode 100644 FModel/Resources/spline.vert create mode 100644 FModel/Views/Snooper/Models/SplineModel.cs diff --git a/CUE4Parse b/CUE4Parse index 74405d72..85bc1ddb 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 74405d72a8a3a0403cd702ec7e4d32cf7bc79b4d +Subproject commit 85bc1ddbb3e4ef43170c9593ef08d147a4945ed3 diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 618c2365..34e7e4e1 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -102,6 +102,7 @@ + @@ -122,6 +123,7 @@ + diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 6e469d96..e8f32c5d 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -85,10 +85,7 @@ public partial class MainWindow #if DEBUG // await _threadWorkerView.Begin(cancellationToken => // _applicationView.CUE4Parse.Extract(cancellationToken, - // _applicationView.CUE4Parse.Provider["Marvel/Content/Marvel/Characters/1050/1050300/Meshes/SK_1050_1050300_Lobby.uasset"])); - // await _threadWorkerView.Begin(cancellationToken => - // _applicationView.CUE4Parse.Extract(cancellationToken, - // "RED/Content/Chara/ABA/Costume01/Animation/Charaselect/body/stand_body01.uasset")); + // _applicationView.CUE4Parse.Provider["FortniteGame/Plugins/GameFeatures/BRMapCh6/Content/Maps/Hermes_Terrain/_Generated_/A6Z7VUFLP2917NVTLT6WZFOVC.umap"])); #endif } diff --git a/FModel/Resources/default.vert b/FModel/Resources/default.vert index 9f396843..256da578 100644 --- a/FModel/Resources/default.vert +++ b/FModel/Resources/default.vert @@ -81,6 +81,18 @@ void main() finalNormal = normalize(finalNormal); finalTangent = normalize(finalTangent); } + else if (uIsSpline) + { + GpuSplineMeshParams params = uSplineParameters[gl_InstanceID]; + float distanceAlong = GetAxisValueRef(params.ForwardAxis, bindPos.xzy); + vec3 computed = ComputeRatioAlongSpline(params, distanceAlong); + mat4 sliceTransform = CalcSliceTransformAtSplineOffset(params, computed); + SetAxisValueRef(params.ForwardAxis, bindPos.xzy, 0f); + + finalPos = (sliceTransform * bindPos.xzyw).xzyw; + finalNormal = bindNormal; + finalTangent = bindTangent; + } else { finalPos = bindPos; diff --git a/FModel/Resources/outline.vert b/FModel/Resources/outline.vert index 0a930758..2e3cf6a5 100644 --- a/FModel/Resources/outline.vert +++ b/FModel/Resources/outline.vert @@ -62,6 +62,17 @@ void main() } } } + else if (uIsSpline) + { + GpuSplineMeshParams params = uSplineParameters[gl_InstanceID]; + float distanceAlong = GetAxisValueRef(params.ForwardAxis, bindPos.xzy); + vec3 computed = ComputeRatioAlongSpline(params, distanceAlong); + mat4 sliceTransform = CalcSliceTransformAtSplineOffset(params, computed); + SetAxisValueRef(params.ForwardAxis, bindPos.xzy, 0f); + + finalPos = (sliceTransform * bindPos.xzyw).xzyw; + finalNormal = bindNormal; + } else { finalPos = bindPos; diff --git a/FModel/Resources/picking.vert b/FModel/Resources/picking.vert index 15194254..745cbc04 100644 --- a/FModel/Resources/picking.vert +++ b/FModel/Resources/picking.vert @@ -47,6 +47,16 @@ void main() } } } + else if (uIsSpline) + { + GpuSplineMeshParams params = uSplineParameters[gl_InstanceID]; + float distanceAlong = GetAxisValueRef(params.ForwardAxis, bindPos.xzy); + vec3 computed = ComputeRatioAlongSpline(params, distanceAlong); + mat4 sliceTransform = CalcSliceTransformAtSplineOffset(params, computed); + SetAxisValueRef(params.ForwardAxis, bindPos.xzy, 0f); + + finalPos = (sliceTransform * bindPos.xzyw).xzyw; + } else finalPos = bindPos; gl_Position = uProjection * uView * vInstanceMatrix * finalPos; diff --git a/FModel/Resources/spline.vert b/FModel/Resources/spline.vert new file mode 100644 index 00000000..af594ba3 --- /dev/null +++ b/FModel/Resources/spline.vert @@ -0,0 +1,216 @@ +#version 460 core + +// yeeted from minshu https://github.com/FabianFG/CUE4Parse/commit/61cef25b8eef4160651ee41e2b1ceefc5135803f + +struct GpuSplineMeshParams { + int ForwardAxis; + float SplineBoundaryMin; + float SplineBoundaryMax; + bool bSmoothInterpRollScale; + + vec3 MeshOrigin; + vec3 MeshBoxExtent; + + vec3 StartPos; + float StartRoll; + vec3 StartTangent; + vec2 StartScale; + vec2 StartOffset; + vec3 EndPos; + float EndRoll; + vec3 EndTangent; + vec2 EndScale; + vec2 EndOffset; + + vec3 SplineUpDir; +}; + +layout(std430, binding = 3) buffer SplineParameters +{ + GpuSplineMeshParams uSplineParameters[]; +}; + +uniform bool uIsSpline; + +vec3 getSafeNormal(vec3 vector) { + float squareSum = dot(vector, vector); + + if (squareSum == 1.0) { + return vector; + } + + if (squareSum < 1e-8f) { + return vec3(0.0); // Return a zero vector + } + + // Calculate the scale factor to normalize the vector + float scale = inversesqrt(squareSum); + return vector * scale; +} + +float GetAxisValueRef(int forwardAxis, vec3 pos) +{ + switch (forwardAxis) + { + case 0: return pos.x; + case 1: return pos.y; + case 2: return pos.z; + default: return 0; + } +} + +void SetAxisValueRef(int forwardAxis, inout vec3 pos, float v) +{ + switch (forwardAxis) + { + case 0: pos.x = v; break; + case 1: pos.y = v; break; + case 2: pos.z = v; break; + } +} + +vec3 SplineEvalTangent(GpuSplineMeshParams params, float a) +{ + vec3 c = (6 * params.StartPos) + (3 * params.StartTangent) + (3 * params.EndTangent) - (6 * params.EndPos); + vec3 d = (-6 * params.StartPos) - (4 * params.StartTangent) - (2 * params.EndTangent) + (6 * params.EndPos); + vec3 e = params.StartTangent; + + float a2 = a * a; + + return (c * a2) + (d * a) + e; +} + +vec3 SplineEvalDir(GpuSplineMeshParams params, float a) +{ + return getSafeNormal(SplineEvalTangent(params, a)); +} + +vec3 SplineEvalPos(GpuSplineMeshParams params, float a) +{ + float a2 = a * a; + float a3 = a2 * a; + + return (((2 * a3) - (3 * a2) + 1) * params.StartPos) + ((a3 - (2 * a2) + a) * params.StartTangent) + ((a3 - a2) * params.EndTangent) + (((-2 * a3) + (3 * a2)) * params.EndPos); +} + +vec3 ComputeRatioAlongSpline(GpuSplineMeshParams params, float distanceAlong) +{ + float alpha = 0f; + float minT = 0f; + float maxT = 1f; + + const float SmallNumber = 1e-8f; + bool bHasCustomBoundary = abs(params.SplineBoundaryMin - params.SplineBoundaryMax) > SmallNumber; + if (bHasCustomBoundary) + { + float splineLength = params.SplineBoundaryMax - params.SplineBoundaryMin; + if (splineLength > 0) + { + alpha = (distanceAlong - params.SplineBoundaryMin) / splineLength; + } + + float boundMin = GetAxisValueRef(params.ForwardAxis, params.MeshOrigin - params.MeshBoxExtent); + float boundMax = GetAxisValueRef(params.ForwardAxis, params.MeshOrigin + params.MeshBoxExtent); + + float boundMinT = (boundMin - params.SplineBoundaryMin) / (params.SplineBoundaryMax - params.SplineBoundaryMin); + float boundMaxT = (boundMax - params.SplineBoundaryMin) / (params.SplineBoundaryMax - params.SplineBoundaryMin); + + const float MaxSplineExtrapolation = 4.0f; + minT = max(-MaxSplineExtrapolation, boundMinT); + maxT = min(boundMaxT, MaxSplineExtrapolation); + } + else + { + float meshMinZ = GetAxisValueRef(params.ForwardAxis, params.MeshOrigin) - GetAxisValueRef(params.ForwardAxis, params.MeshBoxExtent); + float meshRangeZ = 2 * GetAxisValueRef(params.ForwardAxis, params.MeshBoxExtent); + + if (meshRangeZ > SmallNumber) { + alpha = (distanceAlong - meshMinZ) / meshRangeZ; + } + } + return vec3(alpha, minT, maxT); +} + +mat4 CalcSliceTransformAtSplineOffset(GpuSplineMeshParams params, vec3 computed) +{ + float alpha = computed.x; + float minT = computed.y; + float maxT = computed.z; + + float hermiteAlpha = params.bSmoothInterpRollScale ? smoothstep(0.0f, 1.0f, alpha) : alpha; + + vec3 splinePos; + vec3 splineDir; + if (alpha < minT) + { + vec3 startTangent = SplineEvalTangent(params, minT); + splinePos = SplineEvalPos(params, minT) + (startTangent * (alpha - minT)); + splineDir = getSafeNormal(startTangent); + } + else if (alpha > maxT) + { + vec3 endTangent = SplineEvalTangent(params, maxT); + splinePos = SplineEvalPos(params, maxT) + (endTangent * (alpha - maxT)); + splineDir = getSafeNormal(endTangent); + } + else + { + splinePos = SplineEvalPos(params, alpha); + splineDir = SplineEvalDir(params, alpha); + } + + // base + vec3 baseXVec = getSafeNormal(cross(params.SplineUpDir, splineDir)); + vec3 baseYVec = getSafeNormal(cross(splineDir, baseXVec)); + + // Offset the spline by the desired amount + vec2 sliceOffset = mix(params.StartOffset, params.EndOffset, hermiteAlpha); + splinePos += sliceOffset.x * baseXVec; + splinePos += sliceOffset.y * baseYVec; + + // Apply Roll + float useRoll = mix(params.StartRoll, params.EndRoll, hermiteAlpha); + float cosAng = cos(useRoll); + float sinAng = sin(useRoll); + vec3 xVec = (cosAng * baseXVec) - (sinAng * baseYVec); + vec3 yVec = (cosAng * baseYVec) + (sinAng * baseXVec); + + // Find Scale + vec2 useScale = mix(params.StartScale, params.EndScale, hermiteAlpha); + + // Build overall transform + mat4 sliceTransform = mat4(0); + vec3 scale; + switch (params.ForwardAxis) { + case 0: + sliceTransform[0] = vec4(splineDir, 0f); + sliceTransform[1] = vec4(xVec, 0f); + sliceTransform[2] = vec4(yVec, 0f); + sliceTransform[3] = vec4(splinePos, 1f); + scale = vec3(1, useScale.x, useScale.y); + break; + case 1: + sliceTransform[0] = vec4(yVec, 0f); + sliceTransform[1] = vec4(splineDir, 0f); + sliceTransform[2] = vec4(xVec, 0f); + sliceTransform[3] = vec4(splinePos, 1f); + scale = vec3(useScale.y, 1, useScale.x); + break; + case 2: + sliceTransform[0] = vec4(xVec, 0f); + sliceTransform[1] = vec4(yVec, 0f); + sliceTransform[2] = vec4(splineDir, 0f); + sliceTransform[3] = vec4(splinePos, 1f); + scale = vec3(useScale.x, useScale.y, 1); + break; + } + + mat4 scaleMatrix = mat4( + vec4(scale.x, 0.0, 0.0, 0.0), + vec4(0.0, scale.y, 0.0, 0.0), + vec4(0.0, 0.0, scale.z, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); + + return sliceTransform * scaleMatrix; +} diff --git a/FModel/Views/Snooper/Models/SplineModel.cs b/FModel/Views/Snooper/Models/SplineModel.cs new file mode 100644 index 00000000..3850a4ce --- /dev/null +++ b/FModel/Views/Snooper/Models/SplineModel.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.InteropServices; +using CUE4Parse_Conversion.Meshes.PSK; +using CUE4Parse.UE4.Assets.Exports.Component.SplineMesh; +using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.StaticMesh; +using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse.UE4.Objects.Core.Math; +using FModel.Views.Snooper.Buffers; +using FModel.Views.Snooper.Shading; +using OpenTK.Graphics.OpenGL4; + +namespace FModel.Views.Snooper.Models; + +public class SplineModel : StaticModel +{ + [StructLayout(LayoutKind.Sequential)] + public struct GpuParams + { + public int ForwardAxis; + public float SplineBoundaryMin; + public float SplineBoundaryMax; + public bool bSmoothInterpRollScale; + + public FVector MeshOrigin; + public int _padding0; + public FVector MeshBoxExtent; + public int _padding1; + + public FVector StartPos; + public float StartRoll; + public FVector StartTangent; + public int _padding2; + public FVector2D StartScale; + public FVector2D StartOffset; + public FVector EndPos; + public float EndRoll; + public FVector EndTangent; + public int _padding3; + public FVector2D EndScale; + public FVector2D EndOffset; + + public FVector SplineUpDir; + public int _padding4; + + public GpuParams(USplineMeshComponent splineMesh) + { + ForwardAxis = (int)splineMesh.ForwardAxis; + SplineBoundaryMin = splineMesh.SplineBoundaryMin; + SplineBoundaryMax = splineMesh.SplineBoundaryMax; + bSmoothInterpRollScale = splineMesh.bSmoothInterpRollScale; + + var b = splineMesh.GetLoadedStaticMesh()?.RenderData?.Bounds ?? new FBoxSphereBounds(); + MeshOrigin = b.Origin * Constants.SCALE_DOWN_RATIO; + MeshBoxExtent = b.BoxExtent * Constants.SCALE_DOWN_RATIO; + + var p = splineMesh.SplineParams; + StartPos = p.StartPos * Constants.SCALE_DOWN_RATIO; + StartRoll = p.StartRoll; + StartTangent = p.StartTangent * Constants.SCALE_DOWN_RATIO; + StartScale = p.StartScale; + StartOffset = p.StartOffset; + EndPos = p.EndPos * Constants.SCALE_DOWN_RATIO; + EndRoll = p.EndRoll; + EndTangent = p.EndTangent * Constants.SCALE_DOWN_RATIO; + EndScale = p.EndScale; + EndOffset = p.EndOffset; + + SplineUpDir = splineMesh.SplineUpDir; + } + } + + private readonly List _splineParams; + private BufferObject _ssbo; + + public SplineModel(UStaticMesh export, CStaticMesh staticMesh, USplineMeshComponent splineMesh, Transform transform = null) : base(export, staticMesh, transform) + { + _splineParams = [new GpuParams(splineMesh)]; + + Type = "SplineMesh"; + IsVisible = true; + IsTwoSided = true; + } + + public void AddComponent(USplineMeshComponent splineMesh) + { + _splineParams.Add(new GpuParams(splineMesh)); + } + + public override void Setup(Options options) + { + base.Setup(options); + + _ssbo = new BufferObject(_splineParams.ToArray(), BufferTarget.ShaderStorageBuffer); + } + + public void Render(Shader shader) + { + shader.SetUniform("uIsSpline", true); + _ssbo.BindBufferBase(3); + } + + public override void Dispose() + { + base.Dispose(); + _ssbo?.Dispose(); + } +} diff --git a/FModel/Views/Snooper/Models/UModel.cs b/FModel/Views/Snooper/Models/UModel.cs index 62b95084..603a2189 100644 --- a/FModel/Views/Snooper/Models/UModel.cs +++ b/FModel/Views/Snooper/Models/UModel.cs @@ -50,7 +50,7 @@ public abstract class UModel : IRenderableModel public string Path { get; } public string Name { get; } - public string Type { get; } + public string Type { get; protected set; } public int UvCount { get; } public uint[] Indices { get; set; } public float[] Vertices { get; set; } @@ -219,7 +219,7 @@ public abstract class UModel : IRenderableModel collision.Setup(); } - if (options.Models.Count == 1 && Sections.All(x => !x.Show)) + if (options.Models.Count == 1 && Sections.All(x => !x.Show)) // visible if alone and invisible { IsVisible = true; foreach (var section in Sections) @@ -227,9 +227,20 @@ public abstract class UModel : IRenderableModel section.Show = true; } } - else foreach (var section in Sections) + else if (!IsVisible) // default: visible if one section is visible { - if (!IsVisible) IsVisible = section.Show; + foreach (var section in Sections) + { + if (section.Show) + { + IsVisible = true; + break; + } + } + } + else foreach (var section in Sections) // force visibility + { + section.Show = true; } IsSetup = true; @@ -246,7 +257,13 @@ public abstract class UModel : IRenderableModel } if (this is SkeletalModel skeletalModel) skeletalModel.Render(shader); - else shader.SetUniform("uIsAnimated", false); + else if (this is SplineModel splineModel) splineModel.Render(shader); + else + { + shader.SetUniform("uIsAnimated", false); + shader.SetUniform("uIsSpline", false); + } + if (!outline) { shader.SetUniform("uUvCount", UvCount); @@ -290,9 +307,13 @@ public abstract class UModel : IRenderableModel public void PickingRender(Shader shader) { if (IsTwoSided) GL.Disable(EnableCap.CullFace); - if (this is SkeletalModel skeletalModel) - skeletalModel.Render(shader); - else shader.SetUniform("uIsAnimated", false); + if (this is SkeletalModel skeletalModel) skeletalModel.Render(shader); + if (this is SplineModel splineModel) splineModel.Render(shader); + else + { + shader.SetUniform("uIsAnimated", false); + shader.SetUniform("uIsSpline", false); + } Vao.Bind(); foreach (var section in Sections) diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 3a7b811a..dfa91ad3 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -8,6 +8,7 @@ using CUE4Parse_Conversion.Animations; using CUE4Parse_Conversion.Meshes; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Animation; +using CUE4Parse.UE4.Assets.Exports.Component.SplineMesh; using CUE4Parse.UE4.Assets.Exports.Component.StaticMesh; using CUE4Parse.UE4.Assets.Exports.GeometryCollection; using CUE4Parse.UE4.Assets.Exports.Material; @@ -443,8 +444,7 @@ public class Renderer : IDisposable { cancellationToken.ThrowIfCancellationRequested(); - if (persistentLevel.Actors[i].Load() is not { } actor || - actor.ExportType is "LODActor" or "SplineMeshActor") + if (persistentLevel.Actors[i].Load() is not { } actor || actor.ExportType is "LODActor") continue; Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {i}/{length}"); @@ -535,17 +535,17 @@ public class Renderer : IDisposable { foreach (var component in instanceComponents) { - if (!component.TryLoad(out UInstancedStaticMeshComponent staticMeshComp) || + if (!component.TryLoad(out UStaticMeshComponent staticMeshComp) || !staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) || m.Materials.Length < 1) continue; - if (staticMeshComp.PerInstanceSMData is { Length: > 0 }) + var relation = CalculateTransform(staticMeshComp, transform); + if (staticMeshComp is UInstancedStaticMeshComponent { PerInstanceSMData.Length: > 0 } instancedStaticMeshComp) { - var relation = CalculateTransform(staticMeshComp, transform); - foreach (var perInstanceData in staticMeshComp.PerInstanceSMData) + foreach (var perInstanceData in instancedStaticMeshComp.PerInstanceSMData) { - ProcessMesh(actor, staticMeshComp, m, new Transform + ProcessMesh(actor, instancedStaticMeshComp, m, new Transform { Relation = relation.Matrix, Position = perInstanceData.TransformData.Translation * Constants.SCALE_DOWN_RATIO, @@ -554,7 +554,7 @@ public class Renderer : IDisposable }); } } - else ProcessMesh(actor, staticMeshComp, m, CalculateTransform(staticMeshComp, transform)); + else ProcessMesh(actor, staticMeshComp, m, relation); } } else if (actor.TryGetValue(out FPackageIndex componentTemplate, "ComponentTemplate") && @@ -574,7 +574,7 @@ public class Renderer : IDisposable ProcessMesh(actor, compTemplate, m, CalculateTransform(compTemplate, transform), forceShow); } } - else if (actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "ComponentTemplate", "StaticMesh", "Mesh", "LightMesh") && + else if (actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "ComponentTemplate", "StaticMesh", "Mesh", "LightMesh", "SplineMesh") && staticMeshComponent.TryLoad(out UStaticMeshComponent staticMeshComp) && staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) && m.Materials.Length > 0) { @@ -589,14 +589,17 @@ public class Renderer : IDisposable } private void ProcessMesh(IPropertyHolder actor, UObject staticMeshComp, UStaticMesh m, Transform transform, bool forceShow) { + var bSpline = staticMeshComp is USplineMeshComponent; var guid = m.LightingGuid; if (Options.TryGetModel(guid, out var model)) { model.AddInstance(transform); + if (bSpline && model is SplineModel splineModel) + splineModel.AddComponent((USplineMeshComponent)staticMeshComp); } else if (m.TryConvert(out var mesh)) { - model = new StaticModel(m, mesh, transform); + model = bSpline ? new SplineModel(m, mesh, (USplineMeshComponent)staticMeshComp, transform) : new StaticModel(m, mesh, transform); model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided)); if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData")) @@ -671,6 +674,11 @@ public class Renderer : IDisposable private Transform CalculateTransform(IPropertyHolder staticMeshComp, Transform relation) { + if (staticMeshComp.TryGetValue(out FPackageIndex ap, "AttachParent") && ap.TryLoad(out UObject component)) + { + relation = CalculateTransform(component, relation); + } + return new Transform { Relation = relation.Matrix, diff --git a/FModel/Views/Snooper/Shading/Shader.cs b/FModel/Views/Snooper/Shading/Shader.cs index 537800df..a1c0f962 100644 --- a/FModel/Views/Snooper/Shading/Shader.cs +++ b/FModel/Views/Snooper/Shading/Shader.cs @@ -47,13 +47,20 @@ public class Shader : IDisposable private int LoadShader(ShaderType type, string file) { var executingAssembly = Assembly.GetExecutingAssembly(); - using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{file}"); + var executingAssemblyName = executingAssembly.GetName().Name; + using var stream = executingAssembly.GetManifestResourceStream($"{executingAssemblyName}.Resources.{file}"); using var reader = new StreamReader(stream); var handle = GL.CreateShader(type); var content = reader.ReadToEnd(); if (file.Equals("default.frag") && GL.GetInteger(GetPName.MaxTextureCoords) == 0) content = content.Replace("#define MAX_UV_COUNT 8", "#define MAX_UV_COUNT 1"); + if (type == ShaderType.VertexShader && Array.IndexOf(["default.vert", "outline.vert", "picking.vert"], file) > -1) + { + using var splineStream = executingAssembly.GetManifestResourceStream($"{executingAssemblyName}.Resources.spline.vert"); + using var splineReader = new StreamReader(splineStream); + content = splineReader.ReadToEnd() + Environment.NewLine + content.Replace("#version 460 core", ""); + } GL.ShaderSource(handle, content); GL.CompileShader(handle);