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);