mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/PCK-Studio.git
synced 2026-06-18 04:24:04 +00:00
332 lines
14 KiB
C#
332 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using OMI.Formats.Material;
|
|
using OMI.Formats.Model;
|
|
using OpenTK;
|
|
using OpenTK.Graphics.OpenGL;
|
|
using PckStudio.Core.Extensions;
|
|
using PckStudio.Interfaces;
|
|
using PckStudio.Core.Json;
|
|
using PckStudio.Properties;
|
|
using PckStudio.Rendering.Shader;
|
|
using PckStudio.Rendering.Texture;
|
|
using PckStudio.Core;
|
|
using PckStudio.ModelSupport;
|
|
|
|
namespace PckStudio.Rendering
|
|
{
|
|
internal partial class ModelRenderer : SceneViewport
|
|
{
|
|
[Browsable(false)]
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Image Texture
|
|
{
|
|
get => _modelTexture;
|
|
set
|
|
{
|
|
var args = new TextureChangingEventArgs(value);
|
|
Events[nameof(ModelTextureChanging)]?.DynamicInvoke(this, args);
|
|
OnModelTextureChanging(this, args);
|
|
if (!args.Cancel)
|
|
{
|
|
_modelTexture = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Description("Event that gets fired when the skin texture is changing")]
|
|
[Category("Property Chnaged")]
|
|
[Browsable(true)]
|
|
public event EventHandler<TextureChangingEventArgs> ModelTextureChanging
|
|
{
|
|
add => Events.AddHandler(nameof(ModelTextureChanging), value);
|
|
remove => Events.RemoveHandler(nameof(ModelTextureChanging), value);
|
|
}
|
|
|
|
public bool RenderModelBounds { get; set; }
|
|
public string CurrentModelName => _currentModelName;
|
|
|
|
private BoundingBox _maxBounds;
|
|
private string _currentModelName;
|
|
private Image _modelTexture;
|
|
private Texture2D _modelRenderTexture;
|
|
private List<GenericMesh<TextureVertex>> _rootCollection;
|
|
private HighlightInfo _highlightingInfo;
|
|
|
|
private readonly Vector3 modelOffset = Vector3.UnitY * 24f;
|
|
|
|
private struct HighlightInfo
|
|
{
|
|
public static readonly HighlightInfo Empty = new HighlightInfo(Vector3.Zero, Vector3.Zero, BoundingBox.Empty);
|
|
public bool IsEmpty => BoundingBox.Volume.LengthSquared <= 0f;
|
|
public BoundingBox BoundingBox { get; }
|
|
public Vector3 Translation { get; }
|
|
public Vector3 Rotation { get; }
|
|
|
|
public HighlightInfo(System.Numerics.Vector3 translation, System.Numerics.Vector3 rotation, BoundingBox boundingBox)
|
|
: this(translation.ToOpenTKVector(), rotation.ToOpenTKVector(), boundingBox)
|
|
{
|
|
}
|
|
|
|
public HighlightInfo(Vector3 translation, Vector3 rotation, BoundingBox boundingBox)
|
|
{
|
|
Translation = translation;
|
|
Rotation = rotation;
|
|
BoundingBox = boundingBox;
|
|
}
|
|
}
|
|
|
|
public ModelRenderer() : base(fov: 60f)
|
|
{
|
|
InitializeComponent();
|
|
_rootCollection = new List<GenericMesh<TextureVertex>>(5);
|
|
|
|
}
|
|
|
|
protected override void Initialize()
|
|
{
|
|
// render texture
|
|
{
|
|
_modelRenderTexture = new Texture2D();
|
|
_modelRenderTexture.PixelFormat = PixelFormat.Bgra;
|
|
_modelRenderTexture.InternalPixelFormat = PixelInternalFormat.Rgba8;
|
|
_modelRenderTexture.MinFilter = TextureMinFilter.Nearest;
|
|
_modelRenderTexture.MagFilter = TextureMagFilter.Nearest;
|
|
_modelRenderTexture.WrapS = TextureWrapMode.Repeat;
|
|
_modelRenderTexture.WrapT = TextureWrapMode.Repeat;
|
|
}
|
|
|
|
// cubeShader
|
|
{
|
|
var cubeShader = ShaderProgram.Create(
|
|
new ShaderSource(ShaderType.VertexShader, Resources.texturedCubeVertexShader),
|
|
new ShaderSource(ShaderType.FragmentShader, Resources.texturedCubeFragmentShader),
|
|
new ShaderSource(ShaderType.GeometryShader, Resources.texturedCubeGeometryShader)
|
|
);
|
|
cubeShader.Bind();
|
|
cubeShader.SetUniform1("Texture", 0);
|
|
cubeShader.Validate();
|
|
AddShader("CubeShader", cubeShader);
|
|
}
|
|
}
|
|
|
|
public void LoadModel(Model model)
|
|
{
|
|
ResetHighlight();
|
|
_rootCollection?.Clear();
|
|
|
|
_maxBounds = model.GetParts()
|
|
.SelectMany(p => p.GetBoxes().Select(b => new BoundingBox(b.Position + p.Translation, b.Position + p.Translation + b.Size)))
|
|
.GetEnclosingBoundingBox();
|
|
|
|
if (!TryGetModelMetaData(model, out JsonModelMetaData modelMetaData))
|
|
{
|
|
Trace.TraceError($"[{nameof(ModelRenderer)}@{nameof(LoadModel)}] Failed to get meta data for model: '{model.Name}'");
|
|
return;
|
|
}
|
|
|
|
for (int i = -1; i < modelMetaData.UvOffsets.Length; i++)
|
|
{
|
|
bool tryGetPart(string name, out ModelPart modelPart)
|
|
{
|
|
if (!model.TryGetPart(name, out ModelPart originalModelPart))
|
|
{
|
|
modelPart = default;
|
|
return false;
|
|
}
|
|
if (i == -1 || !modelMetaData.UvOffsets.IndexInRange(i))
|
|
{
|
|
modelPart = originalModelPart;
|
|
return true;
|
|
}
|
|
System.Numerics.Vector2 uvoffset = modelMetaData.UvOffsets[i];
|
|
modelPart = new ModelPart(originalModelPart.Name, originalModelPart.ParentName, originalModelPart.Translation, originalModelPart.Rotation, originalModelPart.AdditionalRotation);
|
|
modelPart.AddBoxes(originalModelPart.GetBoxes().Select(box => new ModelBox(box.Position, box.Size, box.Uv + uvoffset, box.Inflate, box.Mirror)));
|
|
return true;
|
|
}
|
|
_rootCollection.AddRange(BuildModelMesh(modelMetaData.RootParts,
|
|
Vector3.Zero, Vector3.Zero, Vector3.Zero,
|
|
TryGet<string, ModelPart>.FromDelegate(tryGetPart)));
|
|
}
|
|
|
|
|
|
if (Context.IsCurrent)
|
|
{
|
|
ShaderProgram shader = GetShader("CubeShader");
|
|
shader.Bind();
|
|
shader.SetUniform2("TexSize", model.TextureSize);
|
|
|
|
// Reset "Material"
|
|
GL.Disable(EnableCap.AlphaTest);
|
|
GL.Disable(EnableCap.Blend);
|
|
GL.BlendFunc(BlendingFactor.One, BlendingFactor.Zero);
|
|
}
|
|
ResetCamera();
|
|
_currentModelName = model.Name;
|
|
}
|
|
|
|
private bool TryGetModelMetaData(Model model, out JsonModelMetaData modelMetaData)
|
|
{
|
|
if (!GameModelImporter.ModelMetaData.TryGetValue(model.Name, out modelMetaData))
|
|
{
|
|
Trace.TraceError($"[{nameof(ModelRenderer)}@{nameof(TryGetModelMetaData)}] Couldn't get meta data for model: '{model.Name}'");
|
|
return false;
|
|
}
|
|
|
|
if (modelMetaData.RootParts.Length == 0)
|
|
{
|
|
modelMetaData.RootParts = model.GetParts().Select(p => new ModelMetaDataPart(p.Name)).ToArray();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public override void ResetCamera(Vector3 offset)
|
|
{
|
|
Vector3 center = (_maxBounds.Start + _maxBounds.End) / 2f;
|
|
Camera.FocalPoint = Vector3.TransformPosition(center - modelOffset + offset, Matrix4.CreateScale(-1f, 1f, 1f));
|
|
Camera.Distance = _maxBounds.Volume.Length;
|
|
Camera.Yaw = 45f;
|
|
Camera.Pitch = 25f;
|
|
}
|
|
|
|
private List<GenericMesh<TextureVertex>> BuildModelMesh(ModelMetaDataPart[] metaDataParts, Vector3 parentTranslation, Vector3 parentRotation, Vector3 parentPivot, ITryGet<string, ModelPart> model)
|
|
{
|
|
List<GenericMesh<TextureVertex>> meshes = new List<GenericMesh<TextureVertex>>();
|
|
foreach (ModelMetaDataPart metaDataPart in metaDataParts)
|
|
{
|
|
if (!model.TryGet(metaDataPart.Name, out ModelPart modelPart))
|
|
{
|
|
Trace.TraceError($"[{nameof(ModelRenderer)}@{nameof(BuildModelMesh)}] : Failed to find part: '{metaDataPart.Name}'");
|
|
continue;
|
|
}
|
|
Vector3 translation = modelPart.Translation.ToOpenTKVector();
|
|
Vector3 pivot = translation * -1;
|
|
Vector3 rotation = (modelPart.Rotation.ToOpenTKVector() + modelPart.AdditionalRotation.ToOpenTKVector());
|
|
CubeMeshCollection cubeCollection = new CubeMeshCollection(modelPart.Name, translation - parentTranslation, pivot - parentPivot, rotation - parentRotation);
|
|
cubeCollection.FlipZMapping = true;
|
|
foreach (ModelBox boxes in modelPart.GetBoxes())
|
|
{
|
|
cubeCollection.AddNamed(modelPart.Name, boxes.Position.ToOpenTKVector(), boxes.Size.ToOpenTKVector(), boxes.Uv.ToOpenTKVector(), boxes.Inflate, boxes.Mirror);
|
|
}
|
|
meshes.Add(cubeCollection);
|
|
BuildModelMesh(metaDataPart.Children, translation, rotation, pivot, model).ForEach(cubeCollection.Add);
|
|
}
|
|
return meshes;
|
|
}
|
|
|
|
protected virtual void OnModelTextureChanging(object sender, TextureChangingEventArgs e)
|
|
{
|
|
if (e.NewTexture is null)
|
|
e.Cancel = true;
|
|
|
|
if (e.Cancel)
|
|
return;
|
|
|
|
if (Context.IsCurrent)
|
|
{
|
|
_modelRenderTexture.SetTexture(e.NewTexture);
|
|
GLErrorCheck();
|
|
}
|
|
}
|
|
|
|
protected override void OnPaint(PaintEventArgs e)
|
|
{
|
|
base.OnPaint(e);
|
|
if (DesignMode)
|
|
return;
|
|
if (!Context.IsCurrent)
|
|
return;
|
|
|
|
GL.Enable(EnableCap.Texture2D); // Enable textures
|
|
|
|
GL.DepthFunc(DepthFunction.Lequal);
|
|
|
|
ShaderProgram shader = GetShader("CubeShader");
|
|
|
|
_modelRenderTexture.Bind(slot: 0);
|
|
|
|
Vector3 scaleVector = new Vector3(1f, -1f, -1f);
|
|
Matrix4 renderTransform = Matrix4.CreateScale(scaleVector) * Matrix4.CreateTranslation(modelOffset);
|
|
|
|
foreach (GenericMesh<TextureVertex> item in _rootCollection)
|
|
{
|
|
DrawMesh(item, shader, item.GetTransform() * renderTransform);
|
|
}
|
|
_modelRenderTexture.Unbind();
|
|
|
|
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
|
|
if (!_highlightingInfo.IsEmpty)
|
|
{
|
|
Matrix4 highlightMatrix = Matrix4.Identity;
|
|
highlightMatrix *= Matrix4.CreateRotationX(MathHelper.DegreesToRadians(_highlightingInfo.Rotation.X));
|
|
highlightMatrix *= Matrix4.CreateRotationY(MathHelper.DegreesToRadians(_highlightingInfo.Rotation.Y));
|
|
highlightMatrix *= Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(_highlightingInfo.Rotation.Z));
|
|
highlightMatrix = Matrix4.CreateTranslation(_highlightingInfo.Translation) * highlightMatrix.Pivoted(_highlightingInfo.Translation * -1);
|
|
|
|
highlightMatrix *= renderTransform;
|
|
DrawBoundingBox(highlightMatrix, _highlightingInfo.BoundingBox, Color.HotPink);
|
|
}
|
|
|
|
if (RenderModelBounds)
|
|
{
|
|
DrawBoundingBox(renderTransform, _maxBounds, Color.Red);
|
|
}
|
|
}
|
|
|
|
internal void Highlight(ModelPart part)
|
|
{
|
|
BoundingBox bb = part.GetBoxes().Select(b => b.GetBoundingBox()).GetEnclosingBoundingBox();
|
|
_highlightingInfo = new HighlightInfo(part.Translation, part.Rotation + part.AdditionalRotation, bb);
|
|
}
|
|
|
|
internal void Highlight(ModelBox box, ModelPart parent)
|
|
{
|
|
BoundingBox bb = box.GetBoundingBox();
|
|
_highlightingInfo = new HighlightInfo(parent.Translation, parent.Rotation + parent.AdditionalRotation, bb);
|
|
}
|
|
|
|
internal void ResetHighlight()
|
|
{
|
|
_highlightingInfo = HighlightInfo.Empty;
|
|
}
|
|
|
|
internal void SetModelMaterial(MaterialContainer.Material entityMaterial)
|
|
{
|
|
_ = entityMaterial ?? throw new ArgumentNullException(nameof(entityMaterial));
|
|
switch (entityMaterial.Type)
|
|
{
|
|
// todo
|
|
//case "entity":
|
|
// break;
|
|
//case "entity_change_color":
|
|
// break;
|
|
case "entity_alphatest":
|
|
GL.Enable(EnableCap.AlphaTest);
|
|
break;
|
|
case "entity_alphatest_change_color":
|
|
GL.Enable(EnableCap.AlphaTest);
|
|
goto default;
|
|
case "entity_emissive_alpha":
|
|
GL.Enable(EnableCap.Blend);
|
|
GL.BlendFunc(BlendingFactor.One, BlendingFactor.Zero);
|
|
break;
|
|
case "entity_emissive_alpha_only":
|
|
GL.Enable(EnableCap.Blend);
|
|
GL.BlendFuncSeparate(BlendingFactorSrc.One, BlendingFactorDest.One, BlendingFactorSrc.One, BlendingFactorDest.Zero);
|
|
break;
|
|
default:
|
|
Debug.WriteLine($"[{nameof(ModelRenderer)}@{nameof(SetModelMaterial)}]: Unhandled entity material(type: {entityMaterial.Type})");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|