mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/PCK-Studio.git
synced 2026-06-12 20:42:41 +00:00
1505 lines
63 KiB
C#
1505 lines
63 KiB
C#
/* Copyright (c) 2024-present miku-666
|
|
* This software is provided 'as-is', without any express or implied
|
|
* warranty. In no event will the authors be held liable for any damages
|
|
* arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
*
|
|
* 1.The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software
|
|
* in a product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software.
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
**/
|
|
//#define USE_FRAMEBUFFER
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using OpenTK;
|
|
using PckStudio.Internal;
|
|
using PckStudio.Extensions;
|
|
using OpenTK.Graphics.OpenGL;
|
|
using System.Windows.Forms;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using PckStudio.Properties;
|
|
using PckStudio.Forms.Editor;
|
|
using System.Collections.ObjectModel;
|
|
using System.Collections.Specialized;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using PckStudio.Rendering.Texture;
|
|
using PckStudio.Rendering.Shader;
|
|
using System.Linq;
|
|
|
|
namespace PckStudio.Rendering
|
|
{
|
|
internal partial class SkinRenderer : SceneViewport
|
|
{
|
|
/// <summary>
|
|
/// The visible Texture on the renderer
|
|
/// </summary>
|
|
/// <returns>The visible Texture</returns>
|
|
[Description("The current skin texture")]
|
|
[Category("Appearance")]
|
|
public Image Texture
|
|
{
|
|
get => _skinImage;
|
|
set
|
|
{
|
|
var args = new TextureChangingEventArgs(value);
|
|
Events[nameof(TextureChanging)]?.DynamicInvoke(this, args);
|
|
OnTextureChanging(this, args);
|
|
if (!args.Cancel)
|
|
{
|
|
_skinImage = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Description("The current cape texture")]
|
|
[Category("Appearance")]
|
|
public Image CapeTexture
|
|
{
|
|
get => _capeImage;
|
|
set
|
|
{
|
|
var args = new TextureChangingEventArgs(value);
|
|
Events[nameof(CapeTextureChanging)]?.DynamicInvoke(this, args);
|
|
OnCapeTextureChanging(this, args);
|
|
if (!args.Cancel)
|
|
{
|
|
_capeImage = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Description("The Color used for outlines")]
|
|
[Category("Appearance")]
|
|
public Color OutlineColor { get; set; }
|
|
|
|
public float MouseSensetivity { get; set; } = 0.01f;
|
|
public int SelectedIndex { get; set; } = -1;
|
|
|
|
public bool ClampModel { get; set; } = false;
|
|
public bool ShowArmor { get; set; } = false;
|
|
public bool ShowGuideLines
|
|
{
|
|
get => guidelineMode != GuidelineMode.None;
|
|
set
|
|
{
|
|
if (value)
|
|
{
|
|
guidelineMode = GuidelineMode.Cubical;
|
|
return;
|
|
}
|
|
guidelineMode = GuidelineMode.None;
|
|
}
|
|
}
|
|
|
|
[Description("Event that gets fired when the skin texture is changing")]
|
|
[Category("Property Chnaged")]
|
|
[Browsable(true)]
|
|
public event EventHandler<TextureChangingEventArgs> TextureChanging
|
|
{
|
|
add => Events.AddHandler(nameof(TextureChanging), value);
|
|
remove => Events.RemoveHandler(nameof(TextureChanging), value);
|
|
}
|
|
|
|
[Description("Event that gets fired when the cape texture is changing")]
|
|
[Category("Property Chnaged")]
|
|
[Browsable(true)]
|
|
public event EventHandler<TextureChangingEventArgs> CapeTextureChanging
|
|
{
|
|
add => Events.AddHandler(nameof(CapeTextureChanging), value);
|
|
remove => Events.RemoveHandler(nameof(CapeTextureChanging), value);
|
|
}
|
|
|
|
[Browsable(false)]
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public SkinANIM ANIM
|
|
{
|
|
get => _anim;
|
|
set
|
|
{
|
|
_anim = value;
|
|
OnANIMUpdate();
|
|
if (initialized)
|
|
{
|
|
MakeCurrent();
|
|
UploadMeshData();
|
|
}
|
|
}
|
|
}
|
|
|
|
public ObservableCollection<SkinBOX> ModelData { get; }
|
|
|
|
/// <summary>
|
|
/// Captures the currently displayed frame
|
|
/// </summary>
|
|
/// <returns>Image of the cameras current view</returns>
|
|
// TODO: add thumbnail size argument
|
|
public Image GetThumbnail()
|
|
{
|
|
Bitmap bmp = new Bitmap(Width, Height);
|
|
BitmapData data = bmp.LockBits(ClientRectangle, ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
|
|
MakeCurrent();
|
|
GL.Finish();
|
|
GL.ReadPixels(0, 0, Width, Height, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
|
|
bmp.UnlockBits(data);
|
|
bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
|
return bmp;
|
|
}
|
|
|
|
private enum GuidelineMode
|
|
{
|
|
None = -1,
|
|
Cubical,
|
|
Skeleton
|
|
};
|
|
|
|
private GuidelineMode guidelineMode { get; set; } = GuidelineMode.None;
|
|
|
|
public Size TextureSize { get; private set; } = new Size(64, 64);
|
|
public Vector2 TillingFactor => new Vector2(1f / TextureSize.Width, 1f / TextureSize.Height);
|
|
private const float OverlayScale = 0.25f;
|
|
|
|
private bool IsMouseHidden
|
|
{
|
|
get => !Cursor.IsVisible();
|
|
set
|
|
{
|
|
if (value)
|
|
{
|
|
Cursor.Hide();
|
|
return;
|
|
}
|
|
Cursor.Show();
|
|
}
|
|
}
|
|
|
|
private Point PreviousMouseLocation;
|
|
private Point CurrentMouseLocation;
|
|
|
|
private ShaderLibrary _shaders;
|
|
private SkinANIM _anim;
|
|
private Image _skinImage;
|
|
private Image _capeImage;
|
|
private Texture2D skinTexture;
|
|
private Texture2D capeTexture;
|
|
private Texture2D armorTexture;
|
|
|
|
#if USE_FRAMEBUFFER
|
|
private FrameBuffer framebuffer;
|
|
private Texture2D framebufferTexture;
|
|
private ShaderProgram framebufferShader;
|
|
private VertexArray framebufferVAO;
|
|
private int framebufferRenderBuffer;
|
|
#endif
|
|
|
|
private DrawContext _cubicalDrawContext;
|
|
private DrawContext _skeletonDrawContext;
|
|
private DrawContext _groundDrawContext;
|
|
|
|
private DrawContext _skyboxRenderBuffer;
|
|
private CubeTexture _skyboxTexture;
|
|
|
|
private Dictionary<string, CubeGroupMesh> meshStorage;
|
|
private Dictionary<string, CubeGroupMesh> offsetSpecificMeshStorage;
|
|
|
|
private CubeGroupMesh cape;
|
|
|
|
private CubeGroupMesh head;
|
|
private CubeGroupMesh body;
|
|
private CubeGroupMesh rightArm;
|
|
private CubeGroupMesh leftArm;
|
|
private CubeGroupMesh rightLeg;
|
|
private CubeGroupMesh leftLeg;
|
|
|
|
private float animationCurrentRotationAngle;
|
|
private float animationRotationSpeed = 0.5f;
|
|
private float animationMaxAngleInDegrees = 5f;
|
|
|
|
private bool showWireFrame = false;
|
|
private bool autoInflateOverlayParts;
|
|
|
|
private float defaultArmRotation => 5f;
|
|
|
|
private Matrix4 RightArmMatrix => Matrix4.CreateFromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(-defaultArmRotation));
|
|
private Matrix4 LeftArmMatrix => Matrix4.CreateFromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(defaultArmRotation));
|
|
|
|
private static Vector3[] cubeVertices = new Vector3[]
|
|
{
|
|
// front
|
|
new Vector3(-1.0f, -1.0f, 1.0f),
|
|
new Vector3( 1.0f, -1.0f, 1.0f),
|
|
new Vector3( 1.0f, 1.0f, 1.0f),
|
|
new Vector3(-1.0f, 1.0f, 1.0f),
|
|
// back
|
|
new Vector3(-1.0f, -1.0f, -1.0f),
|
|
new Vector3( 1.0f, -1.0f, -1.0f),
|
|
new Vector3( 1.0f, 1.0f, -1.0f),
|
|
new Vector3(-1.0f, 1.0f, -1.0f)
|
|
};
|
|
|
|
private static Vector4[] rectVertices = new Vector4[]
|
|
{
|
|
new Vector4( 1.0f, -1.0f, 1.0f, 0.0f),
|
|
new Vector4(-1.0f, -1.0f, 0.0f, 0.0f),
|
|
new Vector4(-1.0f, 1.0f, 0.0f, 1.0f),
|
|
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f),
|
|
new Vector4( 1.0f, -1.0f, 1.0f, 0.0f),
|
|
new Vector4(-1.0f, 1.0f, 0.0f, 1.0f),
|
|
};
|
|
private bool initialized = false;
|
|
|
|
public SkinRenderer() : base()
|
|
{
|
|
InitializeSkinData();
|
|
InitializeCapeData();
|
|
meshStorage = new Dictionary<string, CubeGroupMesh>()
|
|
{
|
|
{ "HEAD", head },
|
|
{ "BODY", body },
|
|
{ "ARM0", rightArm },
|
|
{ "ARM1", leftArm },
|
|
{ "LEG0", rightLeg },
|
|
{ "LEG1", leftLeg },
|
|
|
|
{ "HEADWEAR", head },
|
|
{ "JACKET" , body },
|
|
{ "SLEEVE0" , rightArm },
|
|
{ "SLEEVE1" , leftArm },
|
|
{ "PANTS0" , rightLeg },
|
|
{ "PANTS1" , leftLeg },
|
|
};
|
|
InitializeArmorData();
|
|
InitializeCamera();
|
|
InitializeComponent();
|
|
InitializeDebug();
|
|
|
|
_shaders = new ShaderLibrary();
|
|
ANIM ??= new SkinANIM(SkinAnimMask.RESOLUTION_64x64);
|
|
ModelData = new ObservableCollection<SkinBOX>();
|
|
ModelData.CollectionChanged += ModelData_CollectionChanged;
|
|
}
|
|
|
|
public void Initialize(bool inflateOverlayParts)
|
|
{
|
|
if (initialized)
|
|
Debug.Fail("Already Initialized!");
|
|
autoInflateOverlayParts = inflateOverlayParts;
|
|
MakeCurrent();
|
|
InitializeShaders();
|
|
InitializeFramebuffer();
|
|
Renderer.SetClearColor(BackColor);
|
|
var layout = CubeGroupMesh.GetLayout();
|
|
foreach (var item in meshStorage)
|
|
{
|
|
item.Value.Initialize(layout);
|
|
}
|
|
UploadMeshData();
|
|
foreach (var cubeMesh in offsetSpecificMeshStorage?.Values)
|
|
{
|
|
cubeMesh.Initialize(layout);
|
|
cubeMesh.UploadData();
|
|
}
|
|
cape.Initialize(layout);
|
|
cape.UploadData();
|
|
GLErrorCheck();
|
|
base.Init();
|
|
GLErrorCheck();
|
|
initialized = true;
|
|
}
|
|
|
|
private const float DefaultCameraDistance = 64f;
|
|
private void InitializeCamera()
|
|
{
|
|
Camera.Distance = DefaultCameraDistance;
|
|
Camera.FocalPoint = head.GetCenter(0);
|
|
}
|
|
|
|
private void InitializeSkinData()
|
|
{
|
|
head ??= new CubeGroupMesh("Head") { FlipZMapping = true };
|
|
head.AddCube(new(-4, -8, -4), new(8, 8, 8), new(0, 0));
|
|
head.AddCube(new(-4, -8, -4), new(8, 8, 8), new(32, 0), OverlayScale * 2);
|
|
|
|
body ??= new CubeGroupMesh("Body");
|
|
body.AddCube(new(-4, 0, -2), new(8, 12, 4), new(16, 16));
|
|
body.AddCube(new(-4, 0, -2), new(8, 12, 4), new(16, 32), OverlayScale);
|
|
|
|
var rightArmInfo = ModelPartSpecifics.GetPositioningInfo("ARM0");
|
|
rightArm ??= new CubeGroupMesh("Right Arm", rightArmInfo.Translation.ToOpenTKVector(), rightArmInfo.Pivot.ToOpenTKVector());
|
|
rightArm.AddCube(new(-3, -2, -2), new(4, 12, 4), new(40, 16));
|
|
rightArm.AddCube(new(-3, -2, -2), new(4, 12, 4), new(40, 32), OverlayScale);
|
|
|
|
var leftArmInfo = ModelPartSpecifics.GetPositioningInfo("ARM1");
|
|
leftArm ??= new CubeGroupMesh("Left Arm", leftArmInfo.Translation.ToOpenTKVector(), leftArmInfo.Pivot.ToOpenTKVector());
|
|
leftArm.AddCube(new(-1, -2, -2), new(4, 12, 4), new(32, 48));
|
|
leftArm.AddCube(new(-1, -2, -2), new(4, 12, 4), new(48, 48), inflate: OverlayScale);
|
|
|
|
var rightLegInfo = ModelPartSpecifics.GetPositioningInfo("LEG0");
|
|
rightLeg ??= new CubeGroupMesh("Right Leg", rightLegInfo.Translation.ToOpenTKVector(), rightLegInfo.Pivot.ToOpenTKVector());
|
|
rightLeg.AddCube(new(-2, 0, -2), new(4, 12, 4), new(0, 16));
|
|
rightLeg.AddCube(new(-2, 0, -2), new(4, 12, 4), new(0, 32), OverlayScale);
|
|
|
|
var leftLegInfo = ModelPartSpecifics.GetPositioningInfo("LEG1");
|
|
leftLeg ??= new CubeGroupMesh("Left Leg", leftLegInfo.Translation.ToOpenTKVector(), leftLegInfo.Pivot.ToOpenTKVector());
|
|
leftLeg.AddCube(new(-2, 0, -2), new(4, 12, 4), new(16, 48));
|
|
leftLeg.AddCube(new(-2, 0, -2), new(4, 12, 4), new(0, 48), OverlayScale);
|
|
}
|
|
|
|
private void InitializeCapeData()
|
|
{
|
|
cape ??= new CubeGroupMesh("Cape");
|
|
cape.AddCube(new(-5, 0, -3), new(10, 16, 1), new(0, 0));
|
|
}
|
|
|
|
private void InitializeArmorData()
|
|
{
|
|
const float armorInflation = 0.75f;
|
|
|
|
var helmet = new CubeGroupMesh("HELMET");
|
|
helmet.AddCube(new(-4, -8, -4), new(8, 8, 8), new(0, 0), inflate: armorInflation);
|
|
|
|
var chest = new CubeGroupMesh("CHEST");
|
|
chest.AddCube(new(-4, 0, -2), new(8, 12, 4), new(16, 16), inflate: armorInflation + 0.01f);
|
|
|
|
var shoulder0 = new CubeGroupMesh("SHOULDER0", rightArm.Translation, rightArm.Pivot);
|
|
shoulder0.AddCube(new(-3, -2, -2), new(4, 12, 4), new(40, 16), inflate: armorInflation);
|
|
|
|
var shoulder1 = new CubeGroupMesh("SHOULDER1", leftArm.Translation, leftArm.Pivot);
|
|
shoulder1.AddCube(new(-1, -2, -2), new(4, 12, 4), new(40, 16), inflate: armorInflation, mirrorTexture: true);
|
|
|
|
var waist = new CubeGroupMesh("WAIST");
|
|
waist.AddCube(new(-4, 0, -2), new(8, 12, 4), new(16, 48), inflate: armorInflation);
|
|
|
|
var pants0 = new CubeGroupMesh("PANTS0", rightLeg.Translation, rightLeg.Pivot);
|
|
pants0.AddCube(new(-2, 0, -2), new(4, 12, 4), new(0, 48), inflate: armorInflation);
|
|
|
|
var pants1 = new CubeGroupMesh("PANTS1", leftLeg.Translation, leftLeg.Pivot);
|
|
pants1.AddCube(new(-2, 0, -2), new(4, 12, 4), new(0, 48), inflate: armorInflation, mirrorTexture: true);
|
|
|
|
var boot0 = new CubeGroupMesh("BOOT0", rightLeg.Translation, rightLeg.Pivot);
|
|
boot0.AddCube(new(-2, 0, -2), new(4, 12, 4), new(0, 16), inflate: armorInflation + 0.25f);
|
|
|
|
var boot1 = new CubeGroupMesh("BOOT1", leftLeg.Translation, leftLeg.Pivot);
|
|
boot1.AddCube(new(-2, 0, -2), new(4, 12, 4), new(0, 16), inflate: armorInflation + 0.25f, mirrorTexture: true);
|
|
|
|
offsetSpecificMeshStorage = new Dictionary<string, CubeGroupMesh>
|
|
{
|
|
{ helmet.Name, helmet },
|
|
{ chest.Name, chest },
|
|
{ shoulder0.Name, shoulder0 },
|
|
{ shoulder1.Name, shoulder1 },
|
|
{ waist.Name, waist },
|
|
{ pants0.Name, pants0 },
|
|
{ pants1.Name, pants1 },
|
|
{ boot0.Name, boot0 },
|
|
{ boot1.Name, boot1 }
|
|
};
|
|
|
|
//// TODO
|
|
//{ "TOOL0" , new CubeGroupMesh("TOOL0") },
|
|
//{ "TOOL1" , new CubeGroupMesh("TOOL1") },
|
|
}
|
|
|
|
private void InitializeShaders()
|
|
{
|
|
MakeCurrent();
|
|
|
|
Trace.TraceInformation(GL.GetString(StringName.Version));
|
|
|
|
// Skin shader
|
|
{
|
|
var skinShader = ShaderProgram.Create(
|
|
new ShaderSource(ShaderType.VertexShader, Resources.skinVertexShader),
|
|
new ShaderSource(ShaderType.FragmentShader, Resources.skinFragmentShader),
|
|
new ShaderSource(ShaderType.GeometryShader, Resources.skinGeometryShader)
|
|
);
|
|
skinShader.Bind();
|
|
skinShader.SetUniform1("u_Texture", 0);
|
|
skinShader.Validate();
|
|
_shaders.AddShader("SkinShader", skinShader);
|
|
GLErrorCheck();
|
|
|
|
armorTexture = new Texture2D(0);
|
|
armorTexture.PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Bgra;
|
|
armorTexture.InternalPixelFormat = PixelInternalFormat.Rgba8;
|
|
armorTexture.MinFilter = TextureMinFilter.Nearest;
|
|
armorTexture.MagFilter = TextureMagFilter.Nearest;
|
|
armorTexture.WrapS = TextureWrapMode.Repeat;
|
|
armorTexture.WrapT = TextureWrapMode.Repeat;
|
|
armorTexture.SetTexture(Resources.armor);
|
|
GLErrorCheck();
|
|
|
|
capeTexture = new Texture2D(0);
|
|
capeTexture.PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Bgra;
|
|
capeTexture.InternalPixelFormat = PixelInternalFormat.Rgba8;
|
|
capeTexture.MinFilter = TextureMinFilter.Nearest;
|
|
capeTexture.MagFilter = TextureMagFilter.Nearest;
|
|
capeTexture.WrapS = TextureWrapMode.Repeat;
|
|
capeTexture.WrapT = TextureWrapMode.Repeat;
|
|
GLErrorCheck();
|
|
|
|
skinTexture = new Texture2D(0);
|
|
skinTexture.PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Bgra;
|
|
skinTexture.InternalPixelFormat = PixelInternalFormat.Rgba8;
|
|
skinTexture.MinFilter = TextureMinFilter.Nearest;
|
|
skinTexture.MagFilter = TextureMagFilter.Nearest;
|
|
skinTexture.WrapS = TextureWrapMode.Repeat;
|
|
skinTexture.WrapT = TextureWrapMode.Repeat;
|
|
|
|
GLErrorCheck();
|
|
}
|
|
|
|
// Skybox shader
|
|
{
|
|
var skyboxVAO = new VertexArray();
|
|
var skyboxVBO = new VertexBuffer();
|
|
skyboxVBO.SetData(cubeVertices);
|
|
var vboLayout = new VertexBufferLayout();
|
|
vboLayout.Add(ShaderDataType.Float3);
|
|
skyboxVAO.AddBuffer(skyboxVBO, vboLayout);
|
|
var skybocIBO = IndexBuffer.Create(
|
|
// front
|
|
0, 1, 2,
|
|
2, 3, 0,
|
|
// right
|
|
1, 5, 6,
|
|
6, 2, 1,
|
|
// back
|
|
7, 6, 5,
|
|
5, 4, 7,
|
|
// left
|
|
4, 0, 3,
|
|
3, 7, 4,
|
|
// bottom
|
|
4, 5, 1,
|
|
1, 0, 4,
|
|
// top
|
|
3, 2, 6,
|
|
6, 7, 3);
|
|
|
|
_skyboxRenderBuffer = new DrawContext(skyboxVAO, skybocIBO, PrimitiveType.Triangles);
|
|
|
|
var skyboxShader = ShaderProgram.Create(Resources.skyboxVertexShader, Resources.skyboxFragmentShader);
|
|
skyboxShader.Bind();
|
|
skyboxShader.SetUniform1("skybox", 1);
|
|
skyboxShader.SetUniform1("brightness", 0.8f);
|
|
skyboxShader.Validate();
|
|
_shaders.AddShader("SkyboxShader", skyboxShader);
|
|
|
|
_skyboxTexture = new CubeTexture(1);
|
|
_skyboxTexture.InternalPixelFormat = PixelInternalFormat.Rgb8;
|
|
_skyboxTexture.PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Bgra;
|
|
_skyboxTexture.MinFilter = TextureMinFilter.Linear;
|
|
_skyboxTexture.MagFilter = TextureMagFilter.Linear;
|
|
|
|
_skyboxTexture.WrapS = TextureWrapMode.ClampToEdge;
|
|
_skyboxTexture.WrapT = TextureWrapMode.ClampToEdge;
|
|
_skyboxTexture.WrapR = TextureWrapMode.ClampToEdge;
|
|
|
|
string customSkyboxFilepath = Path.Combine(Program.AppData, "skybox.png");
|
|
using Image skyboxImage = File.Exists(customSkyboxFilepath)
|
|
? Image.FromFile(customSkyboxFilepath)
|
|
: Resources.DefaultSkyTexture;
|
|
_skyboxTexture.SetTexture(skyboxImage);
|
|
GLErrorCheck();
|
|
}
|
|
|
|
#if USE_FRAMEBUFFER
|
|
// Framebuffer shader
|
|
{
|
|
framebufferShader = ShaderProgram.Create(Resources.framebufferVertexShader, Resources.framebufferFragmentShader);
|
|
framebufferShader.Bind();
|
|
framebufferShader.SetUniform1("screenTexture", 0);
|
|
framebufferShader.Validate();
|
|
|
|
GLErrorCheck();
|
|
}
|
|
#endif
|
|
// Plain color shader
|
|
{
|
|
var lineShader = ShaderProgram.Create(Resources.plainColorVertexShader, Resources.plainColorFragmentShader);
|
|
lineShader.Bind();
|
|
lineShader.SetUniform4("baseColor", Color.WhiteSmoke);
|
|
lineShader.SetUniform1("intensity", 0.5f);
|
|
lineShader.Validate();
|
|
_shaders.AddShader("PlainColorShader", lineShader);
|
|
|
|
// Cubical draw context
|
|
{
|
|
VertexArray lineVAO = new VertexArray();
|
|
|
|
void AddOutline(OutlineDefinition outline, ref List<ColorVertex> vertices, ref List<int> indices)
|
|
{
|
|
int offset = vertices.Count;
|
|
vertices.AddRange(outline.verticies.Select(pos => new ColorVertex(pos)));
|
|
indices.AddRange(outline.indicies.Select(i => i + offset));
|
|
}
|
|
|
|
List<ColorVertex> vertices = new List<ColorVertex>(8 * 6);
|
|
List<int> indices = new List<int>(24 * 6);
|
|
AddOutline(head.GetOutline(0), ref vertices, ref indices);
|
|
AddOutline(body.GetOutline(0), ref vertices, ref indices);
|
|
AddOutline(rightArm.GetOutline(0), ref vertices, ref indices);
|
|
AddOutline(leftArm.GetOutline(0), ref vertices, ref indices);
|
|
AddOutline(rightLeg.GetOutline(0), ref vertices, ref indices);
|
|
AddOutline(leftLeg.GetOutline(0), ref vertices, ref indices);
|
|
VertexBuffer buffer = new VertexBuffer();
|
|
buffer.SetData(vertices.ToArray());
|
|
VertexBufferLayout layout = new VertexBufferLayout();
|
|
layout.Add(ShaderDataType.Float3);
|
|
layout.Add(ShaderDataType.Float4);
|
|
lineVAO.AddBuffer(buffer, layout);
|
|
lineVAO.Bind();
|
|
|
|
_cubicalDrawContext = new DrawContext(lineVAO, IndexBuffer.Create(indices.ToArray()), PrimitiveType.Lines);
|
|
}
|
|
|
|
GLErrorCheck();
|
|
|
|
// Skeleton draw context
|
|
{
|
|
VertexArray lineVAO = new VertexArray();
|
|
Vector3 bodyCenterTop = body.GetFaceCenter(0, Cube.Face.Top);
|
|
Vector3 bodyCenterBottom = body.GetFaceCenter(0, Cube.Face.Bottom);
|
|
ColorVertex[] data = [
|
|
new ColorVertex(head.GetFaceCenter(0, Cube.Face.Top)),
|
|
new ColorVertex(bodyCenterBottom),
|
|
|
|
new ColorVertex(rightArm.GetFaceCenter(0, Cube.Face.Bottom)),
|
|
new ColorVertex(rightArm.GetFaceCenter(0, Cube.Face.Top)),
|
|
new ColorVertex(rightArm.GetFaceCenter(0, Cube.Face.Top)),
|
|
new ColorVertex(leftArm.GetFaceCenter(0, Cube.Face.Top)),
|
|
|
|
new ColorVertex(leftArm.GetFaceCenter(0, Cube.Face.Bottom)),
|
|
new ColorVertex(leftArm.GetFaceCenter(0, Cube.Face.Top)),
|
|
|
|
new ColorVertex(rightLeg.GetFaceCenter(0, Cube.Face.Bottom)),
|
|
new ColorVertex(rightLeg.GetFaceCenter(0, Cube.Face.Top)),
|
|
new ColorVertex(rightLeg.GetFaceCenter(0, Cube.Face.Top)),
|
|
new ColorVertex(leftLeg.GetFaceCenter(0, Cube.Face.Top)),
|
|
|
|
new ColorVertex(leftLeg.GetFaceCenter(0, Cube.Face.Bottom)),
|
|
new ColorVertex(leftLeg.GetFaceCenter(0, Cube.Face.Top)),
|
|
];
|
|
VertexBuffer buffer = new VertexBuffer();
|
|
buffer.SetData(data);
|
|
VertexBufferLayout layout = new VertexBufferLayout();
|
|
layout.Add(ShaderDataType.Float3);
|
|
layout.Add(ShaderDataType.Float4);
|
|
lineVAO.AddBuffer(buffer, layout);
|
|
lineVAO.Bind();
|
|
|
|
_skeletonDrawContext = new DrawContext(lineVAO, buffer.GenIndexBuffer(), PrimitiveType.Lines);
|
|
}
|
|
|
|
// Ground plane draw context
|
|
{
|
|
Vector3 center = Vector3.Zero;
|
|
Color planeColor = Color.CadetBlue;
|
|
|
|
ColorVertex[] vertices = [
|
|
new ColorVertex(new Vector3(center.X + 1f, 0f, center.Z + 1f), planeColor),
|
|
new ColorVertex(new Vector3(center.X - 1f, 0f, center.Z + 1f), planeColor),
|
|
new ColorVertex(new Vector3(center.X - 1f, 0f, center.Z - 1f), planeColor),
|
|
new ColorVertex(new Vector3(center.X + 1f, 0f, center.Z - 1f), planeColor),
|
|
];
|
|
var planeVAO = new VertexArray();
|
|
VertexBuffer buffer = new VertexBuffer();
|
|
buffer.SetData(vertices);
|
|
|
|
VertexBufferLayout layout = new VertexBufferLayout();
|
|
layout.Add(ShaderDataType.Float3);
|
|
layout.Add(ShaderDataType.Float4);
|
|
planeVAO.AddBuffer(buffer, layout);
|
|
|
|
_groundDrawContext = new DrawContext(planeVAO, buffer.GenIndexBuffer(), PrimitiveType.Quads);
|
|
}
|
|
|
|
GLErrorCheck();
|
|
}
|
|
|
|
InitializeDebugShaders();
|
|
}
|
|
|
|
private DrawContext GetGuidelineDrawContext()
|
|
{
|
|
return guidelineMode == GuidelineMode.Skeleton ? _skeletonDrawContext : _cubicalDrawContext;
|
|
}
|
|
|
|
protected virtual void OnTextureChanging(object sender, TextureChangingEventArgs e)
|
|
{
|
|
if (e.NewTexture is null)
|
|
e.Cancel = true;
|
|
|
|
if (e.Cancel)
|
|
return;
|
|
skinTexture.SetTexture(e.NewTexture);
|
|
GLErrorCheck();
|
|
}
|
|
|
|
protected virtual void OnCapeTextureChanging(object sender, TextureChangingEventArgs e)
|
|
{
|
|
if (e.NewTexture is null)
|
|
e.Cancel = true;
|
|
|
|
if (e.Cancel)
|
|
return;
|
|
capeTexture.SetTexture(e.NewTexture);
|
|
GLErrorCheck();
|
|
}
|
|
|
|
[Conditional("USE_FRAMEBUFFER")]
|
|
private void InitializeFramebuffer()
|
|
{
|
|
#if USE_FRAMEBUFFER
|
|
framebuffer = new FrameBuffer();
|
|
framebuffer.Bind();
|
|
framebufferTexture = new Texture2D(0);
|
|
framebufferTexture.PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Rgb;
|
|
framebufferTexture.InternalPixelFormat = PixelInternalFormat.Rgb;
|
|
framebufferTexture.SetSize(Size);
|
|
framebufferTexture.WrapS = TextureWrapMode.ClampToEdge;
|
|
framebufferTexture.WrapT = TextureWrapMode.ClampToEdge;
|
|
framebufferTexture.MinFilter = TextureMinFilter.Nearest;
|
|
framebufferTexture.MagFilter = TextureMagFilter.Nearest;
|
|
|
|
framebufferTexture.AttachToFramebuffer(framebuffer, FramebufferAttachment.ColorAttachment0);
|
|
|
|
framebufferRenderBuffer = GL.GenRenderbuffer();
|
|
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, framebufferRenderBuffer);
|
|
GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Depth24Stencil8, Size.Width, Size.Height);
|
|
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, RenderbufferTarget.Renderbuffer, framebufferRenderBuffer);
|
|
|
|
framebufferVAO = new VertexArray();
|
|
VertexBuffer vertexBuffer = new VertexBuffer();
|
|
vertexBuffer.SetData(rectVertices);
|
|
VertexBufferLayout layout = new VertexBufferLayout();
|
|
layout.Add(ShaderDataType.Float4);
|
|
framebufferVAO.AddBuffer(vertexBuffer, layout);
|
|
framebuffer.CheckStatus();
|
|
|
|
if (framebuffer.Status != FramebufferErrorCode.FramebufferComplete)
|
|
{
|
|
Debug.Fail($"Framebuffer status: '{framebuffer.Status}'");
|
|
}
|
|
|
|
framebuffer.Unbind();
|
|
#endif
|
|
}
|
|
|
|
private void UpdateMesh(string name)
|
|
{
|
|
if (!meshStorage.ContainsKey(name))
|
|
return;
|
|
meshStorage[name]?.UploadData();
|
|
}
|
|
|
|
private void UploadMeshData()
|
|
{
|
|
foreach (var cubeMeshName in meshStorage?.Keys)
|
|
{
|
|
UpdateMesh(cubeMeshName);
|
|
}
|
|
}
|
|
|
|
public Vector3 GetTranslation(string name)
|
|
{
|
|
return meshStorage.ContainsKey(name) ? meshStorage[name].Translation : Vector3.Zero;
|
|
}
|
|
|
|
public Vector3 GetPivot(string name)
|
|
{
|
|
return meshStorage.ContainsKey(name) ? meshStorage[name].Pivot : Vector3.Zero;
|
|
}
|
|
|
|
public void SetPartOffset(SkinPartOffset offset)
|
|
{
|
|
SetPartOffset(offset.Type, offset.Value);
|
|
}
|
|
|
|
public void SetPartOffset(string name, float value)
|
|
{
|
|
if (!SkinPartOffset.ValidModelOffsetTypes.Contains(name))
|
|
{
|
|
Trace.TraceWarning($"{name} is not a valid offset.");
|
|
return;
|
|
}
|
|
bool offsetSpecific = offsetSpecificMeshStorage.ContainsKey(name);
|
|
if (!meshStorage.ContainsKey(name) && !offsetSpecific)
|
|
{
|
|
Trace.TraceError($"[{nameof(SetPartOffset)}]: '{name}' is not inside {nameof(meshStorage)} or {nameof(offsetSpecificMeshStorage)}");
|
|
return;
|
|
}
|
|
if (offsetSpecific)
|
|
{
|
|
offsetSpecificMeshStorage[name].Offset = Vector3.UnitY * value;
|
|
return;
|
|
}
|
|
meshStorage[name].Offset = Vector3.UnitY * value;
|
|
}
|
|
|
|
internal void ResetOffsets()
|
|
{
|
|
foreach (var key in meshStorage.Keys.ToArray())
|
|
{
|
|
meshStorage[key].Offset = Vector3.Zero;
|
|
}
|
|
foreach (var key in offsetSpecificMeshStorage.Keys.ToArray())
|
|
{
|
|
offsetSpecificMeshStorage[key].Offset = Vector3.Zero;
|
|
}
|
|
}
|
|
|
|
internal IEnumerable<SkinPartOffset> GetOffsets()
|
|
{
|
|
foreach (var mesh in meshStorage)
|
|
{
|
|
if (SkinPartOffset.ValidModelOffsetTypes.Contains(mesh.Key) && mesh.Value.Offset.Y != 0f)
|
|
yield return new SkinPartOffset(mesh.Key, mesh.Value.Offset.Y);
|
|
}
|
|
foreach (var offsetmesh in offsetSpecificMeshStorage)
|
|
{
|
|
if (offsetmesh.Value.Offset.Y != 0f)
|
|
yield return new SkinPartOffset(offsetmesh.Key, offsetmesh.Value.Offset.Y);
|
|
}
|
|
yield break;
|
|
}
|
|
|
|
private void ModelData_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
// TODO: dont re-initialize everytime..
|
|
switch (e.Action)
|
|
{
|
|
case NotifyCollectionChangedAction.Add:
|
|
if (e.NewItems[0] is SkinBOX addedBox)
|
|
{
|
|
AddCustomModelPart(addedBox);
|
|
UpdateMesh(addedBox.Type);
|
|
}
|
|
break;
|
|
case NotifyCollectionChangedAction.Reset:
|
|
case NotifyCollectionChangedAction.Remove:
|
|
case NotifyCollectionChangedAction.Replace:
|
|
ReInitialzeSkinData();
|
|
goto default;
|
|
case NotifyCollectionChangedAction.Move:
|
|
break;
|
|
default:
|
|
MakeCurrent();
|
|
UploadMeshData();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void AddCustomModelPart(SkinBOX skinBox)
|
|
{
|
|
if (!meshStorage.ContainsKey(skinBox.Type))
|
|
{
|
|
Trace.TraceWarning("[{0}@{1}] Invalid BOX Type: '{2}'", nameof(SkinRenderer), nameof(AddCustomModelPart), skinBox.Type);
|
|
return;
|
|
}
|
|
|
|
CubeGroupMesh cubeMesh = meshStorage[skinBox.Type];
|
|
cubeMesh.AddSkinBox(skinBox, autoInflateOverlayParts && skinBox.IsOverlayPart() ? skinBox.Type == "HEADWEAR" ? OverlayScale * 2 : OverlayScale : 0f);
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
private void GLErrorCheck()
|
|
{
|
|
var error = GL.GetError();
|
|
Debug.Assert(error == ErrorCode.NoError, error.ToString());
|
|
}
|
|
|
|
private void OnANIMUpdate()
|
|
{
|
|
head.SetEnabled(0, !ANIM.GetFlag(SkinAnimFlag.HEAD_DISABLED));
|
|
head.SetEnabled(1, !ANIM.GetFlag(SkinAnimFlag.HEAD_OVERLAY_DISABLED));
|
|
|
|
body.SetEnabled(0, !ANIM.GetFlag(SkinAnimFlag.BODY_DISABLED));
|
|
rightArm.SetEnabled(0, !ANIM.GetFlag(SkinAnimFlag.RIGHT_ARM_DISABLED));
|
|
leftArm.SetEnabled(0, !ANIM.GetFlag(SkinAnimFlag.LEFT_ARM_DISABLED));
|
|
rightLeg.SetEnabled(0, !ANIM.GetFlag(SkinAnimFlag.RIGHT_LEG_DISABLED));
|
|
leftLeg.SetEnabled(0, !ANIM.GetFlag(SkinAnimFlag.LEFT_LEG_DISABLED));
|
|
|
|
bool slim = ANIM.GetFlag(SkinAnimFlag.SLIM_MODEL);
|
|
|
|
head.FlipZMapping = true;
|
|
if (slim || ANIM.GetFlag(SkinAnimFlag.RESOLUTION_64x64))
|
|
{
|
|
TextureSize = new Size(64, 64);
|
|
body.SetEnabled(1, !ANIM.GetFlag(SkinAnimFlag.BODY_OVERLAY_DISABLED));
|
|
rightArm.SetEnabled(1, !ANIM.GetFlag(SkinAnimFlag.RIGHT_ARM_OVERLAY_DISABLED));
|
|
leftArm.SetEnabled(1, !ANIM.GetFlag(SkinAnimFlag.LEFT_ARM_OVERLAY_DISABLED));
|
|
rightLeg.SetEnabled(1, !ANIM.GetFlag(SkinAnimFlag.RIGHT_LEG_OVERLAY_DISABLED));
|
|
leftLeg.SetEnabled(1, !ANIM.GetFlag(SkinAnimFlag.LEFT_LEG_OVERLAY_DISABLED));
|
|
|
|
int slimValue = slim ? 3 : 4;
|
|
rightArm.ReplaceCube(0, new(slim ? -2 : -3, -2, -2), new(slimValue, 12, 4), new(40, 16));
|
|
rightArm.ReplaceCube(1, new(slim ? -2 : -3, -2, -2), new(slimValue, 12, 4), new(40, 32), inflate: OverlayScale);
|
|
|
|
leftArm.ReplaceCube(0, new(-1, -2, -2), new(slimValue, 12, 4), new(32, 48));
|
|
leftArm.ReplaceCube(1, new(-1, -2, -2), new(slimValue, 12, 4), new(48, 48), inflate: OverlayScale);
|
|
|
|
rightLeg.ReplaceCube(0, new(-2, 0, -2), new(4, 12, 4), new(0, 16));
|
|
leftLeg.ReplaceCube(0, new(-2, 0, -2), new(4, 12, 4), new(16, 48));
|
|
return;
|
|
}
|
|
|
|
TextureSize = new Size(64, 32);
|
|
|
|
body.SetEnabled(1, false);
|
|
head.FlipZMapping = false;
|
|
|
|
rightArm.ReplaceCube(0, new(-3, -2, -2), new(4, 12, 4), new(40, 16));
|
|
rightArm.SetEnabled(1, false);
|
|
|
|
leftArm.ReplaceCube(0, new(-1, -2, -2), new(4, 12, 4), new(40, 16), mirrorTexture: true);
|
|
leftArm.SetEnabled(1, false);
|
|
|
|
rightLeg.ReplaceCube(0, new(-2, 0, -2), new(4, 12, 4), new(0, 16));
|
|
rightLeg.SetEnabled(1, false);
|
|
leftLeg.ReplaceCube (0, new(-2, 0, -2), new(4, 12, 4), new(0, 16), mirrorTexture: true);
|
|
leftLeg.SetEnabled(1, false);
|
|
}
|
|
|
|
protected override bool ProcessDialogKey(Keys keyData)
|
|
{
|
|
switch (keyData)
|
|
{
|
|
#if DEBUG
|
|
case Keys.Escape:
|
|
ReleaseMouse();
|
|
var point = new Point(Parent.Location.X + Location.X, Parent.Location.Y + Location.Y);
|
|
contextMenuStrip1.Show(point);
|
|
return true;
|
|
#endif
|
|
case Keys.F3:
|
|
showWireFrame = !showWireFrame;
|
|
return true;
|
|
case Keys.R:
|
|
Camera.Distance = DefaultCameraDistance;
|
|
Camera.FocalPoint = head.GetCenter(0);
|
|
Camera.Yaw = 0f;
|
|
Camera.Pitch = 0f;
|
|
return true;
|
|
case Keys.A:
|
|
ReleaseMouse();
|
|
{
|
|
using var animeditor = new ANIMEditor(ANIM);
|
|
if (animeditor.ShowDialog() == DialogResult.OK)
|
|
{
|
|
ANIM = animeditor.ResultAnim;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return base.ProcessDialogKey(keyData);
|
|
}
|
|
|
|
#if USE_FRAMEBUFFER
|
|
[Conditional("USE_FRAMEBUFFER")]
|
|
private void SetFramebufferSize(Size size)
|
|
{
|
|
MakeCurrent();
|
|
if (framebuffer is not null)
|
|
{
|
|
framebuffer.Bind();
|
|
|
|
framebufferTexture.Bind();
|
|
framebufferTexture.SetSize(size);
|
|
|
|
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, framebufferRenderBuffer);
|
|
GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Depth24Stencil8, size.Width, size.Height);
|
|
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, RenderbufferTarget.Renderbuffer, framebufferRenderBuffer);
|
|
|
|
FramebufferErrorCode status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
|
|
if (status != FramebufferErrorCode.FramebufferComplete)
|
|
{
|
|
Debug.Fail($"Framebuffer status: '{framebuffer.Status}'");
|
|
}
|
|
framebuffer.Unbind();
|
|
}
|
|
}
|
|
|
|
protected override void OnResize(EventArgs e)
|
|
{
|
|
base.OnResize(e);
|
|
if (!IsHandleCreated || DesignMode)
|
|
return;
|
|
SetFramebufferSize(Size);
|
|
}
|
|
#endif
|
|
|
|
protected override void OnPaint(PaintEventArgs e)
|
|
{
|
|
base.OnPaint(e);
|
|
if (DesignMode)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MakeCurrent();
|
|
|
|
#if USE_FRAMEBUFFER
|
|
framebuffer.Bind();
|
|
#endif
|
|
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
|
|
|
GL.Enable(EnableCap.DepthTest); // Enable correct Z Drawings
|
|
GL.Enable(EnableCap.LineSmooth);
|
|
Matrix4 viewProjection = Camera.GetViewProjection();
|
|
|
|
// Render Skybox
|
|
{
|
|
GL.DepthFunc(DepthFunction.Lequal);
|
|
GL.DepthMask(false);
|
|
var skyboxShader = _shaders.GetShader("SkyboxShader");
|
|
skyboxShader.Bind();
|
|
_skyboxTexture.Bind();
|
|
|
|
var view = new Matrix4(new Matrix3(Matrix4.LookAt(Camera.WorldPosition, Camera.WorldPosition + Camera.Orientation, Camera.Up)))
|
|
* Matrix4.CreateRotationY(MathHelper.DegreesToRadians(Camera.Yaw))
|
|
* Matrix4.CreateRotationX(MathHelper.DegreesToRadians(Camera.Pitch));
|
|
var viewproj = view * Camera.GetProjection();
|
|
skyboxShader.SetUniformMat4("ViewProjection", ref viewproj);
|
|
Renderer.Draw(skyboxShader, _skyboxRenderBuffer);
|
|
GL.DepthMask(true);
|
|
GL.DepthFunc(DepthFunction.Less);
|
|
}
|
|
|
|
ShaderProgram lineShader = _shaders.GetShader("PlainColorShader");
|
|
|
|
// Render (custom) skin
|
|
{
|
|
GL.Enable(EnableCap.Texture2D); // Enable textures
|
|
|
|
GL.Enable(EnableCap.Blend);
|
|
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
|
|
GL.Enable(EnableCap.AlphaTest); // Enable transparent
|
|
GL.AlphaFunc(AlphaFunction.Greater, 0.0f);
|
|
GL.DepthFunc(DepthFunction.Lequal);
|
|
|
|
if (showWireFrame)
|
|
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
|
|
|
|
Matrix4 transform = Matrix4.CreateTranslation(0f, 0f, 0f);
|
|
|
|
var skinShader = _shaders.GetShader("SkinShader");
|
|
skinShader.Bind();
|
|
skinShader.SetUniformMat4("u_ViewProjection", ref viewProjection);
|
|
skinShader.SetUniform2("u_TexSize", new Vector2(TextureSize.Width, TextureSize.Height));
|
|
|
|
skinTexture.Bind();
|
|
|
|
if (ANIM.GetFlag(SkinAnimFlag.DINNERBONE))
|
|
{
|
|
transform = Pivot(head.GetFaceCenter(0, Cube.Face.Top), Vector3.UnitY * 12f, transform * Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(-180f)));
|
|
}
|
|
|
|
var legRightMatrix = Matrix4.Identity;
|
|
var legLeftMatrix = Matrix4.Identity;
|
|
var armRightMatrix = Matrix4.Identity;
|
|
var armLeftMatrix = Matrix4.Identity;
|
|
|
|
if (!ANIM.GetFlag(SkinAnimFlag.STATIC_ARMS))
|
|
{
|
|
armRightMatrix = Matrix4.CreateRotationX(MathHelper.DegreesToRadians(animationCurrentRotationAngle));
|
|
armLeftMatrix = Matrix4.CreateRotationX(MathHelper.DegreesToRadians((ANIM.GetFlag(SkinAnimFlag.SYNCED_ARMS) ? 1f : -1f) * animationCurrentRotationAngle));
|
|
}
|
|
|
|
if (ANIM.GetFlag(SkinAnimFlag.ZOMBIE_ARMS))
|
|
{
|
|
var rotation = Matrix4.CreateRotationX(MathHelper.DegreesToRadians(-90f));
|
|
armRightMatrix = rotation;
|
|
armLeftMatrix = rotation;
|
|
}
|
|
|
|
if (ANIM.GetFlag(SkinAnimFlag.STATUE_OF_LIBERTY))
|
|
{
|
|
armRightMatrix = Matrix4.CreateRotationX(MathHelper.DegreesToRadians(-180f));
|
|
armLeftMatrix = Matrix4.CreateRotationX(0f);
|
|
}
|
|
|
|
if (!ANIM.GetFlag(SkinAnimFlag.STATIC_LEGS))
|
|
{
|
|
legRightMatrix = Matrix4.CreateRotationX(MathHelper.DegreesToRadians((ANIM.GetFlag(SkinAnimFlag.SYNCED_LEGS) ? 1f : -1f) * animationCurrentRotationAngle));
|
|
legLeftMatrix = Matrix4.CreateRotationX(MathHelper.DegreesToRadians(animationCurrentRotationAngle));
|
|
}
|
|
|
|
RenderBodyPart(skinShader, Matrix4.Identity, transform, "HEAD", "HEADWEAR");
|
|
RenderBodyPart(skinShader, Matrix4.Identity, transform, "BODY", "JACKET");
|
|
RenderBodyPart(skinShader, RightArmMatrix * armRightMatrix, transform, "ARM0", "SLEEVE0");
|
|
RenderBodyPart(skinShader, LeftArmMatrix * armLeftMatrix, transform, "ARM1", "SLEEVE1");
|
|
RenderBodyPart(skinShader, legRightMatrix, transform, "LEG0", "PANTS0");
|
|
RenderBodyPart(skinShader, legLeftMatrix, transform, "LEG1", "PANTS1");
|
|
|
|
if (_capeImage is not null)
|
|
{
|
|
skinShader.SetUniform2("u_TexSize", new Vector2(64, 32));
|
|
capeTexture.Bind();
|
|
// Defines minimum Angle(in Degrees) of the cape
|
|
float capeMinimumRotationAngle = 7.5f;
|
|
// Controls how much of an angle is applied
|
|
float capeRotationFactor = 0.4f;
|
|
// Low value = slow movement
|
|
float capeRotationSpeed = 0.02f;
|
|
float capeRotation = ((float)MathHelper.RadiansToDegrees(Math.Sin(Math.Abs(animationCurrentRotationAngle) * capeRotationSpeed) * capeRotationFactor)) + capeMinimumRotationAngle;
|
|
Matrix4 partMatrix =
|
|
Matrix4.CreateRotationY(MathHelper.DegreesToRadians(180f)) *
|
|
Matrix4.CreateRotationX(MathHelper.DegreesToRadians(capeRotation));
|
|
RenderPart(skinShader, cape, partMatrix, transform);
|
|
}
|
|
|
|
// Armor rendering
|
|
if (ShowArmor)
|
|
{
|
|
armorTexture.Bind();
|
|
//skinShader.SetUniform4("u_Color", Color.FromArgb(0xff << 24 | Color.White.ToArgb() - _outlineColor.ToArgb()));
|
|
skinShader.SetUniform2("u_TexSize", new Vector2(64, 64));
|
|
if (!ANIM.GetFlag(SkinAnimFlag.HEAD_DISABLED) || ANIM.GetFlag(SkinAnimFlag.FORCE_HEAD_ARMOR))
|
|
RenderPart(skinShader, offsetSpecificMeshStorage["HELMET"], Matrix4.Identity, transform);
|
|
|
|
if (!ANIM.GetFlag(SkinAnimFlag.BODY_DISABLED) || ANIM.GetFlag(SkinAnimFlag.FORCE_BODY_ARMOR))
|
|
RenderPart(skinShader, offsetSpecificMeshStorage["CHEST"], Matrix4.Identity, transform);
|
|
|
|
if (!ANIM.GetFlag(SkinAnimFlag.RIGHT_ARM_DISABLED) || ANIM.GetFlag(SkinAnimFlag.FORCE_RIGHT_ARM_ARMOR))
|
|
RenderPart(skinShader, offsetSpecificMeshStorage["SHOULDER0"], RightArmMatrix * armRightMatrix, transform);
|
|
|
|
if (!ANIM.GetFlag(SkinAnimFlag.LEFT_ARM_DISABLED) || ANIM.GetFlag(SkinAnimFlag.FORCE_LEFT_ARM_ARMOR))
|
|
RenderPart(skinShader, offsetSpecificMeshStorage["SHOULDER1"], LeftArmMatrix * armLeftMatrix, transform);
|
|
|
|
bool showRightLegArmor = !ANIM.GetFlag(SkinAnimFlag.RIGHT_LEG_DISABLED) || ANIM.GetFlag(SkinAnimFlag.FORCE_RIGHT_LEG_ARMOR);
|
|
if (showRightLegArmor)
|
|
{
|
|
RenderPart(skinShader, offsetSpecificMeshStorage["PANTS0"], legRightMatrix, transform);
|
|
RenderPart(skinShader, offsetSpecificMeshStorage["BOOT0"], legRightMatrix, transform);
|
|
}
|
|
|
|
bool showLeftLegArmor = !ANIM.GetFlag(SkinAnimFlag.LEFT_LEG_DISABLED) || ANIM.GetFlag(SkinAnimFlag.FORCE_LEFT_LEG_ARMOR);
|
|
if (showLeftLegArmor)
|
|
{
|
|
RenderPart(skinShader, offsetSpecificMeshStorage["PANTS1"], legLeftMatrix, transform);
|
|
RenderPart(skinShader, offsetSpecificMeshStorage["BOOT1"], legLeftMatrix, transform);
|
|
}
|
|
|
|
if (showRightLegArmor && showLeftLegArmor)
|
|
RenderPart(skinShader, offsetSpecificMeshStorage["WAIST"], Matrix4.Identity, transform);
|
|
}
|
|
|
|
if (showWireFrame)
|
|
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
|
|
|
|
if (ShowGuideLines)
|
|
{
|
|
GL.DepthFunc(DepthFunction.Always);
|
|
GL.BlendFunc(BlendingFactor.DstAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
lineShader.Bind();
|
|
lineShader.SetUniformMat4("ViewProjection", ref viewProjection);
|
|
lineShader.SetUniformMat4("Transform", ref transform);
|
|
lineShader.SetUniform1("intensity", 1f);
|
|
lineShader.SetUniform4("baseColor", OutlineColor);
|
|
Renderer.SetLineWidth(2.5f);
|
|
Renderer.Draw(lineShader, GetGuidelineDrawContext());
|
|
Renderer.SetLineWidth(1f);
|
|
GL.BlendFunc(BlendingFactor.DstAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
GL.DepthFunc(DepthFunction.Less);
|
|
}
|
|
|
|
if (ModelData.IndexInRange(SelectedIndex))
|
|
{
|
|
SkinBOX box = ModelData[SelectedIndex];
|
|
var cubeBoundingBox = Cube.FromSkinBox(box).GetBoundingBox();
|
|
|
|
if (meshStorage.ContainsKey(box.Type))
|
|
{
|
|
var cubeMesh = meshStorage[box.Type];
|
|
|
|
Matrix4 GetGroupTransform(string type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case "ARM0":
|
|
case "SLEEVE0":
|
|
return RightArmMatrix * armRightMatrix;
|
|
case "ARM1":
|
|
case "SLEEVE1":
|
|
return LeftArmMatrix * armLeftMatrix;
|
|
case "LEG0":
|
|
case "PANTS0":
|
|
return legRightMatrix;
|
|
case "LEG1":
|
|
case "PANTS1":
|
|
return legLeftMatrix;
|
|
default:
|
|
return Matrix4.Identity;
|
|
}
|
|
}
|
|
|
|
transform *= GetGroupTransform(box.Type);
|
|
Vector3 translation = cubeMesh.Translation - cubeMesh.Offset;
|
|
Vector3 pivot = cubeMesh.Pivot + cubeMesh.Offset;
|
|
transform = Pivot(translation, pivot, transform);
|
|
GL.BlendFunc(BlendingFactor.DstAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
DrawBoundingBox(transform, cubeBoundingBox, OutlineColor);
|
|
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Ground plane
|
|
{
|
|
GL.Enable(EnableCap.DepthTest);
|
|
GL.Enable(EnableCap.AlphaTest); // Enable transparent
|
|
GL.AlphaFunc(AlphaFunction.Always, 0.0f);
|
|
GL.BlendFunc(BlendingFactor.DstAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
lineShader.Bind();
|
|
lineShader.SetUniformMat4("ViewProjection", ref viewProjection);
|
|
lineShader.SetUniform1("intensity", 0.5f);
|
|
lineShader.SetUniform4("baseColor", Color.AntiqueWhite);
|
|
Matrix4 transform = Matrix4.CreateScale(25f) * Matrix4.CreateTranslation(new Vector3(0f, -24.1f, 0f));
|
|
lineShader.SetUniformMat4("Transform", ref transform);
|
|
Renderer.Draw(lineShader, _groundDrawContext);
|
|
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
}
|
|
|
|
// Debug
|
|
RenderDebug();
|
|
|
|
#if USE_FRAMEBUFFER
|
|
framebuffer.Unbind();
|
|
GL.Disable(EnableCap.DepthTest);
|
|
framebufferShader.Bind();
|
|
framebufferVAO.Bind();
|
|
framebufferTexture.Bind();
|
|
|
|
GL.DrawArrays(PrimitiveType.Triangles, 0, 6);
|
|
|
|
framebufferTexture.Unbind();
|
|
#endif
|
|
SwapBuffers();
|
|
|
|
}
|
|
|
|
protected override void OnMouseMove(MouseEventArgs e)
|
|
{
|
|
base.OnMouseMove(e);
|
|
|
|
float deltaX = (Cursor.Position.X - CurrentMouseLocation.X) * MouseSensetivity;
|
|
float deltaY = (Cursor.Position.Y - CurrentMouseLocation.Y) * MouseSensetivity;
|
|
|
|
switch (e.Button)
|
|
{
|
|
case MouseButtons.None:
|
|
case MouseButtons.Middle:
|
|
case MouseButtons.XButton1:
|
|
case MouseButtons.XButton2:
|
|
break;
|
|
case MouseButtons.Left:
|
|
Camera.Rotate(deltaX, deltaY);
|
|
goto default;
|
|
case MouseButtons.Right:
|
|
Camera.Pan(deltaX, deltaY);
|
|
goto default;
|
|
default:
|
|
Cursor.Position = new Point((int)Math.Round(Screen.PrimaryScreen.Bounds.Width / 2d), (int)Math.Round(Screen.PrimaryScreen.Bounds.Height / 2d));
|
|
CurrentMouseLocation = Cursor.Position;
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseWheel(MouseEventArgs e)
|
|
{
|
|
Camera.Distance -= e.Delta / System.Windows.Input.Mouse.MouseWheelDeltaForOneLine;
|
|
}
|
|
|
|
protected override void OnMouseUp(MouseEventArgs e)
|
|
{
|
|
ReleaseMouse();
|
|
base.OnMouseUp(e);
|
|
}
|
|
|
|
private void ReleaseMouse()
|
|
{
|
|
if (IsMouseHidden)
|
|
{
|
|
IsMouseHidden = false;
|
|
Cursor.Position = PreviousMouseLocation;
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseDown(MouseEventArgs e)
|
|
{
|
|
base.OnMouseDown(e);
|
|
if (e.Button == MouseButtons.Right || e.Button == MouseButtons.Left)
|
|
{
|
|
IsMouseHidden = true;
|
|
CurrentMouseLocation = PreviousMouseLocation = Cursor.Position;
|
|
}
|
|
}
|
|
|
|
private void RenderBodyPart(ShaderProgram shader, Matrix4 partsMatrix, Matrix4 globalMatrix, params string[] partNames)
|
|
{
|
|
foreach (var partName in partNames)
|
|
{
|
|
RenderPart(shader, meshStorage[partName], partsMatrix, globalMatrix);
|
|
}
|
|
}
|
|
|
|
private void RenderPart(ShaderProgram shader, CubeGroupMesh cubeMesh, Matrix4 partMatrix, Matrix4 globalMatrix)
|
|
{
|
|
Vector3 translation = cubeMesh.Translation - cubeMesh.Offset;
|
|
Vector3 pivot = cubeMesh.Pivot + cubeMesh.Offset;
|
|
Matrix4 transform = Pivot(translation, pivot, partMatrix);
|
|
transform *= globalMatrix;
|
|
shader.SetUniformMat4("u_Transform", ref transform);
|
|
cubeMesh.Draw(shader);
|
|
}
|
|
|
|
private static Matrix4 Pivot(Vector3 translation, Vector3 pivot, Matrix4 target)
|
|
{
|
|
var model = Matrix4.CreateTranslation(translation);
|
|
model *= Matrix4.CreateTranslation(pivot);
|
|
model *= target;
|
|
model *= Matrix4.CreateTranslation(pivot).Inverted();
|
|
return model;
|
|
}
|
|
|
|
protected override void OnUpdate(object sender, TimestepEventArgs e)
|
|
{
|
|
base.OnUpdate(sender, e);
|
|
animationCurrentRotationAngle += animationRotationSpeed;
|
|
if (animationCurrentRotationAngle >= animationMaxAngleInDegrees || animationCurrentRotationAngle <= -animationMaxAngleInDegrees)
|
|
animationRotationSpeed = -animationRotationSpeed;
|
|
}
|
|
|
|
private void ReInitialzeSkinData()
|
|
{
|
|
foreach (var mesh in meshStorage.Values)
|
|
{
|
|
mesh.ClearData();
|
|
}
|
|
|
|
InitializeSkinData();
|
|
UpdateModelData();
|
|
OnANIMUpdate();
|
|
}
|
|
|
|
private void UpdateModelData()
|
|
{
|
|
foreach (var item in ModelData)
|
|
{
|
|
AddCustomModelPart(item);
|
|
}
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
private void InitializeDebugShaders()
|
|
{
|
|
#if DEBUG
|
|
// Debug point render
|
|
{
|
|
ColorVertex[] vertices = [
|
|
new ColorVertex(Vector3.Zero, Color.White)
|
|
];
|
|
VertexArray vao = new VertexArray();
|
|
var debugVBO = new VertexBuffer();
|
|
debugVBO.SetData(vertices);
|
|
VertexBufferLayout layout = new VertexBufferLayout();
|
|
layout.Add(ShaderDataType.Float3);
|
|
layout.Add(ShaderDataType.Float4);
|
|
vao.AddBuffer(debugVBO, layout);
|
|
d_debugPointDrawContext = new DrawContext(vao, debugVBO.GenIndexBuffer(), PrimitiveType.Points);
|
|
}
|
|
// Debug point render
|
|
{
|
|
ColorVertex[] vertices = [
|
|
new ColorVertex(Vector3.Zero),
|
|
new ColorVertex(Vector3.One)
|
|
];
|
|
VertexArray vao = new VertexArray();
|
|
var debugVBO = new VertexBuffer();
|
|
debugVBO.SetData(vertices);
|
|
VertexBufferLayout layout = new VertexBufferLayout();
|
|
layout.Add(ShaderDataType.Float3);
|
|
layout.Add(ShaderDataType.Float4);
|
|
vao.AddBuffer(debugVBO, layout);
|
|
d_debugLineDrawContext = new DrawContext(vao, debugVBO.GenIndexBuffer(), PrimitiveType.Lines);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
private void RenderDebug()
|
|
{
|
|
#if DEBUG
|
|
var colorShader = _shaders.GetShader("PlainColorShader");
|
|
if (d_showFocalPoint)
|
|
{
|
|
GL.BlendFunc(BlendingFactor.DstAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
GL.DepthFunc(DepthFunction.Always);
|
|
GL.DepthMask(false);
|
|
GL.Enable(EnableCap.PointSmooth);
|
|
colorShader.Bind();
|
|
var viewProjection = Camera.GetViewProjection();
|
|
var transform = Matrix4.CreateTranslation(Camera.FocalPoint).Inverted();
|
|
colorShader.SetUniformMat4("Transform", ref transform);
|
|
colorShader.SetUniformMat4("ViewProjection", ref viewProjection);
|
|
colorShader.SetUniform1("intensity", 0.75f);
|
|
colorShader.SetUniform4("baseColor", Color.DeepPink);
|
|
GL.PointSize(5f);
|
|
Renderer.Draw(colorShader, d_debugPointDrawContext);
|
|
GL.PointSize(1f);
|
|
GL.DepthMask(true);
|
|
GL.DepthFunc(DepthFunction.Less);
|
|
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
}
|
|
if (d_showDirectionArrows)
|
|
{
|
|
GL.BlendFunc(BlendingFactor.DstAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
GL.DepthFunc(DepthFunction.Always);
|
|
GL.DepthMask(false);
|
|
GL.Enable(EnableCap.LineSmooth);
|
|
colorShader.Bind();
|
|
|
|
var viewProjection = Camera.GetViewProjection();
|
|
var transform = Matrix4.Identity;
|
|
transform *= Matrix4.CreateTranslation(Vector3.Zero);
|
|
transform *= Matrix4.CreateScale(Camera.Distance / 4f).Inverted();
|
|
transform.Invert();
|
|
colorShader.SetUniformMat4("Transform", ref transform);
|
|
colorShader.SetUniformMat4("ViewProjection", ref viewProjection);
|
|
colorShader.SetUniform1("intensity", 0.75f);
|
|
|
|
|
|
Renderer.SetLineWidth(2f);
|
|
|
|
colorShader.SetUniform4("baseColor", Color.Red);
|
|
ColorVertex[] line = [new ColorVertex(Vector3.Zero), new ColorVertex(Vector3.UnitX)];
|
|
d_debugLineDrawContext.VertexArray.GetBuffer(0).SetData(line);
|
|
Renderer.Draw(colorShader, d_debugLineDrawContext);
|
|
|
|
colorShader.SetUniform4("baseColor", Color.Green);
|
|
line = [new ColorVertex(Vector3.Zero), new ColorVertex(Vector3.UnitY)];
|
|
d_debugLineDrawContext.VertexArray.GetBuffer(0).SetData(line);
|
|
Renderer.Draw(colorShader, d_debugLineDrawContext);
|
|
|
|
colorShader.SetUniform4("baseColor", Color.Blue);
|
|
line = [new ColorVertex(Vector3.Zero), new ColorVertex(Vector3.UnitZ)];
|
|
d_debugLineDrawContext.VertexArray.GetBuffer(0).SetData(line);
|
|
Renderer.Draw(colorShader, d_debugLineDrawContext);
|
|
|
|
Renderer.SetLineWidth(1f);
|
|
|
|
GL.DepthMask(true);
|
|
GL.DepthFunc(DepthFunction.Less);
|
|
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
}
|
|
d_debugLabel.Text = Camera.ToString();
|
|
#endif
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
private void InitializeDebug()
|
|
{
|
|
#if DEBUG
|
|
reToolStripMenuItem = new ToolStripMenuItem();
|
|
contextMenuStrip1 = new ContextMenuStrip(this.components);
|
|
guidelineModeToolStripMenuItem = new ToolStripMenuItem();
|
|
contextMenuStrip1.SuspendLayout();
|
|
SuspendLayout();
|
|
//
|
|
// contextMenuStrip1
|
|
//
|
|
contextMenuStrip1.Items.AddRange(new ToolStripItem[] {
|
|
reToolStripMenuItem,
|
|
guidelineModeToolStripMenuItem});
|
|
contextMenuStrip1.Name = "contextMenuStrip1";
|
|
contextMenuStrip1.Size = new Size(159, 48);
|
|
//
|
|
// reToolStripMenuItem
|
|
//
|
|
reToolStripMenuItem.Name = "reToolStripMenuItem";
|
|
reToolStripMenuItem.Size = new Size(158, 22);
|
|
reToolStripMenuItem.Text = "Re-Init";
|
|
reToolStripMenuItem.Click += new EventHandler(this.reInitToolStripMenuItem_Click);
|
|
//
|
|
// guidelineModeToolStripMenuItem
|
|
//
|
|
guidelineModeToolStripMenuItem.Name = "guidelineModeToolStripMenuItem";
|
|
guidelineModeToolStripMenuItem.Size = new Size(158, 22);
|
|
guidelineModeToolStripMenuItem.Text = "Guideline Mode";
|
|
guidelineModeToolStripMenuItem.Click += new EventHandler(this.guidelineModeToolStripMenuItem_Click);
|
|
//
|
|
// debugLabel
|
|
//
|
|
d_debugLabel = new Label();
|
|
d_debugLabel.AutoSize = true;
|
|
d_debugLabel.Visible = false;
|
|
d_debugLabel.BackColor = Color.Transparent;
|
|
d_debugLabel.ForeColor = SystemColors.ControlLight;
|
|
d_debugLabel.Location = new Point(3, 4);
|
|
d_debugLabel.Name = "debugLabel";
|
|
d_debugLabel.Size = new Size(37, 13);
|
|
d_debugLabel.TabIndex = 2;
|
|
d_debugLabel.Text = "debug";
|
|
var debugCameraToolStripMenuItem = new ToolStripMenuItem("Show Camera debug information");
|
|
debugCameraToolStripMenuItem.CheckOnClick = true;
|
|
debugCameraToolStripMenuItem.Click += (s, e) => d_debugLabel.Visible = debugCameraToolStripMenuItem.Checked;
|
|
contextMenuStrip1.Items.Add(debugCameraToolStripMenuItem);
|
|
|
|
var debugShowFocalPointToolStripMenuItem = new ToolStripMenuItem("Show Camera Focal point");
|
|
debugShowFocalPointToolStripMenuItem.CheckOnClick = true;
|
|
debugShowFocalPointToolStripMenuItem.Click += (s, e) => d_showFocalPoint = debugShowFocalPointToolStripMenuItem.Checked;
|
|
contextMenuStrip1.Items.Add(debugShowFocalPointToolStripMenuItem);
|
|
|
|
var debugShowDirectionArrows = new ToolStripMenuItem("Show Direction Arrows");
|
|
debugShowDirectionArrows.CheckOnClick = true;
|
|
debugShowDirectionArrows.Click += (s, e) => d_showDirectionArrows = debugShowDirectionArrows.Checked;
|
|
contextMenuStrip1.Items.Add(debugShowDirectionArrows);
|
|
|
|
Controls.Add(d_debugLabel);
|
|
|
|
this.contextMenuStrip1.ResumeLayout(false);
|
|
#endif
|
|
}
|
|
|
|
#if DEBUG
|
|
private bool d_showFocalPoint;
|
|
private bool d_showDirectionArrows;
|
|
private DrawContext d_debugPointDrawContext;
|
|
private DrawContext d_debugLineDrawContext;
|
|
private Label d_debugLabel;
|
|
private ToolStripMenuItem reToolStripMenuItem;
|
|
private ContextMenuStrip contextMenuStrip1;
|
|
private ToolStripMenuItem guidelineModeToolStripMenuItem;
|
|
|
|
private void reInitToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
ReInitialzeSkinData();
|
|
MakeCurrent();
|
|
UploadMeshData();
|
|
}
|
|
|
|
private void guidelineModeToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
if (!Enum.IsDefined(typeof(GuidelineMode), ++guidelineMode))
|
|
{
|
|
guidelineMode = GuidelineMode.None;
|
|
}
|
|
guidelineModeToolStripMenuItem.Text = $"Guideline Mode: {guidelineMode}";
|
|
}
|
|
#endif
|
|
}
|
|
} |