bump c4p + spline mesh preview in umaps
Some checks are pending
FModel QA Builder / build (push) Waiting to run

This commit is contained in:
Asval 2025-03-16 02:01:19 +01:00
parent 20a5c63baa
commit e3764ef2d0
11 changed files with 417 additions and 24 deletions

@ -1 +1 @@
Subproject commit 74405d72a8a3a0403cd702ec7e4d32cf7bc79b4d
Subproject commit 85bc1ddbb3e4ef43170c9593ef08d147a4945ed3

View File

@ -102,6 +102,7 @@
<None Remove="Resources\npcleftside.png" />
<None Remove="Resources\default.frag" />
<None Remove="Resources\default.vert" />
<None Remove="Resources\spline.vert" />
<None Remove="Resources\grid.frag" />
<None Remove="Resources\grid.vert" />
<None Remove="Resources\skybox.frag" />
@ -122,6 +123,7 @@
<ItemGroup>
<EmbeddedResource Include="Resources\Json.xshd" />
<EmbeddedResource Include="Resources\Ini.xshd" />
<EmbeddedResource Include="Resources\spline.vert" />
<EmbeddedResource Include="Resources\Verse.xshd" />
<EmbeddedResource Include="Resources\Xml.xshd" />
<EmbeddedResource Include="Resources\Cpp.xshd" />

View File

@ -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
}

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

@ -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<GpuParams> _splineParams;
private BufferObject<GpuParams> _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<GpuParams>(_splineParams.ToArray(), BufferTarget.ShaderStorageBuffer);
}
public void Render(Shader shader)
{
shader.SetUniform("uIsSpline", true);
_ssbo.BindBufferBase(3);
}
public override void Dispose()
{
base.Dispose();
_ssbo?.Dispose();
}
}

View File

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

View File

@ -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,

View File

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