FModel/FModel/Views/Snooper/Renderer.cs
Asval c6c8cf201e
Some checks failed
FModel QA Builder / build (push) Has been cancelled
FModel v4.4.4
2024-10-20 21:11:17 +02:00

808 lines
32 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Windows;
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.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.GeometryCollection;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.UE4.Objects.Engine;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Creator;
using FModel.Extensions;
using FModel.Settings;
using FModel.Views.Snooper.Animations;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Lights;
using FModel.Views.Snooper.Models;
using FModel.Views.Snooper.Shading;
using OpenTK.Windowing.GraphicsLibraryFramework;
using UModel = FModel.Views.Snooper.Models.UModel;
namespace FModel.Views.Snooper;
public enum VertexColor
{
Default,
Sections,
Colors,
Normals,
TextureCoordinates
}
public class Renderer : IDisposable
{
private readonly Skybox _skybox;
private readonly Grid _grid;
private Shader _shader;
private Shader _outline;
private Shader _light;
private Shader _bone;
private Shader _collision;
private bool _saveCameraMode;
public bool ShowSkybox;
public bool ShowGrid;
public bool ShowLights;
public bool AnimateWithRotationOnly;
public bool IsSkeletonTreeOpen;
public VertexColor Color;
public Camera CameraOp { get; }
public PickingTexture Picking { get; }
public Options Options { get; }
public Renderer(int width, int height)
{
_skybox = new Skybox();
_grid = new Grid();
CameraOp = new Camera();
Picking = new PickingTexture(width, height);
Options = new Options();
ShowSkybox = UserSettings.Default.ShowSkybox;
ShowGrid = UserSettings.Default.ShowGrid;
AnimateWithRotationOnly = UserSettings.Default.AnimateWithRotationOnly;
Color = VertexColor.Default;
}
public void Load(CancellationToken cancellationToken, UObject export)
{
ShowLights = false;
Color = VertexColor.Default;
_saveCameraMode = export is not UWorld and not UBlueprintGeneratedClass;
switch (export)
{
case UStaticMesh st:
LoadStaticMesh(st);
break;
case USkeletalMesh sk:
LoadSkeletalMesh(sk);
break;
case USkeleton skel:
LoadSkeleton(skel);
break;
case UMaterialInstance mi:
LoadMaterialInstance(mi);
break;
case UWorld wd:
LoadWorld(cancellationToken, wd, Transform.Identity);
break;
case UBlueprintGeneratedClass bp:
LoadJunoWorld(cancellationToken, bp, Transform.Identity);
Color = VertexColor.Colors;
break;
case UPaperSprite ps:
LoadPaperSprite(ps);
break;
}
CameraOp.Mode = _saveCameraMode ? UserSettings.Default.CameraMode : Camera.WorldMode.FlyCam;
SetupCamera();
}
public void Swap(UMaterialInstance unrealMaterial)
{
if (!Options.TryGetModel(out var model) || !Options.TryGetSection(model, out var section)) return;
model.Materials[section.MaterialIndex].SwapMaterial(unrealMaterial);
Application.Current.Dispatcher.Invoke(() => model.Materials[section.MaterialIndex].Setup(Options, model.UvCount));
}
public void Animate(UObject anim) => Animate(anim, Options.SelectedModel);
private void Animate(UObject anim, FGuid guid)
{
if (!Options.TryGetModel(guid, out var m) || m is not SkeletalModel model)
return;
float maxElapsedTime;
switch (anim)
{
case UAnimSequence animSequence when animSequence.Skeleton.TryLoad(out USkeleton skeleton):
{
var animSet = skeleton.ConvertAnims(animSequence);
var animation = new Animation(animSequence, animSet, guid);
maxElapsedTime = animation.TotalElapsedTime;
model.Skeleton.Animate(animSet);
Options.AddAnimation(animation);
break;
}
case UAnimMontage animMontage when animMontage.Skeleton.TryLoad(out USkeleton skeleton):
{
var animSet = skeleton.ConvertAnims(animMontage);
var animation = new Animation(animMontage, animSet, guid);
maxElapsedTime = animation.TotalElapsedTime;
model.Skeleton.Animate(animSet);
Options.AddAnimation(animation);
foreach (var notifyEvent in animMontage.Notifies)
{
if (!notifyEvent.NotifyStateClass.TryLoad(out UObject notifyClass) ||
!notifyClass.TryGetValue(out FPackageIndex meshProp, "SkeletalMeshProp", "StaticMeshProp", "Mesh") ||
!meshProp.TryLoad(out UObject export)) continue;
var t = Transform.Identity;
if (notifyClass.TryGetValue(out FTransform offset, "Offset"))
{
t.Rotation = offset.Rotation;
t.Position = offset.Translation * Constants.SCALE_DOWN_RATIO;
t.Scale = offset.Scale3D;
}
UModel addedModel = null;
switch (export)
{
case UStaticMesh st:
{
guid = st.LightingGuid;
if (Options.TryGetModel(guid, out addedModel))
{
addedModel.AddInstance(t);
}
else if (st.TryConvert(out var mesh))
{
addedModel = new StaticModel(st, mesh, t);
Options.Models[guid] = addedModel;
}
break;
}
case USkeletalMesh sk:
{
guid = Guid.NewGuid();
if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh))
{
addedModel = new SkeletalModel(sk, mesh, t);
Options.Models[guid] = addedModel;
}
break;
}
}
if (addedModel == null)
throw new ArgumentException("Unknown model type");
addedModel.IsProp = true;
if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation"))
Animate(skeletalMeshPropAnimation, guid);
if (notifyClass.TryGetValue(out FName socketName, "SocketName"))
{
t = Transform.Identity;
if (notifyClass.TryGetValue(out FVector location, "LocationOffset", "Location"))
t.Position = location * Constants.SCALE_DOWN_RATIO;
if (notifyClass.TryGetValue(out FRotator rotation, "RotationOffset", "Rotation"))
t.Rotation = rotation.Quaternion();
if (notifyClass.TryGetValue(out FVector scale, "Scale"))
t.Scale = scale;
var s = new Socket($"ANIM_{addedModel.Name}", socketName, t, true);
model.Sockets.Add(s);
addedModel.Attachments.Attach(model, addedModel.GetTransform(), s,
new SocketAttachementInfo { Guid = guid, Instance = addedModel.SelectedInstance });
}
}
break;
}
case UAnimComposite animComposite when animComposite.Skeleton.TryLoad(out USkeleton skeleton):
{
var animSet = skeleton.ConvertAnims(animComposite);
var animation = new Animation(animComposite, animSet, guid);
maxElapsedTime = animation.TotalElapsedTime;
model.Skeleton.Animate(animSet);
Options.AddAnimation(animation);
break;
}
default:
throw new ArgumentException();
}
Options.Tracker.IsPaused = false;
Options.Tracker.SafeSetMaxElapsedTime(maxElapsedTime);
}
public void Setup()
{
_skybox.Setup();
_grid.Setup();
_shader = new Shader();
_outline = new Shader("outline");
_light = new Shader("light");
_bone = new Shader("bone");
_collision = new Shader("collision", "bone");
Picking.Setup();
Options.SetupModelsAndLights();
}
public void Render()
{
var viewMatrix = CameraOp.GetViewMatrix();
var projMatrix = CameraOp.GetProjectionMatrix();
if (ShowSkybox) _skybox.Render(viewMatrix, projMatrix);
if (ShowGrid) _grid.Render(viewMatrix, projMatrix, CameraOp.Near, CameraOp.Far);
_shader.Render(viewMatrix, CameraOp.Position, projMatrix);
for (int i = 0; i < 5; i++)
_shader.SetUniform($"bVertexColors[{i}]", i == (int) Color);
// render model pass
foreach (var model in Options.Models.Values)
{
if (!model.IsVisible) continue;
model.Render(_shader, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null);
}
{ // light pass
var uNumLights = Math.Min(Options.Lights.Count, 100);
_shader.SetUniform("uNumLights", ShowLights ? uNumLights : 0);
if (ShowLights)
for (int i = 0; i < uNumLights; i++)
Options.Lights[i].Render(i, _shader);
_light.Render(viewMatrix, projMatrix);
for (int i = 0; i < uNumLights; i++)
Options.Lights[i].Render(_light);
}
// debug + outline pass
if (Options.TryGetModel(out var selected) && selected.IsVisible)
{
if (IsSkeletonTreeOpen && selected is SkeletalModel skeletalModel)
{
_bone.Render(viewMatrix, projMatrix);
skeletalModel.RenderBones(_bone);
}
else if (selected.ShowCollisions)
{
_collision.Render(viewMatrix, projMatrix);
selected.RenderCollision(_collision);
}
_outline.Render(viewMatrix, CameraOp.Position, projMatrix);
selected.Render(_outline, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null, true);
}
// picking pass (dedicated FBO, binding to 0 afterward)
Picking.Render(viewMatrix, projMatrix, Options.Models);
}
public void Update(Snooper wnd, float deltaSeconds)
{
if (Options.Animations.Count > 0) Options.Tracker.Update(deltaSeconds);
foreach (var animation in Options.Animations)
{
animation.TimeCalculation(Options.Tracker.ElapsedTime);
foreach (var guid in animation.AttachedModels)
{
if (Options.Models[guid] is not SkeletalModel skeletalModel) continue;
skeletalModel.Skeleton.UpdateAnimationMatrices(animation, AnimateWithRotationOnly);
}
}
{
foreach (var model in Options.Models.Values)
{
model.Update(Options);
}
if (IsSkeletonTreeOpen && Options.TryGetModel(out var selected) && selected is SkeletalModel { IsVisible: true } skeletalModel)
{
skeletalModel.Skeleton.UpdateVertices();
}
}
CameraOp.Modify(wnd.KeyboardState, deltaSeconds);
if (wnd.KeyboardState.IsKeyPressed(Keys.Z) &&
Options.TryGetModel(out var selectedModel) &&
selectedModel is SkeletalModel)
{
Options.RemoveAnimations();
Options.AnimateMesh(true);
wnd.WindowShouldClose(true, false);
}
if (wnd.KeyboardState.IsKeyPressed(Keys.Space))
Options.Tracker.IsPaused = !Options.Tracker.IsPaused;
if (wnd.KeyboardState.IsKeyPressed(Keys.Delete))
Options.RemoveModel(Options.SelectedModel);
if (wnd.KeyboardState.IsKeyPressed(Keys.H))
wnd.WindowShouldClose(true, false);
if (wnd.KeyboardState.IsKeyPressed(Keys.Escape))
wnd.WindowShouldClose(true, true);
}
private void LoadStaticMesh(UStaticMesh original)
{
var guid = original.LightingGuid;
if (Options.TryGetModel(guid, out var model))
{
model.AddInstance(Transform.Identity);
Application.Current.Dispatcher.Invoke(() => model.SetupInstances());
return;
}
if (!original.TryConvert(out var mesh))
return;
Options.Models[guid] = new StaticModel(original, mesh);
Options.SelectModel(guid);
}
private void LoadSkeletalMesh(USkeletalMesh original)
{
var guid = new FGuid((uint) original.GetFullName().GetHashCode());
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return;
var skeletalModel = new SkeletalModel(original, mesh);
Options.Models[guid] = skeletalModel;
Options.SelectModel(guid);
}
private void LoadSkeleton(USkeleton original)
{
var guid = original.Guid;
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out _, out var box)) return;
var fakeSkeletalModel = new SkeletalModel(original, box);
Options.Models[guid] = fakeSkeletalModel;
Options.SelectModel(guid);
IsSkeletonTreeOpen = true;
}
private void LoadMaterialInstance(UMaterialInstance original)
{
if (!Utils.TryLoadObject("Engine/Content/BasicShapes/Cube.Cube", out UStaticMesh editorCube))
return;
var guid = editorCube.LightingGuid;
if (Options.TryGetModel(guid, out var model))
{
model.Materials[0].SwapMaterial(original);
Application.Current.Dispatcher.Invoke(() => model.Materials[0].Setup(Options, model.UvCount));
return;
}
if (!editorCube.TryConvert(out var mesh))
return;
Options.Models[guid] = new StaticModel(original, mesh);
Options.SelectModel(guid);
}
private void LoadPaperSprite(UPaperSprite original)
{
if (!(original.BakedSourceTexture?.TryLoad(out UTexture2D texture) ?? false))
return;
var guid = texture.LightingGuid;
if (Options.TryGetModel(guid, out var model))
{
model.AddInstance(Transform.Identity);
Application.Current.Dispatcher.Invoke(() => model.SetupInstances());
return;
}
Options.Models[guid] = new StaticModel(original, texture);
Options.SelectModel(guid);
}
private void SetupCamera()
{
if (Options.TryGetModel(out var model))
CameraOp.Setup(model.Box);
}
private void LoadWorld(CancellationToken cancellationToken, UWorld original, Transform transform)
{
CameraOp.Setup(new FBox(FVector.ZeroVector, new FVector(0, 10, 10)));
if (original.PersistentLevel.Load<ULevel>() is not { } persistentLevel)
return;
if (persistentLevel.TryGetValue(out FSoftObjectPath runtimeCell, "WorldPartitionRuntimeCell") &&
Utils.TryLoadObject(runtimeCell.AssetPathName.Text.SubstringBeforeWithLast(".") + runtimeCell.SubPathString.SubstringAfterLast("."), out UObject worldPartition))
{
var position = worldPartition.GetOrDefault("Position", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO;
var box = worldPartition.GetOrDefault("ContentBounds", new FBox(FVector.ZeroVector, FVector.OneVector));
box *= MathF.Pow(Constants.SCALE_DOWN_RATIO, 2);
CameraOp.Teleport(new Vector3(position.X, position.Z, position.Y), box, true);
}
var length = persistentLevel.Actors.Length;
for (var i = 0; i < length; i++)
{
cancellationToken.ThrowIfCancellationRequested();
if (persistentLevel.Actors[i].Load() is not { } actor ||
actor.ExportType is "LODActor" or "SplineMeshActor")
continue;
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {i}/{length}");
WorldCamera(actor);
WorldLight(actor);
WorldMesh(actor, transform);
AdditionalWorlds(actor, transform.Matrix, cancellationToken);
}
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}");
}
private void LoadJunoWorld(CancellationToken cancellationToken, UBlueprintGeneratedClass original, Transform transform)
{
CameraOp.Setup(new FBox(FVector.ZeroVector, new FVector(0, 10, 10)));
var length = 0;
FPackageIndex[] allNodes = [];
IPropertyHolder[] records = [];
if (original.TryGetValue(out FPackageIndex simpleConstructionScript, "SimpleConstructionScript") &&
simpleConstructionScript.TryLoad(out var scs) && scs.TryGetValue(out allNodes, "AllNodes"))
length = allNodes.Length;
else if (original.TryGetValue(out FPackageIndex inheritableComponentHandler, "InheritableComponentHandler") &&
inheritableComponentHandler.TryLoad(out var ich) && ich.TryGetValue(out records, "Records"))
length = records.Length;
for (var i = 0; i < length; i++)
{
cancellationToken.ThrowIfCancellationRequested();
IPropertyHolder actor;
if (allNodes is {Length: > 0} && allNodes[i].TryLoad(out UObject node))
{
actor = node;
}
else if (records is {Length: > 0})
{
actor = records[i];
}
else continue;
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {i}/{length}");
WorldMesh(actor, transform, true);
}
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}");
if (Options.Models.Count == 1)
{
var (guid, model) = Options.Models.First();
Options.SelectModel(guid);
CameraOp.Setup(model.Box);
_saveCameraMode = true;
}
}
private void WorldCamera(UObject actor)
{
if (actor.ExportType != "LevelBounds" || !actor.TryGetValue(out FPackageIndex boxComponent, "BoxComponent") ||
boxComponent.Load() is not { } boxObject) return;
var direction = boxObject.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO;
var position = boxObject.GetOrDefault("RelativeScale3D", FVector.OneVector) / 2f * Constants.SCALE_DOWN_RATIO;
CameraOp.Setup(new FBox(direction, position));
}
private void WorldLight(UObject actor)
{
if (!actor.TryGetValue(out FPackageIndex lightComponent, "LightComponent") ||
lightComponent.Load() is not { } lightObject) return;
switch (actor.ExportType)
{
case "PointLight":
Options.Lights.Add(new PointLight(Options.Icons["pointlight"], lightObject));
break;
case "SpotLight":
Options.Lights.Add(new SpotLight(Options.Icons["spotlight"], lightObject));
break;
case "RectLight":
case "SkyLight":
case "DirectionalLight":
break;
}
}
private void WorldMesh(IPropertyHolder actor, Transform transform, bool forceShow = false)
{
if (actor.TryGetValue(out FPackageIndex[] instanceComponents, "InstanceComponents"))
{
foreach (var component in instanceComponents)
{
if (!component.TryLoad(out UInstancedStaticMeshComponent staticMeshComp) ||
!staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) || m.Materials.Length < 1)
continue;
if (staticMeshComp.PerInstanceSMData is { Length: > 0 })
{
var relation = CalculateTransform(staticMeshComp, transform);
foreach (var perInstanceData in staticMeshComp.PerInstanceSMData)
{
ProcessMesh(actor, staticMeshComp, m, new Transform
{
Relation = relation.Matrix,
Position = perInstanceData.TransformData.Translation * Constants.SCALE_DOWN_RATIO,
Rotation = perInstanceData.TransformData.Rotation,
Scale = perInstanceData.TransformData.Scale3D
});
}
}
else ProcessMesh(actor, staticMeshComp, m, CalculateTransform(staticMeshComp, transform));
}
}
else if (actor.TryGetValue(out FPackageIndex componentTemplate, "ComponentTemplate") &&
componentTemplate.TryLoad(out UObject compTemplate))
{
UGeometryCollection geometryCollection = null;
if (!compTemplate.TryGetValue(out UStaticMesh m, "StaticMesh") &&
compTemplate.TryGetValue(out FPackageIndex restCollection, "RestCollection") &&
restCollection.TryLoad(out geometryCollection) && geometryCollection.RootProxyData is { ProxyMeshes.Length: > 0 } rootProxyData)
{
rootProxyData.ProxyMeshes[0].TryLoad(out m);
}
if (m is { Materials.Length: > 0 })
{
OverrideJunoVertexColors(m, geometryCollection);
ProcessMesh(actor, compTemplate, m, CalculateTransform(compTemplate, transform), forceShow);
}
}
else if (actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "ComponentTemplate", "StaticMesh", "Mesh", "LightMesh") &&
staticMeshComponent.TryLoad(out UStaticMeshComponent staticMeshComp) &&
staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) && m.Materials.Length > 0)
{
ProcessMesh(actor, staticMeshComp, m, CalculateTransform(staticMeshComp, transform));
}
}
private void ProcessMesh(IPropertyHolder actor, UStaticMeshComponent staticMeshComp, UStaticMesh m, Transform transform)
{
OverrideVertexColors(staticMeshComp, m);
ProcessMesh(actor, staticMeshComp, m, transform, false);
}
private void ProcessMesh(IPropertyHolder actor, UObject staticMeshComp, UStaticMesh m, Transform transform, bool forceShow)
{
var guid = m.LightingGuid;
if (Options.TryGetModel(guid, out var model))
{
model.AddInstance(transform);
}
else if (m.TryConvert(out var mesh))
{
model = new StaticModel(m, mesh, transform);
model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided));
if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
{
var material = model.Materials.FirstOrDefault();
if (material is { IsUsed: true })
{
for (int j = 0; j < textureData.Length; j++)
{
if (textureData[j]?.Load() is not { } textureDataIdx)
continue;
if (textureDataIdx.TryGetValue(out FPackageIndex overrideMaterial, "OverrideMaterial") &&
overrideMaterial.TryLoad(out var oMaterial) && oMaterial is UMaterialInterface oUnrealMaterial)
material.SwapMaterial(oUnrealMaterial);
WorldTextureData(material, textureDataIdx, "Diffuse", j switch
{
0 => "Diffuse",
> 0 => $"Diffuse_Texture_{j + 1}",
_ => CMaterialParams2.FallbackDiffuse
});
WorldTextureData(material, textureDataIdx, "Normal", j switch
{
0 => "Normals",
> 0 => $"Normals_Texture_{j + 1}",
_ => CMaterialParams2.FallbackNormals
});
WorldTextureData(material, textureDataIdx, "Specular", j switch
{
0 => "SpecularMasks",
> 0 => $"SpecularMasks_{j + 1}",
_ => CMaterialParams2.FallbackNormals
});
}
}
}
if (staticMeshComp.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials"))
{
for (var j = 0; j < overrideMaterials.Length && j < model.Sections.Length; j++)
{
var matIndex = model.Sections[j].MaterialIndex;
if (matIndex < 0 || matIndex >= model.Materials.Length || matIndex >= overrideMaterials.Length ||
overrideMaterials[matIndex].Load() is not UMaterialInterface unrealMaterial) continue;
model.Materials[matIndex].SwapMaterial(unrealMaterial);
}
}
if (forceShow)
{
foreach (var section in model.Sections)
{
section.Show = true;
}
}
Options.Models[guid] = model;
}
if (actor.TryGetValue(out FPackageIndex treasureLight, "PointLight", "TreasureLight") &&
treasureLight.TryLoad(out var pl1) && pl1.Template.TryLoad(out var pl2))
{
Options.Lights.Add(new PointLight(guid, Options.Icons["pointlight"], pl1, pl2, transform));
}
if (actor.TryGetValue(out FPackageIndex spotLight, "SpotLight") &&
spotLight.TryLoad(out var sl1) && sl1.Template.TryLoad(out var sl2))
{
Options.Lights.Add(new SpotLight(guid, Options.Icons["spotlight"], sl1, sl2, transform));
}
}
private Transform CalculateTransform(IPropertyHolder staticMeshComp, Transform relation)
{
return new Transform
{
Relation = relation.Matrix,
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion(),
Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector)
};
}
private void OverrideJunoVertexColors(UStaticMesh staticMesh, UGeometryCollection geometryCollection = null)
{
if (staticMesh.RenderData is not { LODs.Length: > 0 } || staticMesh.RenderData.LODs[0].ColorVertexBuffer == null)
return;
var dico = new Dictionary<byte, FColor>();
if (geometryCollection?.Materials is not { Length: > 0 })
{
var distinctReds = new HashSet<byte>();
for (int i = 0; i < staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data.Length; i++)
{
ref var vertexColor = ref staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data[i];
var indexAsByte = vertexColor.R;
if (indexAsByte == 255) indexAsByte = vertexColor.A;
distinctReds.Add(indexAsByte);
}
foreach (var indexAsByte in distinctReds)
{
var path = string.Concat("/JunoAtomAssets/Materials/MI_LegoStandard_", indexAsByte, ".MI_LegoStandard_", indexAsByte);
if (!Utils.TryLoadObject(path, out UMaterialInterface unrealMaterial))
continue;
var parameters = new CMaterialParams2();
unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer);
if (!parameters.TryGetLinearColor(out var color, "Color"))
color = FLinearColor.Gray;
dico[indexAsByte] = color.ToFColor(true);
}
}
else foreach (var material in geometryCollection.Materials)
{
if (!material.TryLoad(out UMaterialInterface unrealMaterial)) continue;
var parameters = new CMaterialParams2();
unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer);
if (!byte.TryParse(material.Name.SubstringAfterLast("_"), out var indexAsByte))
indexAsByte = byte.MaxValue;
if (!parameters.TryGetLinearColor(out var color, "Color"))
color = FLinearColor.Gray;
dico[indexAsByte] = color.ToFColor(true);
}
for (int i = 0; i < staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data.Length; i++)
{
ref var vertexColor = ref staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data[i];
vertexColor = dico.TryGetValue(vertexColor.R, out var color) ? color : FColor.Gray;
}
}
private void OverrideVertexColors(UStaticMeshComponent staticMeshComp, UStaticMesh staticMesh)
{
if (staticMeshComp.LODData is not { Length: > 0 } || staticMesh.RenderData is not { LODs.Length: > 0 })
return;
for (var lod = 0; lod < staticMeshComp.LODData.Length; lod++)
{
var vertexColors = staticMeshComp.LODData[lod].OverrideVertexColors;
if (vertexColors == null) continue;
staticMesh.RenderData.LODs[lod].ColorVertexBuffer = vertexColors;
}
}
private void WorldTextureData(Material material, UObject textureData, string name, string key)
{
if (textureData.TryGetValue(out FPackageIndex package, name) && package.Load() is UTexture2D texture)
material.Parameters.Textures[key] = texture;
}
private void AdditionalWorlds(UObject actor, Matrix4x4 relation, CancellationToken cancellationToken)
{
if (!actor.TryGetValue(out FSoftObjectPath[] additionalWorlds, "AdditionalWorlds") ||
!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "Mesh") ||
staticMeshComponent.Load() is not { } staticMeshComp)
return;
var transform = new Transform
{
Relation = relation,
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion()
};
for (int j = 0; j < additionalWorlds.Length; j++)
if (Utils.TryLoadObject(additionalWorlds[j].AssetPathName.Text, out UWorld w))
LoadWorld(cancellationToken, w, transform);
}
public void WindowResized(int width, int height)
{
CameraOp.AspectRatio = width / (float) height;
Picking.WindowResized(width, height);
}
public void Save()
{
Options.ResetModelsLightsAnimations();
Options.SelectModel(Guid.Empty);
Options.SwapMaterial(false);
Options.AnimateMesh(false);
if (_saveCameraMode) UserSettings.Default.CameraMode = CameraOp.Mode;
UserSettings.Default.ShowSkybox = ShowSkybox;
UserSettings.Default.ShowGrid = ShowGrid;
UserSettings.Default.AnimateWithRotationOnly = AnimateWithRotationOnly;
}
public void Dispose()
{
_skybox?.Dispose();
_grid?.Dispose();
_shader?.Dispose();
_outline?.Dispose();
_light?.Dispose();
_bone?.Dispose();
_collision?.Dispose();
Picking?.Dispose();
Options?.Dispose();
}
}