├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── .gitmodules ├── Easel.Core ├── DisposeManager.cs ├── Easel.Core.csproj ├── EaselException.cs ├── EnvVars.cs ├── Logger.cs └── Utils.cs ├── Easel.Graphics ├── Bitmap.cs ├── BitmapF.cs ├── BlendState.cs ├── Canvas.cs ├── Cubemap.cs ├── DropShadow.cs ├── Easel.Graphics.csproj ├── EaselGraphics.cs ├── Effect.cs ├── EffectLayout.cs ├── Effects │ └── GaussianBlur.cs ├── Lighting │ ├── DirectionalLight.cs │ ├── DirectionalShadow.cs │ └── ShadowMap.cs ├── Materials │ ├── Material.cs │ ├── StandardMaterial.cs │ └── TranslucentStandardMaterial.cs ├── Mesh.cs ├── Model.cs ├── ModelMesh.cs ├── Primitives │ ├── Cube.cs │ ├── IPrimitive.cs │ ├── Quad.cs │ └── Sphere.cs ├── RasterizerState.cs ├── RenderTarget.cs ├── Renderers │ ├── ForwardRenderer.cs │ ├── I2DDrawMethods.cs │ ├── IRenderer.cs │ ├── RenderOptions.cs │ ├── Renderable.cs │ ├── Sprite.cs │ ├── SpriteRenderer.cs │ └── Structs │ │ ├── CameraInfo.cs │ │ ├── ProjViewModel.cs │ │ ├── SceneInfo.cs │ │ ├── ShaderDirLight.cs │ │ ├── ShaderMaterial.cs │ │ └── TransformedRenderable.cs ├── SamplerState.cs ├── Shaders │ ├── CompileShaders.sh │ ├── Forward │ │ ├── Standard.hlsl │ │ ├── Standard_frag.spv │ │ └── Standard_vert.spv │ ├── Lighting.hlsl │ ├── PostProcess │ │ ├── FXAA3_11.h │ │ ├── PostProcess.hlsl │ │ ├── PostProcess_frag.spv │ │ └── PostProcess_vert.spv │ ├── Shadow.hlsl │ ├── Shadow_frag.spv │ ├── Shadow_vert.spv │ ├── Skybox.hlsl │ ├── Skybox_frag.spv │ ├── Skybox_vert.spv │ ├── SpriteRenderer │ │ ├── Shapes.hlsl │ │ ├── Sprite.hlsl │ │ ├── Sprite_frag.spv │ │ └── Sprite_vert.spv │ ├── Types.hlsl │ └── Utils │ │ └── Math.hlsl ├── Skybox.cs ├── Texture.cs ├── Texture2D.cs └── VertexPositionTextureNormalTangent.cs ├── Easel.ImGui ├── Easel.ImGui.csproj ├── FilePicker.cs ├── FilePickerType.cs ├── FontInfo.cs └── ImGuiRenderer.cs ├── Easel.Math ├── Color.cs ├── Easel.Math.csproj ├── EaselMath.cs ├── MatrixT.cs ├── QuaternionT.cs ├── Rectangle.cs ├── Size.cs ├── Vector2T.cs ├── Vector3T.cs ├── Vector4T.cs └── swizgen.py ├── Easel.Tests ├── Content │ ├── Abel-Regular.ttf │ ├── Audio │ │ ├── help.mp3 │ │ ├── help.ogg │ │ └── help.wav │ ├── DDS │ │ ├── 24bitcolor-BGRA8.dds │ │ ├── 24bitcolor-RGBA8.dds │ │ ├── Compressed │ │ │ ├── 24bitcolor-BC1.dds │ │ │ ├── 24bitcolor-BC1_SRGB.dds │ │ │ ├── 24bitcolor-BC2.dds │ │ │ ├── 24bitcolor-BC2_SRGB.dds │ │ │ ├── 24bitcolor-BC3.dds │ │ │ ├── 24bitcolor-BC3_SRGB.dds │ │ │ ├── 24bitcolor-BC4.dds │ │ │ ├── 24bitcolor-BC5.dds │ │ │ ├── 24bitcolor-BC6H_SF16.dds │ │ │ ├── 24bitcolor-BC6H_UF16.dds │ │ │ └── 24bitcolor-BC7.dds │ │ └── awesomeface.dds │ ├── Fox.bin │ ├── Fox.gltf │ ├── Texture.png │ ├── awesomeface.png │ ├── back.jpg │ ├── bottom.jpg │ ├── fox.glb │ ├── front.jpg │ ├── left.jpg │ ├── right.jpg │ └── top.jpg ├── Easel.Tests.csproj ├── Easel.Tests.dxvk-cache ├── Program.cs ├── TestGame.cs ├── TestScenes │ ├── Test2D.cs │ ├── Test3D.cs │ ├── TestAudio.cs │ ├── TestCanvas.cs │ ├── TestFont.cs │ ├── TestGame.cs │ ├── TestManyEntities.cs │ ├── TestPhysics.cs │ ├── TestServer.cs │ └── TestShadow.cs └── imgui.ini ├── Easel.sln ├── Easel ├── Animations │ ├── Animation.cs │ ├── IAnimationChannel.cs │ ├── IAnimationKeyframe.cs │ ├── TransformChannel.cs │ ├── TransformKeyframe.cs │ └── Tween.cs ├── Audio │ ├── AudioEffect.cs │ └── EaselAudio.cs ├── Content │ ├── BitmapProcessor.cs │ ├── Builder │ │ ├── ContentBuilder.cs │ │ ├── ContentDefinition.cs │ │ ├── ContentValidity.cs │ │ ├── DuplicateHandling.cs │ │ ├── FontContent.cs │ │ ├── IContentType.cs │ │ ├── ImageContent.cs │ │ ├── ModelContent.cs │ │ └── SoundContent.cs │ ├── ContentManager.cs │ ├── FontProcessor.cs │ ├── IContentProcessor.cs │ ├── Localization │ │ └── Locale.cs │ ├── ModelProcessor.cs │ ├── SoundProcessor.cs │ └── TextureProcessor.cs ├── Data │ ├── Data.cs │ ├── DisplayConfig.cs │ ├── EaselConfig.cs │ └── XmlSerializer.cs ├── Easel.csproj ├── EaselGame.cs ├── EaselLogo.png ├── EaselWindow.cs ├── Entities │ ├── Camera.cs │ ├── Components │ │ ├── Component.cs │ │ ├── DirectionalLight.cs │ │ ├── ModelRenderer.cs │ │ ├── NoClipCamera.cs │ │ ├── Rigidbody.cs │ │ └── Sprite.cs │ ├── Entity.cs │ ├── Tags.cs │ └── Transform.cs ├── Formats │ ├── DDS.cs │ ├── ETF.cs │ └── ETF.md ├── GUI │ ├── Anchor.cs │ ├── BBCode │ │ ├── BBCodeInstruction.cs │ │ ├── BBCodeParser.cs │ │ ├── ColorInstruction.cs │ │ ├── InstructionType.cs │ │ └── TextInstruction.cs │ ├── Charmap.cs │ ├── Font.cs │ ├── FontHelper.cs │ ├── FontOptions.cs │ ├── Justification.cs │ ├── Panel.cs │ ├── Position.cs │ ├── Style.cs │ ├── UI.cs │ └── UIElement.cs ├── GameSettings.cs ├── Input.cs ├── Interfaces │ └── InheritableEntity.cs ├── Metrics.cs ├── Physics │ ├── Internal │ │ ├── BroadPhaseLayerInterfaceImpl.cs │ │ ├── Layers.cs │ │ ├── ObjectLayerPairFilterImpl.cs │ │ └── ObjectVsBroadPhaseLayerFilterImpl.cs │ ├── PhysicsInitSettings.cs │ ├── Shapes │ │ ├── BoxShape.cs │ │ └── IShape.cs │ ├── Simulation.cs │ └── Structs │ │ ├── BodyType.cs │ │ └── RigidbodyInitSettings.cs ├── Roboto-Regular.ttf ├── Scenes │ ├── Scene.cs │ └── SceneManager.cs ├── SystemInfo.cs ├── Time.cs ├── TitleBarFlags.cs ├── joltc.dll └── libjoltc.so ├── LICENSE ├── README.md └── Update.sh /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: Build Easel 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | submodules: 'recursive' 17 | - name: Setup .NET 18 | uses: actions/setup-dotnet@v3 19 | with: 20 | dotnet-version: 7.0.x 21 | - name: Restore dependencies 22 | run: dotnet restore 23 | - name: Build 24 | run: dotnet build --no-restore 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Pie"] 2 | path = Pie 3 | url = https://github.com/IsometricSoftware/Pie 4 | [submodule "JoltPhysicsSharp"] 5 | path = JoltPhysicsSharp 6 | url = https://github.com/piegfx/JoltPhysicsSharp 7 | branch = net7-only 8 | -------------------------------------------------------------------------------- /Easel.Core/DisposeManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Easel.Core; 5 | 6 | public static class DisposeManager 7 | { 8 | private static List _disposables; 9 | 10 | static DisposeManager() 11 | { 12 | _disposables = new List(); 13 | } 14 | 15 | public static void AddItem(IDisposable disposable) 16 | { 17 | _disposables.Add(disposable); 18 | } 19 | 20 | public static void ClearAll() 21 | { 22 | _disposables.Clear(); 23 | } 24 | 25 | public static void DisposeAll() 26 | { 27 | for (int i = 0; i < _disposables.Count; i++) 28 | _disposables[i].Dispose(); 29 | 30 | _disposables.Clear(); 31 | } 32 | } -------------------------------------------------------------------------------- /Easel.Core/Easel.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | disable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Easel.Core/EaselException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Easel.Core; 5 | 6 | public class EaselException : Exception 7 | { 8 | public EaselException() { } 9 | protected EaselException(SerializationInfo info, StreamingContext context) : base(info, context) { } 10 | public EaselException(string message) : base(message) { } 11 | public EaselException(string message, Exception innerException) : base(message, innerException) { } 12 | } -------------------------------------------------------------------------------- /Easel.Core/EnvVars.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.Core; 2 | 3 | public static class EnvVars 4 | { 5 | public const string ForceApi = "EASEL_FORCE_API"; 6 | 7 | public const string PrintEffects = "EASEL_PRINT_EFFECTS"; 8 | 9 | public const string AllowMissingContent = "EASEL_ALLOW_MISSING"; 10 | } -------------------------------------------------------------------------------- /Easel.Core/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Reflection; 5 | 6 | namespace Easel.Core; 7 | 8 | public static class Logger 9 | { 10 | public static event OnLogAdded LogAdded; 11 | 12 | public static bool ShowCallerClass; 13 | 14 | public static bool ShowNamespace; 15 | 16 | public static bool ShowCallerMethod; 17 | 18 | static Logger() 19 | { 20 | #if DEBUG 21 | ShowCallerClass = true; 22 | ShowCallerMethod = true; 23 | ShowNamespace = true; 24 | #endif 25 | } 26 | 27 | public static void Log(LogType type, string message) 28 | { 29 | string caller = GetCaller(3); 30 | LogAdded?.Invoke(type, caller, message); 31 | if (type == LogType.Fatal) 32 | throw new EaselException(message); 33 | } 34 | 35 | public static void Debug(string message) 36 | { 37 | Log(LogType.Debug, message); 38 | } 39 | 40 | public static void Info(string message) 41 | { 42 | Log(LogType.Info, message); 43 | } 44 | 45 | public static void Warn(string message) 46 | { 47 | Log(LogType.Warn, message); 48 | } 49 | 50 | public static void Error(string message) 51 | { 52 | Log(LogType.Error, message); 53 | } 54 | 55 | public static void Fatal(string message) 56 | { 57 | Log(LogType.Fatal, message); 58 | } 59 | 60 | private static string GetCaller(int frames) 61 | { 62 | if (!(ShowCallerClass || ShowCallerMethod)) 63 | return ""; 64 | 65 | MethodBase info = new StackFrame(frames).GetMethod(); 66 | string caller = ""; 67 | if (ShowCallerClass) 68 | caller += ShowNamespace ? info.DeclaringType.FullName : info.DeclaringType.Name; 69 | if (ShowCallerMethod) 70 | caller += "::" + info.Name; 71 | return caller; 72 | } 73 | 74 | public delegate void OnLogAdded(LogType type, string caller, string message); 75 | 76 | private static string GetLogMessage(LogType type, string caller, string message) 77 | { 78 | if (caller.Length > 0) 79 | caller += " "; 80 | 81 | return "[" + caller + type.ToString().ToUpper() + "] " + message; 82 | } 83 | 84 | public enum LogType 85 | { 86 | Debug, 87 | Info, 88 | Warn, 89 | Error, 90 | Fatal 91 | } 92 | 93 | public static void UseConsoleLogs() 94 | { 95 | LogAdded += ConsoleLog; 96 | } 97 | 98 | private static void ConsoleLog(LogType type, string caller, string message) 99 | { 100 | string msg = GetLogMessage(type, caller, message); 101 | 102 | ConsoleColor color = Console.ForegroundColor; 103 | 104 | Console.ForegroundColor = type switch 105 | { 106 | LogType.Debug => color, 107 | LogType.Info => ConsoleColor.Cyan, 108 | LogType.Warn => ConsoleColor.Yellow, 109 | LogType.Error => ConsoleColor.Red, 110 | LogType.Fatal => ConsoleColor.DarkRed, 111 | _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) 112 | }; 113 | 114 | Console.WriteLine(msg); 115 | Console.ForegroundColor = color; 116 | } 117 | 118 | #region Log file 119 | 120 | public static string LogFilePath { get; private set; } 121 | 122 | private static StreamWriter _stream; 123 | 124 | public static void InitializeLogFile(string path) 125 | { 126 | LogFilePath = path; 127 | _stream = new StreamWriter(path, true); 128 | _stream.AutoFlush = true; 129 | 130 | LogAdded += LogFile; 131 | } 132 | 133 | private static void LogFile(LogType type, string caller, string message) 134 | { 135 | _stream.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff") + ": " + GetLogMessage(type, caller, message)); 136 | } 137 | 138 | #endregion 139 | } -------------------------------------------------------------------------------- /Easel.Core/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Numerics; 5 | using System.Reflection; 6 | using System.Text; 7 | using Easel.Math; 8 | using Mth = System.Math; 9 | 10 | namespace Easel.Core; 11 | 12 | /// 13 | /// Provides certain utilities and extension methods. 14 | /// 15 | public static class Utils 16 | { 17 | /// 18 | /// Get a random value between the min (inclusive) and max (exclusive) value. 19 | /// 20 | /// The random instance. 21 | /// The minimum value (inclusive). 22 | /// The maximum value (exclusive). 23 | /// 24 | public static float NextFloat(this Random random, float min, float max) 25 | { 26 | return EaselMath.Lerp(min, max, random.NextSingle()); 27 | } 28 | 29 | public static Vector3 ToEulerAngles(this Quaternion quat) 30 | { 31 | double test = quat.X * quat.Y + quat.Z * quat.W; 32 | 33 | double yaw, pitch, roll; 34 | 35 | const double accuracy = 0.4999; 36 | 37 | if (test > accuracy) 38 | { 39 | yaw = 2 * Mth.Atan2(quat.X, quat.W); 40 | pitch = Mth.PI / 2; 41 | roll = 0; 42 | } 43 | else if (test < -accuracy) 44 | { 45 | yaw = -2 * Mth.Atan2(quat.X, quat.W); 46 | pitch = -Mth.PI / 2; 47 | roll = 0; 48 | } 49 | else 50 | { 51 | double xx = quat.X * quat.X; 52 | double yy = quat.Y * quat.Y; 53 | double zz = quat.Z * quat.Z; 54 | 55 | yaw = Mth.Atan2(2 * quat.Y * quat.W - 2 * quat.X * quat.Z, 1 - 2 * yy - 2 * zz); 56 | pitch = Mth.Asin(2 * test); 57 | roll = Mth.Atan2(2 * quat.X * quat.W - 2 * quat.Y * quat.Z, 1 - 2 * xx - 2 * zz); 58 | } 59 | 60 | return new Vector3((float) yaw, (float) pitch, (float) roll); 61 | } 62 | 63 | public static System.Numerics.Vector2 ToVector2(this Vector3 vector3) 64 | { 65 | return new System.Numerics.Vector2(vector3.X, vector3.Y); 66 | } 67 | 68 | public static Matrix4x4 To3x3Matrix(this Matrix4x4 matrix) 69 | { 70 | return new Matrix4x4( 71 | matrix.M11, matrix.M12, matrix.M13, 0, 72 | matrix.M21, matrix.M22, matrix.M23, 0, 73 | matrix.M31, matrix.M32, matrix.M33, 0, 74 | 0, 0, 0, 1); 75 | } 76 | 77 | /// 78 | /// Load an embedded resource with the given name. 79 | /// 80 | /// The assembly name of the resource to load. 81 | /// The loaded resource. 82 | public static byte[] LoadEmbeddedResource(Assembly assembly, string assemblyName) 83 | { 84 | using Stream stream = assembly.GetManifestResourceStream(assemblyName); 85 | using MemoryStream memoryStream = new MemoryStream(); 86 | stream!.CopyTo(memoryStream); 87 | return memoryStream.ToArray(); 88 | } 89 | 90 | public static string LoadEmbeddedString(Assembly assembly, string assemblyName, Encoding encoding) 91 | { 92 | return encoding.GetString(LoadEmbeddedResource(assembly, assemblyName)); 93 | } 94 | 95 | public static string LoadEmbeddedString(Assembly assembly, string assemblyName) => LoadEmbeddedString(assembly, assemblyName, Encoding.UTF8); 96 | 97 | public static byte[] Compress(byte[] data, CompressionLevel level = CompressionLevel.Optimal) 98 | { 99 | using MemoryStream stream = new MemoryStream(); 100 | using DeflateStream deflate = new DeflateStream(stream, level); 101 | deflate.Write(data, 0, data.Length); 102 | return stream.ToArray(); 103 | } 104 | 105 | public static byte[] Decompress(byte[] data) 106 | { 107 | using MemoryStream stream = new MemoryStream(data); 108 | using MemoryStream output = new MemoryStream(); 109 | using DeflateStream deflate = new DeflateStream(stream, CompressionMode.Decompress); 110 | deflate.CopyTo(output); 111 | return output.ToArray(); 112 | } 113 | } -------------------------------------------------------------------------------- /Easel.Graphics/Bitmap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Easel.Core; 4 | using Easel.Math; 5 | using Pie; 6 | using StbImageSharp; 7 | 8 | namespace Easel.Graphics; 9 | 10 | /// 11 | /// A helper class for loading bitmap images, supporting popular image formats such as png, jpg, and bmp. 12 | /// Unlike a , this does not allocate any GPU memory, and is therefore recommended 13 | /// for long-term storage of bitmap images. 14 | /// 15 | public class Bitmap 16 | { 17 | /// 18 | /// The byte data of this bitmap. Its size is width * height * 4. 19 | /// 20 | public readonly byte[] Data; 21 | 22 | /// 23 | /// The size (resolution), in pixels, of this bitmap. 24 | /// 25 | public readonly Size Size; 26 | 27 | /// 28 | /// The pixel format of this bitmap. 29 | /// 30 | public readonly Format Format; 31 | 32 | public Bitmap(string path) 33 | { 34 | if (!File.Exists(path)) 35 | Logger.Fatal($"Failed to find path \"{path}\"."); 36 | 37 | ImageResult result = ImageResult.FromMemory(File.ReadAllBytes(path), ColorComponents.RedGreenBlueAlpha); 38 | Data = result.Data; 39 | Size = new Size(result.Width, result.Height); 40 | Format = Format.R8G8B8A8_UNorm; 41 | } 42 | 43 | public Bitmap(byte[] fileData) 44 | { 45 | ImageResult result = ImageResult.FromMemory(fileData, ColorComponents.RedGreenBlueAlpha); 46 | Data = result.Data; 47 | Size = new Size(result.Width, result.Height); 48 | Format = Format.R8G8B8A8_UNorm; 49 | } 50 | 51 | public Bitmap(int width, int height, Format format, byte[] data) 52 | { 53 | Size = new Size(width, height); 54 | Format = format; 55 | Data = data; 56 | } 57 | 58 | internal static byte[] GetMissingBitmap(int width, int height) 59 | { 60 | byte[] data = new byte[width * height * 4]; 61 | for (int x = 0; x < width; x++) 62 | { 63 | for (int y = 0; y < height; y++) 64 | { 65 | int pos = (y * width + x) * 4; 66 | if ((x < width / 2 && y < height / 2) || (x > width / 2 && y > height / 2)) 67 | { 68 | data[pos] = 255; 69 | data[pos + 1] = 0; 70 | data[pos + 2] = 255; 71 | } 72 | 73 | data[pos + 3] = 255; 74 | } 75 | } 76 | 77 | return data; 78 | } 79 | } -------------------------------------------------------------------------------- /Easel.Graphics/BitmapF.cs: -------------------------------------------------------------------------------- 1 | /*using System.IO; 2 | using Easel.Core; 3 | using Easel.Math; 4 | using Pie; 5 | using StbImageSharp; 6 | 7 | namespace Easel.Graphics; 8 | 9 | public struct BitmapF 10 | { 11 | /// 12 | /// The byte data of this bitmap. Its size is width * height * 4. 13 | /// 14 | public readonly float[] Data; 15 | 16 | /// 17 | /// The size (resolution), in pixels, of this bitmap. 18 | /// 19 | public readonly Size Size; 20 | 21 | /// 22 | /// The pixel format of this bitmap. 23 | /// 24 | public readonly Format Format; 25 | 26 | public BitmapF(string path) 27 | { 28 | if (!File.Exists(path)) 29 | Logger.Fatal($"Failed to find path \"{path}\"."); 30 | 31 | ImageResultFloat result = ImageResultFloat.FromMemory(File.ReadAllBytes(path), ColorComponents.RedGreenBlueAlpha); 32 | Data = result.Data; 33 | Size = new Size(result.Width, result.Height); 34 | Format = Format.R8G8B8A8_UNorm; 35 | } 36 | 37 | public BitmapF(byte[] fileData) 38 | { 39 | ImageResult result = ImageResult.FromMemory(fileData, ColorComponents.RedGreenBlueAlpha); 40 | Data = result.Data; 41 | Size = new Size(result.Width, result.Height); 42 | Format = Format.R8G8B8A8_UNorm; 43 | } 44 | 45 | public BitmapF(int width, int height, Format format, byte[] data) 46 | { 47 | Size = new Size(width, height); 48 | Format = format; 49 | Data = data; 50 | } 51 | }*/ -------------------------------------------------------------------------------- /Easel.Graphics/BlendState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Easel.Core; 4 | using Pie; 5 | 6 | namespace Easel.Graphics; 7 | 8 | public class BlendState : IDisposable 9 | { 10 | private static Dictionary _cachedStates; 11 | 12 | static BlendState() 13 | { 14 | _cachedStates = new Dictionary(); 15 | } 16 | 17 | public Pie.BlendState PieBlendState; 18 | 19 | private BlendState(in BlendStateDescription description) 20 | { 21 | GraphicsDevice device = EaselGraphics.Instance.PieGraphics; 22 | PieBlendState = device.CreateBlendState(description); 23 | } 24 | 25 | public static BlendState FromDescription(in BlendStateDescription description) 26 | { 27 | if (!_cachedStates.TryGetValue(description, out BlendState state)) 28 | { 29 | Logger.Debug("Creating new blend state."); 30 | state = new BlendState(description); 31 | _cachedStates.Add(description, state); 32 | } 33 | 34 | return state; 35 | } 36 | 37 | public static BlendState Disabled => FromDescription(BlendStateDescription.Disabled); 38 | 39 | public static BlendState DisabledRgbMask => FromDescription(BlendStateDescription.Disabled with 40 | { 41 | ColorWriteMask = ColorWriteMask.Red | ColorWriteMask.Green | ColorWriteMask.Blue 42 | }); 43 | 44 | public static BlendState AlphaBlend => FromDescription(BlendStateDescription.AlphaBlend); 45 | 46 | public static BlendState Additive => FromDescription(BlendStateDescription.Additive); 47 | 48 | public static BlendState Opaque => FromDescription(BlendStateDescription.Opaque); 49 | 50 | public static BlendState NonPremultiplied => FromDescription(BlendStateDescription.NonPremultiplied); 51 | 52 | public void Dispose() 53 | { 54 | _cachedStates.Remove(PieBlendState.Description); 55 | PieBlendState.Dispose(); 56 | } 57 | } -------------------------------------------------------------------------------- /Easel.Graphics/Cubemap.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.Graphics; 2 | 3 | public class Cubemap : Texture 4 | { 5 | public Cubemap(SamplerState state, bool autoDispose) : base(state, autoDispose) { } 6 | } -------------------------------------------------------------------------------- /Easel.Graphics/DropShadow.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Easel.Math; 3 | 4 | namespace Easel.Graphics; 5 | 6 | public struct DropShadow 7 | { 8 | public Color Color; 9 | 10 | public Vector2 Offset; 11 | 12 | public DropShadow(Color color, Vector2 offset) 13 | { 14 | Color = color; 15 | Offset = offset; 16 | } 17 | } -------------------------------------------------------------------------------- /Easel.Graphics/Easel.Graphics.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | disable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Easel.Graphics/EffectLayout.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Pie; 3 | 4 | namespace Easel.Graphics; 5 | 6 | /// 7 | /// Represents a combined and . 8 | /// 9 | public class EffectLayout : IDisposable 10 | { 11 | /// 12 | /// The of this effect layout. 13 | /// 14 | public readonly Effect Effect; 15 | 16 | /// 17 | /// The of this effect layout. 18 | /// 19 | public readonly InputLayout Layout; 20 | 21 | public readonly uint Stride; 22 | 23 | /// 24 | /// Create a new with the given and . 25 | /// 26 | /// The to use. 27 | /// The to use. 28 | public EffectLayout(Effect effect, InputLayout layout, uint stride) 29 | { 30 | Effect = effect; 31 | Layout = layout; 32 | Stride = stride; 33 | } 34 | 35 | public void Dispose() 36 | { 37 | Effect.Dispose(); 38 | Layout.Dispose(); 39 | } 40 | } -------------------------------------------------------------------------------- /Easel.Graphics/Effects/GaussianBlur.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Easel.Core; 4 | using Easel.Graphics.Renderers; 5 | using Easel.Math; 6 | using Pie; 7 | using Pie.ShaderCompiler; 8 | 9 | namespace Easel.Graphics.Effects; 10 | 11 | public class GaussianBlur : IDisposable 12 | { 13 | private static Effect _effect; 14 | 15 | private RenderTarget _target; 16 | private RenderTarget _target2; 17 | 18 | public Texture Texture; 19 | 20 | public float Radius; 21 | 22 | public int Iterations; 23 | 24 | public GaussianBlur(Texture texture, float radius, int iterations, bool autoDispose = true) 25 | { 26 | Texture = texture; 27 | Radius = radius; 28 | Iterations = iterations; 29 | 30 | _target = new RenderTarget(texture.Size, autoDispose: false); 31 | _target2 = new RenderTarget(texture.Size, autoDispose: false); 32 | 33 | _effect ??= Effect.FromPath("Easel.Graphics.Shaders.SpriteRenderer.Sprite_vert.spv", 34 | "Easel.Graphics.Shaders.SpriteRenderer.Sprite_frag.spv", 35 | constants: new[] { new SpecializationConstant(0, 1u) }); 36 | 37 | if (autoDispose) 38 | DisposeManager.AddItem(this); 39 | } 40 | 41 | public Texture Blur() 42 | { 43 | EaselGraphics graphics = EaselGraphics.Instance; 44 | SpriteRenderer renderer = graphics.SpriteRenderer; 45 | 46 | graphics.SetRenderTarget(_target); 47 | graphics.Clear(Color.Black); 48 | //graphics.Viewport = new Rectangle(Vector2T.Zero, graphics.MainTarget.Size); 49 | 50 | renderer.Begin(blendState: BlendState.DisabledRgbMask); 51 | 52 | renderer.Draw(Texture, Vector2.Zero, null, Color.White, 0, Vector2.Zero, Vector2.One); 53 | 54 | renderer.End(); 55 | 56 | for (int i = 0; i < Iterations; i++) 57 | { 58 | float radius = (Iterations - i - 1) * Radius; 59 | Vector2 direction = i % 2 == 0 ? new Vector2(radius, 0) : new Vector2(0, radius); 60 | 61 | graphics.SetRenderTarget(_target2); 62 | renderer.Begin(effect: _effect, blendState: BlendState.Disabled); 63 | 64 | renderer.Draw(_target, Vector2.Zero, null, Color.White, 0, Vector2.Zero, Vector2.One, 65 | meta1: new Vector4((System.Numerics.Vector2) direction, _target.Size.Width, _target.Size.Height)); 66 | 67 | renderer.End(); 68 | graphics.SetRenderTarget(null); 69 | (_target, _target2) = (_target2, _target); 70 | } 71 | 72 | return _target; 73 | } 74 | 75 | public void Dispose() 76 | { 77 | _target.Dispose(); 78 | _target2.Dispose(); 79 | } 80 | } -------------------------------------------------------------------------------- /Easel.Graphics/Lighting/DirectionalLight.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Easel.Graphics.Renderers.Structs; 4 | using Easel.Math; 5 | 6 | namespace Easel.Graphics.Lighting; 7 | 8 | public struct DirectionalLight 9 | { 10 | private Vector2 _direction; 11 | private Vector3 _position; 12 | 13 | private Matrix4x4 _projection; 14 | private Matrix4x4 _view; 15 | 16 | public Vector2 Direction 17 | { 18 | get => _direction; 19 | set 20 | { 21 | _direction = value; 22 | float theta = -value.Y; 23 | float phi = value.X; 24 | _position = new Vector3(MathF.Cos(phi) * MathF.Cos(theta), MathF.Cos(phi) * MathF.Sin(theta), 25 | MathF.Sin(phi)); 26 | } 27 | } 28 | 29 | public Color Color; 30 | 31 | public ShadowMap ShadowMap; 32 | 33 | public DirectionalLight(Vector2 direction, Color color, int numShadowCascades = 0) 34 | { 35 | Direction = direction; 36 | Color = color; 37 | if (numShadowCascades > 0) 38 | ShadowMap = new ShadowMap(new Size(1024), numShadowCascades); 39 | } 40 | 41 | public ShaderDirLight ShaderDirLight => new ShaderDirLight() 42 | { 43 | Direction = new Vector4(_position, 0), 44 | Color = Color 45 | }; 46 | 47 | private void CalculateViewProjMatrices() 48 | { 49 | // TODO: Move these to user-adjustable values. 50 | const float near = 0.1f; 51 | const float far = 1000.0f; 52 | } 53 | } -------------------------------------------------------------------------------- /Easel.Graphics/Lighting/DirectionalShadow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Easel.Graphics.Lighting; 4 | 5 | public class DirectionalShadow : IDisposable 6 | { 7 | 8 | 9 | public void Dispose() 10 | { 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /Easel.Graphics/Lighting/ShadowMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Easel.Core; 3 | using Easel.Math; 4 | using Pie; 5 | using Color = System.Drawing.Color; 6 | 7 | namespace Easel.Graphics.Lighting; 8 | 9 | public class ShadowMap : IDisposable 10 | { 11 | internal Framebuffer[] Framebuffers; 12 | internal Pie.Texture[] Textures; 13 | public Pie.SamplerState SamplerState; 14 | 15 | public ShadowMap(Size size, int numCascades) 16 | { 17 | GraphicsDevice device = EaselGraphics.Instance.PieGraphics; 18 | 19 | // TODO: USE TEXTURE ARRAYS!!!!!!!!!!!!!11111111111 20 | 21 | Textures = new Pie.Texture[numCascades]; 22 | Framebuffers = new Framebuffer[numCascades]; 23 | 24 | for (int i = 0; i < numCascades; i++) 25 | { 26 | Textures[i] = device.CreateTexture(TextureDescription.Texture2D(size.Width, size.Height, Format.D32_Float, 27 | 1, 1, TextureUsage.ShaderResource | TextureUsage.Framebuffer)); 28 | Framebuffers[i] = device.CreateFramebuffer(new FramebufferAttachment(Textures[i])); 29 | } 30 | 31 | SamplerState = device.CreateSamplerState(new SamplerStateDescription(TextureFilter.MinMagMipLinear, 32 | TextureAddress.ClampToBorder, TextureAddress.ClampToBorder, TextureAddress.ClampToBorder, 0, Color.White, 0, 33 | float.MaxValue)); 34 | } 35 | 36 | public void Dispose() 37 | { 38 | for (int i = 0; i < Framebuffers.Length; i++) 39 | { 40 | Framebuffers[i].Dispose(); 41 | Textures[i].Dispose(); 42 | } 43 | 44 | SamplerState.Dispose(); 45 | } 46 | } -------------------------------------------------------------------------------- /Easel.Graphics/Materials/Material.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Numerics; 5 | using Easel.Core; 6 | using Easel.Graphics.Renderers.Structs; 7 | using Easel.Math; 8 | using Pie; 9 | using Pie.ShaderCompiler; 10 | 11 | namespace Easel.Graphics.Materials; 12 | 13 | /// 14 | /// A material represents a set of parameters that tells Easel how to render an object. 15 | /// 16 | public abstract class Material : IDisposable 17 | { 18 | public const int TextureBindingLoc = 3; 19 | 20 | private static Dictionary _cache; 21 | private int _hash; 22 | 23 | public bool IsDisposed { get; private set; } 24 | 25 | static Material() 26 | { 27 | _cache = new Dictionary(); 28 | } 29 | 30 | /// 31 | /// The of this material. 32 | /// Each material will contain its own . 33 | /// 34 | public EffectLayout EffectLayout { get; protected set; } 35 | 36 | /// 37 | /// Is when using a translucent material, such as . 38 | /// 39 | public bool IsTranslucent { get; protected set; } 40 | 41 | public abstract ShaderMaterial ShaderMaterial { get; } 42 | 43 | /// 44 | /// How much the texture will tile. (Default: 1) 45 | /// 46 | public Vector2 Tiling; 47 | 48 | /// 49 | /// The rasterizer state of this material. (Default: CullClockwise) 50 | /// 51 | public RasterizerState RasterizerState; 52 | 53 | // TODO: Depth states 54 | public BlendState BlendState; 55 | 56 | protected Material() 57 | { 58 | Tiling = Vector2.One; 59 | RasterizerState = RasterizerState.CullCounterClockwise; 60 | BlendState = BlendState.NonPremultiplied; 61 | DisposeManager.AddItem(this); 62 | } 63 | 64 | protected internal abstract void ApplyTextures(GraphicsDevice device); 65 | 66 | protected EffectLayout GetEffectLayout(byte[] vShader, byte[] fShader, SpecializationConstant[] constants, InputLayoutDescription[] descriptions, uint stride) 67 | { 68 | // TODO: Potentially a better way of getting a hash code? 69 | 70 | _hash = unchecked(Convert.ToBase64String(vShader).GetHashCode() + 71 | Convert.ToBase64String(fShader).GetHashCode() + 72 | constants.Sum(constant => constant.GetHashCode())); 73 | if (!_cache.TryGetValue(_hash, out MaterialCache cache)) 74 | { 75 | Logger.Debug($"Creating new material cache. (ID: {_hash})"); 76 | InputLayout layout = EaselGraphics.Instance.PieGraphics.CreateInputLayout(descriptions); 77 | 78 | cache = new MaterialCache() 79 | { 80 | EffectLayout = new EffectLayout(new Effect(vShader, fShader, constants), layout, stride), 81 | NumReferences = 0 82 | }; 83 | 84 | _cache.Add(_hash, cache); 85 | } 86 | 87 | cache.NumReferences++; 88 | return cache.EffectLayout; 89 | } 90 | 91 | public virtual void Dispose() 92 | { 93 | if (IsDisposed) 94 | return; 95 | IsDisposed = true; 96 | 97 | MaterialCache cache = _cache[_hash]; 98 | cache.NumReferences--; 99 | if (cache.NumReferences <= 0) 100 | { 101 | Logger.Debug($"Disposing of material cache. (ID: {_hash})"); 102 | cache.EffectLayout.Dispose(); 103 | _cache.Remove(_hash); 104 | } 105 | 106 | Logger.Debug("Material disposed."); 107 | } 108 | 109 | private class MaterialCache 110 | { 111 | public EffectLayout EffectLayout; 112 | public int NumReferences; 113 | } 114 | } -------------------------------------------------------------------------------- /Easel.Graphics/Materials/TranslucentStandardMaterial.cs: -------------------------------------------------------------------------------- 1 | using Pie.ShaderCompiler; 2 | 3 | namespace Easel.Graphics.Materials; 4 | 5 | public class TranslucentStandardMaterial : StandardMaterial 6 | { 7 | public TranslucentStandardMaterial() : this(Texture2D.White) { } 8 | 9 | public TranslucentStandardMaterial(Texture albedo) : this(albedo, Texture2D.EmptyNormal, Texture2D.Black, Texture2D.White, Texture2D.White) { } 10 | 11 | public TranslucentStandardMaterial(Texture albedo, Texture normal, Texture metallicRoughnessAo) : base(albedo, 12 | normal, metallicRoughnessAo, metallicRoughnessAo, metallicRoughnessAo, 13 | new[] { new SpecializationConstant(0, (uint) (0x1 | 0x2 | 0x4)) }) 14 | { 15 | IsTranslucent = true; 16 | } 17 | 18 | public TranslucentStandardMaterial(Texture albedo, Texture normal, Texture metallic, Texture roughness, Texture ao) 19 | : base(albedo, normal, metallic, roughness, ao, new[] { new SpecializationConstant(0, (uint) (0x1 | 0x4)) }) 20 | { 21 | IsTranslucent = true; 22 | } 23 | } -------------------------------------------------------------------------------- /Easel.Graphics/Mesh.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Numerics; 5 | using Easel.Core; 6 | using Easel.Graphics.Primitives; 7 | using Silk.NET.Assimp; 8 | using Material = Easel.Graphics.Materials.Material; 9 | 10 | namespace Easel.Graphics; 11 | 12 | public struct Mesh 13 | { 14 | public VertexPositionTextureNormalTangent[] Vertices; 15 | public uint[] Indices; 16 | public Material Material; 17 | 18 | public Mesh(VertexPositionTextureNormalTangent[] vertices, uint[] indices, Material material) 19 | { 20 | Vertices = vertices; 21 | Indices = indices; 22 | Material = material; 23 | } 24 | } -------------------------------------------------------------------------------- /Easel.Graphics/ModelMesh.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace Easel.Graphics; 4 | 5 | public class ModelMesh 6 | { 7 | public Mesh[] Meshes; 8 | 9 | public Matrix4x4 Transform; 10 | 11 | public ModelMesh(Mesh[] meshes, Matrix4x4 transform) 12 | { 13 | Meshes = meshes; 14 | Transform = transform; 15 | } 16 | } -------------------------------------------------------------------------------- /Easel.Graphics/Primitives/Cube.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Easel.Math; 3 | 4 | namespace Easel.Graphics.Primitives; 5 | 6 | /// 7 | /// A cube primitive, containing 6 sides, texture coordinates, and normals. 8 | /// 9 | public struct Cube : IPrimitive 10 | { 11 | public VertexPositionTextureNormalTangent[] Vertices => new[] 12 | { 13 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, 0.5f, -0.5f), new Vector2(0, 0), new Vector3(0, 1, 0), new Vector3(-1, 0, 0)), 14 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, 0.5f, -0.5f), new Vector2(1, 0), new Vector3(0, 1, 0), new Vector3(-1, 0, 0)), 15 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, 0.5f, 0.5f), new Vector2(1, 1), new Vector3(0, 1, 0), new Vector3(-1, 0, 0)), 16 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, 0.5f, 0.5f), new Vector2(0, 1), new Vector3(0, 1, 0), new Vector3(-1, 0, 0)), 17 | 18 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, -0.5f, 0.5f), new Vector2(0, 0), new Vector3(0, -1, 0), new Vector3(1, 0, 0)), 19 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, -0.5f, 0.5f), new Vector2(1, 0), new Vector3(0, -1, 0), new Vector3(1, 0, 0)), 20 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, -0.5f, -0.5f), new Vector2(1, 1), new Vector3(0, -1, 0), new Vector3(1, 0, 0)), 21 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, -0.5f, -0.5f), new Vector2(0, 1), new Vector3(0, -1, 0), new Vector3(1, 0, 0)), 22 | 23 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, 0.5f, -0.5f), new Vector2(0, 0), new Vector3(-1, 0, 0), new Vector3(0, 0, 1)), 24 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, 0.5f, 0.5f), new Vector2(1, 0), new Vector3(-1, 0, 0), new Vector3(0, 0, 1)), 25 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, -0.5f, 0.5f), new Vector2(1, 1), new Vector3(-1, 0, 0), new Vector3(0, 0, 1)), 26 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, -0.5f, -0.5f), new Vector2(0, 1), new Vector3(-1, 0, 0), new Vector3(0, 0, 1)), 27 | 28 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, 0.5f, 0.5f), new Vector2(0, 0), new Vector3(1, 0, 0), new Vector3(0, 0, 1)), 29 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, 0.5f, -0.5f), new Vector2(1, 0), new Vector3(1, 0, 0), new Vector3(0, 0, 1)), 30 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, -0.5f, -0.5f), new Vector2(1, 1), new Vector3(1, 0, 0), new Vector3(0, 0, 1)), 31 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, -0.5f, 0.5f), new Vector2(0, 1), new Vector3(1, 0, 0), new Vector3(0, 0, 1)), 32 | 33 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, 0.5f, -0.5f), new Vector2(0, 0), new Vector3(0, 0, -1), new Vector3(-1, 0, 0)), 34 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, 0.5f, -0.5f), new Vector2(1, 0), new Vector3(0, 0, -1), new Vector3(-1, 0, 0)), 35 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, -0.5f, -0.5f), new Vector2(1, 1), new Vector3(0, 0, -1), new Vector3(-1, 0, 0)), 36 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, -0.5f, -0.5f), new Vector2(0, 1), new Vector3(0, 0, -1), new Vector3(-1, 0, 0)), 37 | 38 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, 0.5f, 0.5f), new Vector2(0, 0), new Vector3(0, 0, 1), new Vector3(-1, 0, 0)), 39 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, 0.5f, 0.5f), new Vector2(1, 0), new Vector3(0, 0, 1), new Vector3(-1, 0, 0)), 40 | new VertexPositionTextureNormalTangent(new Vector3(0.5f, -0.5f, 0.5f), new Vector2(1, 1), new Vector3(0, 0, 1), new Vector3(-1, 0, 0)), 41 | new VertexPositionTextureNormalTangent(new Vector3(-0.5f, -0.5f, 0.5f), new Vector2(0, 1), new Vector3(0, 0, 1), new Vector3(-1, 0, 0)) 42 | }; 43 | 44 | public uint[] Indices => new uint[] 45 | { 46 | 0, 1, 2, 0, 2, 3, 47 | 4, 5, 6, 4, 6, 7, 48 | 8, 9, 10, 8, 10, 11, 49 | 12, 13, 14, 12, 14, 15, 50 | 16, 17, 18, 16, 18, 19, 51 | 20, 21, 22, 20, 22, 23 52 | }; 53 | } -------------------------------------------------------------------------------- /Easel.Graphics/Primitives/IPrimitive.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.Graphics.Primitives; 2 | 3 | /// 4 | /// The base interface for in-built engine primitives. 5 | /// 6 | public interface IPrimitive 7 | { 8 | /// 9 | /// The vertices of this primitive. 10 | /// 11 | public VertexPositionTextureNormalTangent[] Vertices { get; } 12 | 13 | /// 14 | /// The indices of this primitive. 15 | /// 16 | public uint[] Indices { get; } 17 | } -------------------------------------------------------------------------------- /Easel.Graphics/Primitives/Quad.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.Graphics.Primitives; 2 | 3 | public struct Quad : IPrimitive 4 | { 5 | public VertexPositionTextureNormalTangent[] Vertices { get; } 6 | public uint[] Indices { get; } 7 | } -------------------------------------------------------------------------------- /Easel.Graphics/Primitives/Sphere.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Numerics; 4 | 5 | namespace Easel.Graphics.Primitives; 6 | 7 | public struct Sphere : IPrimitive 8 | { 9 | private VertexPositionTextureNormalTangent[] _vertices; 10 | 11 | private uint[] _indices; 12 | 13 | public VertexPositionTextureNormalTangent[] Vertices => _vertices; 14 | public uint[] Indices => _indices; 15 | 16 | public Sphere(int latitude, int longitude, float radius) 17 | { 18 | List vertices = new List(); 19 | List indices = new List(); 20 | 21 | VertexPositionTextureNormalTangent vptnt = new VertexPositionTextureNormalTangent(); 22 | 23 | for (int lat = 0; lat <= latitude; lat++) 24 | { 25 | float theta = lat * MathF.PI / latitude; 26 | float sinTheta = MathF.Sin(theta); 27 | float cosTheta = MathF.Cos(theta); 28 | 29 | for (int lng = 0; lng <= longitude; lng++) 30 | { 31 | float phi = lng * 2 * MathF.PI / longitude; 32 | float sinPhi = MathF.Sin(phi); 33 | float cosPhi = MathF.Cos(phi); 34 | 35 | // TODO: U component of tex coords is seemingly reversed. 36 | vptnt.Normals = new Vector3(cosPhi * sinTheta, cosTheta, sinPhi * sinTheta); 37 | vptnt.TexCoords = new Vector2((lng / (float) longitude), (lat / (float) latitude)); 38 | vptnt.Position = new Vector3(radius * vptnt.Normals.X, radius * vptnt.Normals.Y, radius * vptnt.Normals.Z); 39 | 40 | vertices.Add(vptnt); 41 | } 42 | } 43 | 44 | for (int lat = 0; lat < latitude; lat++) 45 | { 46 | for (int lng = 0; lng < longitude; lng++) 47 | { 48 | uint first = (uint) ((lat * (longitude + 1)) + lng); 49 | uint second = (uint) (first + longitude + 1); 50 | 51 | indices.Add(first); 52 | indices.Add(second); 53 | indices.Add(first + 1); 54 | 55 | indices.Add(second); 56 | indices.Add(second + 1); 57 | indices.Add(first + 1); 58 | } 59 | } 60 | 61 | _vertices = vertices.ToArray(); 62 | _indices = indices.ToArray(); 63 | 64 | for (int i = 0; i < indices.Count; i += 3) 65 | { 66 | ref VertexPositionTextureNormalTangent v0 = ref _vertices[_indices[i]]; 67 | ref VertexPositionTextureNormalTangent v1 = ref _vertices[_indices[i + 1]]; 68 | ref VertexPositionTextureNormalTangent v2 = ref _vertices[_indices[i + 2]]; 69 | 70 | Vector3 edge1 = v1.Position - v0.Position; 71 | Vector3 edge2 = v2.Position - v0.Position; 72 | 73 | float deltaU1 = v1.TexCoords.X - v0.TexCoords.X; 74 | float deltaV1 = v1.TexCoords.Y - v0.TexCoords.Y; 75 | float deltaU2 = v2.TexCoords.X - v0.TexCoords.X; 76 | float deltaV2 = v2.TexCoords.Y - v0.TexCoords.Y; 77 | 78 | float f = 1f / (deltaU1 * deltaV2 - deltaU2 * deltaV1); 79 | 80 | Vector3 tangent = new Vector3( 81 | f * (deltaV2 * edge1.X - deltaV1 * edge2.X), 82 | f * (deltaV2 * edge1.Y - deltaV1 * edge2.Y), 83 | f * (deltaV2 * edge1.Z - deltaV1 * edge2.Z)); 84 | 85 | v0.Tangents += tangent; 86 | v1.Tangents += tangent; 87 | v2.Tangents += tangent; 88 | } 89 | 90 | for (int i = 0; i < _vertices.Length; i++) 91 | _vertices[i].Tangents = Vector3.Normalize(_vertices[i].Tangents); 92 | } 93 | } -------------------------------------------------------------------------------- /Easel.Graphics/RasterizerState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Easel.Core; 4 | using Pie; 5 | 6 | namespace Easel.Graphics; 7 | 8 | public class RasterizerState : IDisposable 9 | { 10 | private static Dictionary _cachedStates; 11 | 12 | static RasterizerState() 13 | { 14 | _cachedStates = new Dictionary(); 15 | } 16 | 17 | public Pie.RasterizerState PieRasterizerState; 18 | 19 | private RasterizerState(in RasterizerStateDescription description) 20 | { 21 | GraphicsDevice device = EaselGraphics.Instance.PieGraphics; 22 | PieRasterizerState = device.CreateRasterizerState(description); 23 | } 24 | 25 | public static RasterizerState FromDescription(in RasterizerStateDescription description) 26 | { 27 | if (!_cachedStates.TryGetValue(description, out RasterizerState state)) 28 | { 29 | Logger.Debug("Creating new rasterizer state."); 30 | state = new RasterizerState(description); 31 | _cachedStates.Add(description, state); 32 | } 33 | 34 | return state; 35 | } 36 | 37 | public static RasterizerState CullNone => FromDescription(RasterizerStateDescription.CullNone); 38 | 39 | public static RasterizerState CullClockwise => FromDescription(RasterizerStateDescription.CullClockwise); 40 | 41 | public static RasterizerState CullCounterClockwise => FromDescription(RasterizerStateDescription.CullCounterClockwise); 42 | 43 | public void Dispose() 44 | { 45 | _cachedStates.Remove(PieRasterizerState.Description); 46 | PieRasterizerState.Dispose(); 47 | } 48 | } -------------------------------------------------------------------------------- /Easel.Graphics/RenderTarget.cs: -------------------------------------------------------------------------------- 1 | using Easel.Core; 2 | using Easel.Math; 3 | using Pie; 4 | 5 | namespace Easel.Graphics; 6 | 7 | public class RenderTarget : Texture 8 | { 9 | public readonly Framebuffer PieBuffer; 10 | private Pie.Texture _depth; 11 | 12 | public RenderTarget(Size size, SamplerState samplerState = null, bool autoDispose = true) 13 | : base(samplerState ?? SamplerState.LinearClamp, autoDispose) 14 | { 15 | // TODO: RGB render targets that draw by ignoring the alpha value in the frag shader (since D3D doesn't support RGB) 16 | 17 | TextureDescription description = TextureDescription.Texture2D(size.Width, size.Height, Format.B8G8R8A8_UNorm, 1, 18 | 1, TextureUsage.ShaderResource | TextureUsage.Framebuffer); 19 | 20 | GraphicsDevice device = EaselGraphics.Instance.PieGraphics; 21 | PieTexture = device.CreateTexture(description); 22 | 23 | description.Format = Format.D24_UNorm_S8_UInt; 24 | description.Usage = TextureUsage.Framebuffer; 25 | 26 | _depth = device.CreateTexture(description); 27 | 28 | PieBuffer = device.CreateFramebuffer(new FramebufferAttachment(PieTexture), new FramebufferAttachment(_depth)); 29 | } 30 | 31 | public override void Dispose() 32 | { 33 | PieBuffer.Dispose(); 34 | //_depth.Dispose(); 35 | 36 | base.Dispose(); 37 | } 38 | } -------------------------------------------------------------------------------- /Easel.Graphics/Renderers/I2DDrawMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Easel.Math; 3 | 4 | namespace Easel.Graphics.Renderers; 5 | 6 | public interface I2DDrawMethods 7 | { 8 | public void Draw(Texture texture, Rectangle destination, Color tint, float z = 0); 9 | 10 | public void Draw(Texture texture, Rectangle destination, Rectangle? source, Color tint, float z = 0); 11 | 12 | public void Draw(Texture texture, Rectangle destination, Rectangle? source, Color tint, float rotation, 13 | Vector2 origin, SpriteFlip flip = SpriteFlip.None, float z = 0); 14 | 15 | public void Draw(Texture texture, Vector3 position); 16 | 17 | public void Draw(Texture texture, Vector3 position, Color tint); 18 | 19 | public void Draw(Texture texture, Vector3 position, Rectangle? source, Color tint); 20 | 21 | public void Draw(Texture texture, Vector3 position, Rectangle? source, Color tint, float rotation, 22 | Vector2 origin, float scale, SpriteFlip flip = SpriteFlip.None); 23 | 24 | public void Draw(Texture texture, Vector3 position, Rectangle? source, Color tint, float rotation, 25 | Vector2 origin, Vector2 scale, SpriteFlip flip = SpriteFlip.None); 26 | 27 | public void DrawRectangle(Vector3 position, Size size, int borderWidth, float radius, Color color, 28 | Color borderColor, float rotation, Vector2 origin); 29 | 30 | public void DrawRectangle(Texture texture, Vector3 position, Size size, int borderWidth, float radius, 31 | Color color, Color borderColor, float rotation, Vector2 origin); 32 | } -------------------------------------------------------------------------------- /Easel.Graphics/Renderers/IRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Easel.Graphics.Lighting; 4 | using Easel.Graphics.Renderers.Structs; 5 | 6 | namespace Easel.Graphics.Renderers; 7 | 8 | public interface IRenderer : IDisposable 9 | { 10 | public DirectionalLight? DirectionalLight { get; set; } 11 | 12 | public RenderTarget MainTarget { get; set; } 13 | 14 | /// 15 | /// Draw an object instance that will be rendered in the scene. 16 | /// 17 | /// The renderable instance to draw. 18 | /// Its world transform. 19 | public void Draw(in Renderable renderable, in Matrix4x4 world); 20 | 21 | /// 22 | /// Draw a sprite that will be batched and rendered in the scene. 23 | /// 24 | /// The sprite to render. 25 | public void DrawSprite(in Sprite sprite); 26 | 27 | /// 28 | /// Prepare the renderer for a new frame of objects. 29 | /// 30 | public void NewFrame(); 31 | 32 | public void DoneFrame(); 33 | 34 | /// 35 | /// Render all objects added to the current render frame. 36 | /// 37 | public void Perform3DPass(CameraInfo cameraInfo); 38 | 39 | public void Perform2DPass(CameraInfo cameraInfo); 40 | } -------------------------------------------------------------------------------- /Easel.Graphics/Renderers/RenderOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.Graphics.Renderers; 2 | 3 | public struct RenderOptions 4 | { 5 | /// 6 | /// If enabled, Pie's debug layer will be enabled. Useful if you are encountering unexpected graphical bugs either 7 | /// in the engine itself, or in your custom rendering code. !! WARNING !! This will spam your logs with debug 8 | /// messages. It is highly recommended that you do not enable this option when releasing. 9 | /// 10 | public bool GraphicsDebugging; 11 | 12 | /// 13 | /// If enabled, Easel will use a Deferred pipeline. Otherwise, a Forward+ pipeline will be used. 14 | /// 15 | public bool Deferred; 16 | 17 | public static RenderOptions Default => new RenderOptions() 18 | { 19 | Deferred = false, 20 | GraphicsDebugging = false 21 | }; 22 | } -------------------------------------------------------------------------------- /Easel.Graphics/Renderers/Renderable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Easel.Graphics.Materials; 4 | using Pie; 5 | 6 | namespace Easel.Graphics.Renderers; 7 | 8 | public struct Renderable : IDisposable 9 | { 10 | public GraphicsBuffer VertexBuffer; 11 | public GraphicsBuffer IndexBuffer; 12 | 13 | public uint NumIndices; 14 | public Material Material; 15 | 16 | public Renderable(GraphicsBuffer vertexBuffer, GraphicsBuffer indexBuffer, uint numIndices, Material material) 17 | { 18 | VertexBuffer = vertexBuffer; 19 | IndexBuffer = indexBuffer; 20 | NumIndices = numIndices; 21 | Material = material; 22 | } 23 | 24 | public static Renderable CreateFromMesh(in Mesh mesh) 25 | { 26 | GraphicsDevice device = EaselGraphics.Instance.PieGraphics; 27 | 28 | GraphicsBuffer vertexBuffer = device.CreateBuffer(BufferType.VertexBuffer, mesh.Vertices); 29 | GraphicsBuffer indexBuffer = device.CreateBuffer(BufferType.IndexBuffer, mesh.Indices); 30 | 31 | return new Renderable(vertexBuffer, indexBuffer, (uint) mesh.Indices.Length, mesh.Material); 32 | } 33 | 34 | public static Renderable[] CreateFromMeshes(in Mesh[] meshes) 35 | { 36 | List renderables = new List(meshes.Length); 37 | 38 | for (int i = 0; i < meshes.Length; i++) 39 | renderables.Add(CreateFromMesh(meshes[i])); 40 | 41 | return renderables.ToArray(); 42 | } 43 | 44 | public void Dispose() 45 | { 46 | VertexBuffer.Dispose(); 47 | IndexBuffer.Dispose(); 48 | } 49 | } -------------------------------------------------------------------------------- /Easel.Graphics/Renderers/Sprite.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Easel.Math; 3 | 4 | namespace Easel.Graphics.Renderers; 5 | 6 | public struct Sprite 7 | { 8 | public Texture Texture; 9 | public Vector3 Position; 10 | public Rectangle? Source; 11 | public Color Tint; 12 | public float Rotation; 13 | public Vector2 Origin; 14 | public Vector2 Scale; 15 | public SpriteFlip Flip; 16 | 17 | public Sprite(Texture texture, Vector3 position, Rectangle? source, Color tint, float rotation, Vector2 origin, Vector2 scale, SpriteFlip flip) 18 | { 19 | Texture = texture; 20 | Position = position; 21 | Source = source; 22 | Tint = tint; 23 | Rotation = rotation; 24 | Origin = origin; 25 | Scale = scale; 26 | Flip = flip; 27 | } 28 | } -------------------------------------------------------------------------------- /Easel.Graphics/Renderers/Structs/CameraInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Easel.Math; 3 | 4 | namespace Easel.Graphics.Renderers.Structs; 5 | 6 | public struct CameraInfo 7 | { 8 | public Matrix4x4 Projection; 9 | public Matrix4x4 View; 10 | public Vector3 Position; 11 | 12 | public Color? ClearColor; 13 | public Skybox Skybox; 14 | 15 | /// 16 | /// Create new camera info. Using this constructor will set the value automatically. 17 | /// 18 | /// The projection matrix. 19 | /// The view matrix. 20 | /// The clear color, if any. If a clear color is not set, the scene will not be cleared. 21 | /// The skybox, if any. 22 | public CameraInfo(Matrix4x4 projection, Matrix4x4 view, Color? clearColor = null, Skybox skybox = null) 23 | { 24 | Projection = projection; 25 | View = view; 26 | ClearColor = clearColor; 27 | Skybox = skybox; 28 | 29 | Matrix4x4.Invert(view, out Matrix4x4 invView); 30 | Position = invView.Translation; 31 | } 32 | } -------------------------------------------------------------------------------- /Easel.Graphics/Renderers/Structs/ProjViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Easel.Graphics.Renderers.Structs; 5 | 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct ProjViewModel 8 | { 9 | public Matrix4x4 Projection; 10 | public Matrix4x4 View; 11 | public Matrix4x4 Model; 12 | public Matrix4x4 LightSpace; 13 | 14 | public ProjViewModel() 15 | { 16 | Projection = Matrix4x4.Identity; 17 | View = Matrix4x4.Identity; 18 | Model = Matrix4x4.Identity; 19 | LightSpace = Matrix4x4.Identity; 20 | } 21 | } -------------------------------------------------------------------------------- /Easel.Graphics/Renderers/Structs/SceneInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Easel.Graphics.Renderers.Structs; 5 | 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct SceneInfo 8 | { 9 | public Vector4 CameraPos; 10 | public ShaderMaterial Material; 11 | public ShaderDirLight Sun; 12 | } -------------------------------------------------------------------------------- /Easel.Graphics/Renderers/Structs/ShaderDirLight.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.InteropServices; 3 | using Easel.Math; 4 | 5 | namespace Easel.Graphics.Renderers.Structs; 6 | 7 | [StructLayout(LayoutKind.Sequential)] 8 | public struct ShaderDirLight 9 | { 10 | public Vector4 Direction; 11 | public Color Color; 12 | } -------------------------------------------------------------------------------- /Easel.Graphics/Renderers/Structs/ShaderMaterial.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.InteropServices; 3 | using Easel.Math; 4 | 5 | namespace Easel.Graphics.Renderers.Structs; 6 | 7 | /// 8 | /// Contains all the possible material parameters that can be sent to the shader. 9 | /// Some shaders will completely ignore certain parameters. 10 | /// 11 | [StructLayout(LayoutKind.Sequential)] 12 | public struct ShaderMaterial 13 | { 14 | public Vector4 Tiling; 15 | 16 | public Color Albedo; 17 | 18 | public float Metallic; 19 | 20 | public float Roughness; 21 | 22 | public float Ao; 23 | 24 | private float _padding; 25 | } -------------------------------------------------------------------------------- /Easel.Graphics/Renderers/Structs/TransformedRenderable.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace Easel.Graphics.Renderers.Structs; 4 | 5 | public struct TransformedRenderable 6 | { 7 | public Renderable Renderable; 8 | public Matrix4x4 Transform; 9 | 10 | public TransformedRenderable(Renderable renderable, Matrix4x4 transform) 11 | { 12 | Renderable = renderable; 13 | Transform = transform; 14 | } 15 | } -------------------------------------------------------------------------------- /Easel.Graphics/SamplerState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Easel.Core; 4 | using Pie; 5 | 6 | namespace Easel.Graphics; 7 | 8 | public class SamplerState : IDisposable 9 | { 10 | private static Dictionary _cachedStates; 11 | 12 | static SamplerState() 13 | { 14 | _cachedStates = new Dictionary(); 15 | } 16 | 17 | public Pie.SamplerState PieSamplerState; 18 | 19 | private SamplerState(in SamplerStateDescription description) 20 | { 21 | GraphicsDevice device = EaselGraphics.Instance.PieGraphics; 22 | PieSamplerState = device.CreateSamplerState(description); 23 | } 24 | 25 | public static SamplerState FromDescription(in SamplerStateDescription description) 26 | { 27 | if (!_cachedStates.TryGetValue(description, out SamplerState state)) 28 | { 29 | Logger.Debug("Creating new sampler state."); 30 | state = new SamplerState(description); 31 | _cachedStates.Add(description, state); 32 | } 33 | 34 | return state; 35 | } 36 | 37 | public static SamplerState PointClamp => FromDescription(SamplerStateDescription.PointClamp); 38 | 39 | public static SamplerState PointRepeat => FromDescription(SamplerStateDescription.PointRepeat); 40 | 41 | public static SamplerState LinearClamp => FromDescription(SamplerStateDescription.LinearClamp); 42 | 43 | public static SamplerState LinearRepeat => FromDescription(SamplerStateDescription.LinearRepeat); 44 | 45 | public static SamplerState AnisotropicClamp => FromDescription(SamplerStateDescription.AnisotropicClamp); 46 | 47 | public static SamplerState AnisotropicRepeat => FromDescription(SamplerStateDescription.AnisotropicRepeat); 48 | 49 | public void Dispose() 50 | { 51 | _cachedStates.Remove(PieSamplerState.Description); 52 | PieSamplerState.Dispose(); 53 | } 54 | } -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/CompileShaders.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # WARNING! Requires that you have the Vulkan toolchain and glslc installed. 4 | 5 | printf "Compiling HLSL shaders...\n" 6 | for file in $(find . -type f -name "*.hlsl"); do 7 | printf "Compiling \"%s\"... " "$file" 8 | 9 | filename=${file%.*} 10 | 11 | hasCompiled=false 12 | 13 | if grep -q " VertexShader" "$file"; then 14 | printf "Vertex... " 15 | #glslc -fshader-stage=vertex -fentry-point="VertexShader" -fauto-combined-image-sampler -o "${filename}_vert.spv" "$file" 16 | dxc -spirv -T vs_6_0 -E "VertexShader" -Fo "${filename}_vert.spv" "$file" 17 | 18 | if [ $? -ne 0 ]; then 19 | exit 1 20 | fi 21 | 22 | hasCompiled=true 23 | fi 24 | 25 | if grep -q " PixelShader" "$file"; then 26 | printf "Pixel... " 27 | #glslc -fshader-stage=fragment -fentry-point="PixelShader" -fauto-combined-image-sampler -o "${filename}_frag.spv" "$file" 28 | dxc -spirv -T ps_6_0 -E "PixelShader" -Fo "${filename}_frag.spv" "$file" 29 | 30 | if [ $? -ne 0 ]; then 31 | exit 1 32 | fi 33 | 34 | hasCompiled=true 35 | fi 36 | 37 | if ! $hasCompiled; then 38 | printf "Ignoring as this file doesn't contain an entry point.\n" 39 | else 40 | printf "Done!\n" 41 | fi 42 | done 43 | 44 | printf "Compiling GLSL shaders...\n" 45 | for file in $(find . -type f -name "*.vert"); do 46 | printf "Compiling \"%s\"... " "$file" 47 | 48 | filename=${file%.*} 49 | 50 | glslc -o "${filename}_vert.spv" "$file" 51 | 52 | if [ $? -ne 0 ]; then 53 | exit 1 54 | fi 55 | 56 | printf "Done!\n" 57 | done 58 | 59 | for file in $(find . -type f -name "*.frag"); do 60 | printf "Compiling \"%s\"... " "$file" 61 | 62 | filename=${file%.*} 63 | 64 | glslc -o "${filename}_frag.spv" "$file" 65 | 66 | if [ $? -ne 0 ]; then 67 | exit 1 68 | fi 69 | 70 | printf "Done!\n" 71 | done 72 | -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Forward/Standard.hlsl: -------------------------------------------------------------------------------- 1 | #include "../Types.hlsl" 2 | #include "../Lighting.hlsl" 3 | 4 | struct VSInput 5 | { 6 | float3 position: POSITION; 7 | float2 texCoords: TEXCOORD; 8 | float3 normals: NORMAL; 9 | float3 tangents: TANGENT; 10 | }; 11 | 12 | struct VSOutput 13 | { 14 | float4 position: SV_Position; 15 | float2 texCoords: TEXCOORD0; 16 | float3 normal: NORMAL; 17 | float3 fragPos: TEXCOORD1; 18 | float4 lightSpace: TEXCOORD2; 19 | }; 20 | 21 | struct PSOutput 22 | { 23 | float4 color : SV_Target0; 24 | }; 25 | 26 | cbuffer ProjViewModel : register(b0) 27 | { 28 | float4x4 projection; 29 | float4x4 view; 30 | float4x4 model; 31 | float4x4 lightSpace; 32 | } 33 | 34 | cbuffer SceneInfo : register(b1) 35 | { 36 | float4 cameraPos; 37 | Material material; 38 | DirectionalLight sun; 39 | } 40 | 41 | // TODO: Probably can make this a single sampler state. 42 | 43 | Texture2D shadowTex : register(t2); 44 | SamplerState shadowState : register(s2); 45 | Texture2D albedoTex : register(t3); 46 | SamplerState albedoState : register(s3); 47 | Texture2D normalTex : register(t4); 48 | SamplerState normalState : register(s4); 49 | 50 | Texture2D metallicTex : register(t5); 51 | SamplerState metallicState : register(s5); 52 | Texture2D roughnessTex : register(t6); 53 | SamplerState roughnessState : register(s6); 54 | Texture2D aoTex : register(t7); 55 | SamplerState aoState : register(s7); 56 | 57 | // Various options during shader compilation. 58 | // 0x1 = Lighting 59 | // 0x2 = Combine textures 60 | // 0x4 = Alpha transparency 61 | [[vk::constant_id(0)]] const uint options = 0; 62 | 63 | VSOutput VertexShader(in VSInput input) 64 | { 65 | VSOutput output; 66 | 67 | float4 fragPos = mul(model, float4(input.position, 1.0)); 68 | 69 | output.position = mul(mul(projection, view), fragPos); 70 | output.texCoords = input.texCoords * material.tiling.xy; 71 | output.normal = mul((float3x3) model, input.normals); 72 | output.fragPos = (float3) fragPos; 73 | output.lightSpace = mul(lightSpace, float4(fragPos.xyz, 1.0)); 74 | 75 | return output; 76 | } 77 | 78 | PSOutput PixelShader(in VSOutput input) 79 | { 80 | PSOutput output; 81 | 82 | if ((options & 0x1) != 0x1) 83 | { 84 | float4 color = albedoTex.Sample(albedoState, input.texCoords) * material.albedo; 85 | 86 | if ((options & 0x4) == 0x4) 87 | output.color = color; 88 | else 89 | output.color = float4(color.rgb, 1.0); 90 | return output; 91 | } 92 | 93 | float4 albedo = pow(albedoTex.Sample(albedoState, input.texCoords), (float4) 2.2) * material.albedo; 94 | float3 normal = TempNormal(normalTex, normalState, input.fragPos, input.texCoords, input.normal); 95 | 96 | float metallic, roughness, ao; 97 | 98 | // if() 99 | metallic = metallicTex.Sample(metallicState, input.texCoords).r * material.metallic; 100 | roughness = roughnessTex.Sample(roughnessState, input.texCoords).r * material.roughness; 101 | ao = aoTex.Sample(aoState, input.texCoords).r * material.ao; 102 | 103 | float3 viewDir = normalize((float3) cameraPos - input.fragPos); 104 | float3 result = ProcessDirLight(sun, viewDir, albedo.rgb, normal, metallic, roughness); 105 | 106 | //float shadow = CalculateShadow(input.lightSpace, shadowTex, shadowState, normal, normalize((float3) -sun.direction - input.fragPos)); 107 | float shadow = 0.0; 108 | 109 | float3 ambient = (float3) 0.03 * albedo.rgb * ao; 110 | float3 color = ambient + (1.0 - shadow) * result; 111 | //float3 color = (1.0 - shadow); 112 | 113 | color = color / (color + (float3) 1.0); 114 | color = pow(color, (float3) (1.0 / 2.2)); 115 | 116 | if ((options & 0x4) == 0x4) 117 | output.color = float4(color, albedo.a); 118 | else 119 | output.color = float4(color, 1.0); 120 | 121 | return output; 122 | } -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Forward/Standard_frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Graphics/Shaders/Forward/Standard_frag.spv -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Forward/Standard_vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Graphics/Shaders/Forward/Standard_vert.spv -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Lighting.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef LIGHTING_HLSL 2 | #define LIGHTING_HLSL 3 | 4 | #include "Types.hlsl" 5 | #include "Utils/Math.hlsl" 6 | 7 | // TODO: Replace this with proper normal mapping (still) 8 | float3 TempNormal(Texture2D normalTex, SamplerState state, float3 fragPos, float2 texCoords, float3 normal) 9 | { 10 | float3 tangentNormal = normalTex.Sample(state, texCoords).rgb * 2.0 - 1.0; 11 | 12 | float3 Q1 = ddx(fragPos); 13 | float3 Q2 = ddy(fragPos); 14 | float2 st1 = ddx(texCoords); 15 | float2 st2 = ddy(texCoords); 16 | 17 | float3 N = normalize(normal); 18 | float3 T = normalize(Q1 * st2.y - Q2 * st1.y); 19 | float3 B = -normalize(cross(N, T)); 20 | float3x3 tbn = float3x3(T, B, N); 21 | 22 | return normalize(mul(tangentNormal, tbn)); 23 | } 24 | 25 | float3 FresnelSchlick(float cosTheta, float3 F0) 26 | { 27 | return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); 28 | } 29 | 30 | float DistributionGGX(float3 N, float3 H, float roughness) 31 | { 32 | float a = roughness * roughness; 33 | float a2 = a * a; 34 | float NdotH = max(dot(N, H), 0.0); 35 | float NdotH2 = NdotH * NdotH; 36 | 37 | float num = a2; 38 | float denom = (NdotH2 * (a2 - 1.0) + 1.0); 39 | denom = PI * denom * denom; 40 | 41 | return num / denom; 42 | } 43 | 44 | float GeometrySchlickGGX(float NdotV, float roughness) 45 | { 46 | float r = roughness + 1.0; 47 | float k = (r * r) / 8.0; 48 | 49 | float num = NdotV; 50 | float denom = NdotV * (1.0 - k) + k; 51 | 52 | return num / denom; 53 | } 54 | 55 | float GeometrySmith(float3 N, float3 V, float3 L, float roughness) 56 | { 57 | float NdotV = max(dot(N, V), 0.0); 58 | float NDotL = max(dot(N, L), 0.0); 59 | float ggx2 = GeometrySchlickGGX(NdotV, roughness); 60 | float ggx1 = GeometrySchlickGGX(NDotL, roughness); 61 | 62 | return ggx1 * ggx2; 63 | } 64 | 65 | float3 ProcessLight(float3 albedo, float3 normal, float metallic, float roughness, float3 L, float3 N, float3 V, float3 radiance) 66 | { 67 | float3 H = normalize(V + L); 68 | 69 | // 0.04 looks correct for dialectic surfaces. 70 | float3 F0 = (float3) 0.04; 71 | F0 = lerp(F0, albedo, metallic); 72 | float3 F = FresnelSchlick(max(dot(H, V), 0.0), F0); 73 | 74 | float NDF = DistributionGGX(N, H, roughness); 75 | float G = GeometrySmith(N, V, L, roughness); 76 | 77 | float3 numerator = NDF * G * F; 78 | float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; 79 | float3 specular = numerator / denominator; 80 | 81 | float3 kS = F; 82 | float3 kD = (float3) 1.0 - kS; 83 | kD *= 1.0 - metallic; 84 | 85 | float NdotL = max(dot(N, L), 0.0); 86 | return (kD * albedo.rgb / PI + specular) * radiance * NdotL; 87 | } 88 | 89 | float3 ProcessDirLight(DirectionalLight light, float3 viewDir, float3 albedo, float3 normal, float metallic, float roughness) 90 | { 91 | float3 N = normalize(normal); 92 | 93 | float3 L = normalize(-light.direction.xyz); 94 | float3 radiance = light.color.rgb; 95 | 96 | return ProcessLight(albedo, normal, metallic, roughness, L, N, viewDir, radiance); 97 | } 98 | 99 | float CalculateShadow(float4 lightSpace, Texture2D shadowTex, SamplerState state, float3 normal, float3 lightDir) 100 | { 101 | float3 projCoords = lightSpace.xyz / lightSpace.w; 102 | 103 | projCoords = projCoords * 0.5 + 0.5; 104 | 105 | float closestDepth = shadowTex.Sample(state, projCoords.xy).r; 106 | float currentDepth = projCoords.z; 107 | 108 | float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); 109 | float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0; 110 | 111 | return shadow; 112 | } 113 | 114 | #endif -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/PostProcess/PostProcess.hlsl: -------------------------------------------------------------------------------- 1 | #define FXAA_PC 1 2 | #define FXAA_HLSL_5 1 3 | #define FXAA_GREEN_AS_LUMA 1 4 | #define FXAA_QUALITY__PRESET 26 5 | 6 | #include "FXAA3_11.h" 7 | 8 | struct VSInput 9 | { 10 | float2 position: POSITION; 11 | float2 texCoords: TEXCOORD0; 12 | float4 tint: COLOR0; 13 | float1 rotation: TEXCOORD1; 14 | float2 origin: TEXCOORD2; 15 | float2 scale: TEXCOORD3; 16 | float4 meta1: TEXCOORD4; 17 | float4 meta2: TEXCOORD5; 18 | }; 19 | 20 | struct VSOutput 21 | { 22 | float4 position: SV_Position; 23 | noperspective float2 texCoords: TEXCOORD0; 24 | float4 tint: COLOR0; 25 | float4 meta1: TEXCOORD1; 26 | float4 meta2: TEXCOORD2; 27 | }; 28 | 29 | struct PSOutput 30 | { 31 | float4 color: SV_Target0; 32 | }; 33 | 34 | cbuffer ProjView : register(b0) 35 | { 36 | float4x4 projView; 37 | }; 38 | 39 | Texture2D sprite : register(t1); 40 | SamplerState state : register(s1); 41 | 42 | VSOutput VertexShader(in VSInput input) 43 | { 44 | VSOutput output; 45 | 46 | float cosRot = cos(input.rotation); 47 | float sinRot = sin(input.rotation); 48 | 49 | float2 vertexPos = input.position - (input.origin * input.scale); 50 | float2x2 rot = float2x2( 51 | cosRot, sinRot, 52 | -sinRot, cosRot 53 | ); 54 | 55 | vertexPos = mul(rot, vertexPos); 56 | vertexPos += input.origin * input.scale; 57 | 58 | output.position = mul(projView, float4(vertexPos, 0.0, 1.0)); 59 | output.texCoords = input.texCoords; 60 | output.tint = input.tint; 61 | output.meta1 = input.meta1; 62 | output.meta2 = input.meta2; 63 | 64 | return output; 65 | } 66 | 67 | PSOutput PixelShader(in VSOutput input) 68 | { 69 | PSOutput output; 70 | 71 | uint width, height; 72 | sprite.GetDimensions(width, height); 73 | 74 | float2 frame = float2(1.0 / width, 1.0 / height); 75 | 76 | FxaaTex tex; 77 | tex.tex = sprite; 78 | tex.smpl = state; 79 | 80 | float4 color = FxaaPixelShader( 81 | input.texCoords, 82 | 0.0, 83 | tex, 84 | tex, 85 | tex, 86 | frame, 87 | 0.0, 88 | 0.0, 89 | 0.0, 90 | 0.75, 91 | 0.166, 92 | 0.0833, 93 | 0.0, 94 | 0.0, 95 | 0.0, 96 | 0.0 97 | ); 98 | 99 | output.color = color; 100 | 101 | return output; 102 | } -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/PostProcess/PostProcess_frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Graphics/Shaders/PostProcess/PostProcess_frag.spv -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/PostProcess/PostProcess_vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Graphics/Shaders/PostProcess/PostProcess_vert.spv -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Shadow.hlsl: -------------------------------------------------------------------------------- 1 | struct VSInput 2 | { 3 | float3 position: POSITION; 4 | }; 5 | 6 | struct VSOutput 7 | { 8 | float4 position: SV_Position; 9 | }; 10 | 11 | cbuffer ProjViewModel : register(b0) 12 | { 13 | float4x4 projection; 14 | float4x4 view; 15 | float4x4 model; 16 | float4x4 lightSpace; 17 | } 18 | 19 | VSOutput VertexShader(in VSInput input) 20 | { 21 | VSOutput output; 22 | output.position = mul(mul(mul(projection, view), model), float4(input.position, 1.0)); 23 | 24 | return output; 25 | } 26 | 27 | void PixelShader(in VSOutput output) {} -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Shadow_frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Graphics/Shaders/Shadow_frag.spv -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Shadow_vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Graphics/Shaders/Shadow_vert.spv -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Skybox.hlsl: -------------------------------------------------------------------------------- 1 | struct VSInput 2 | { 3 | float3 position : POSITION; 4 | }; 5 | 6 | struct VSOutput 7 | { 8 | float4 position: SV_Position; 9 | float3 texCoords: TEXCOORD0; 10 | }; 11 | 12 | struct PSOutput 13 | { 14 | float4 color: SV_Target0; 15 | }; 16 | 17 | cbuffer CameraInfo : register(b0) 18 | { 19 | float4x4 projection; 20 | float4x4 view; 21 | }; 22 | 23 | TextureCube skybox : register(t1); 24 | SamplerState state : register(s1); 25 | 26 | VSOutput VertexShader(in VSInput input) 27 | { 28 | VSOutput output; 29 | 30 | output.position = mul(mul(projection, view), float4(input.position, 1.0)).xyww; 31 | output.texCoords = input.position; 32 | 33 | return output; 34 | } 35 | 36 | PSOutput PixelShader(in VSOutput input) 37 | { 38 | PSOutput output; 39 | 40 | output.color = skybox.Sample(state, input.texCoords); 41 | 42 | return output; 43 | } -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Skybox_frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Graphics/Shaders/Skybox_frag.spv -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Skybox_vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Graphics/Shaders/Skybox_vert.spv -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/SpriteRenderer/Shapes.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef SHAPES_HLSL 2 | #define SHAPES_HLSL 3 | 4 | struct SDFResult 5 | { 6 | float dist; 7 | float blendAmount; 8 | }; 9 | 10 | float RectSDF(float2 p, float2 b, float r) 11 | { 12 | float2 d = abs(p) - b + (float2) r; 13 | return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r; 14 | } 15 | 16 | SDFResult RoundedRect(float2 size, float2 position, float2 texCoords) 17 | { 18 | float2 pos = size * texCoords; 19 | float fDist = RectSDF(pos - size / 2.0, size / 2.0 - position.x / 2.0 - 1.0, position.y); 20 | float fBlendAmount = smoothstep(-1.0, 1.0, abs(fDist) - position.x / 2.0); 21 | 22 | SDFResult result; 23 | result.dist = fDist; 24 | result.blendAmount = fBlendAmount; 25 | 26 | return result; 27 | } 28 | 29 | #endif -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/SpriteRenderer/Sprite.hlsl: -------------------------------------------------------------------------------- 1 | #include "Shapes.hlsl" 2 | 3 | struct VSInput 4 | { 5 | float2 position: POSITION; 6 | float2 texCoords: TEXCOORD0; 7 | float4 tint: COLOR0; 8 | float1 rotation: TEXCOORD1; 9 | float2 origin: TEXCOORD2; 10 | float2 scale: TEXCOORD3; 11 | float4 meta1: TEXCOORD4; 12 | float4 meta2: TEXCOORD5; 13 | }; 14 | 15 | struct VSOutput 16 | { 17 | float4 position: SV_Position; 18 | float2 texCoords: TEXCOORD0; 19 | float4 tint: COLOR0; 20 | float4 meta1: TEXCOORD1; 21 | float4 meta2: TEXCOORD2; 22 | }; 23 | 24 | struct PSOutput 25 | { 26 | float4 color: SV_Target0; 27 | }; 28 | 29 | cbuffer ProjView : register(b0) 30 | { 31 | float4x4 projView; 32 | }; 33 | 34 | Texture2D sprite : register(t1); 35 | SamplerState state : register(s1); 36 | 37 | // Constant options 38 | // 0 = Standard. Draw a sprite as usual. 39 | // 1 = Blur. Blur the sprite. 40 | // 2 = SDF Rounded Rectangle 41 | [[vk::constant_id(0)]] const uint options = 0; 42 | 43 | VSOutput VertexShader(in VSInput input) 44 | { 45 | VSOutput output; 46 | 47 | float cosRot = cos(input.rotation); 48 | float sinRot = sin(input.rotation); 49 | 50 | float2 vertexPos = input.position - (input.origin * input.scale); 51 | float2x2 rot = float2x2( 52 | cosRot, sinRot, 53 | -sinRot, cosRot 54 | ); 55 | 56 | vertexPos = mul(rot, vertexPos); 57 | vertexPos += input.origin * input.scale; 58 | 59 | output.position = mul(projView, float4(vertexPos, 0.0, 1.0)); 60 | output.texCoords = input.texCoords; 61 | output.tint = input.tint; 62 | output.meta1 = input.meta1; 63 | output.meta2 = input.meta2; 64 | 65 | return output; 66 | } 67 | 68 | PSOutput PixelShader(in VSOutput input) 69 | { 70 | PSOutput output; 71 | 72 | if (options == 1) 73 | { 74 | float4 color = 0.0; 75 | float2 direction = input.meta1.xy; 76 | float2 resolution = input.meta1.zw; 77 | float2 off1 = 1.3846153846 * direction; 78 | float2 off2 = 3.2307692308 * direction; 79 | 80 | color += sprite.Sample(state, input.texCoords) * 0.2270270270; 81 | color += sprite.Sample(state, input.texCoords + (off1 / resolution)) * 0.3162162162; 82 | color += sprite.Sample(state, input.texCoords - (off1 / resolution)) * 0.3162162162; 83 | color += sprite.Sample(state, input.texCoords + (off2 / resolution)) * 0.0702702703; 84 | color += sprite.Sample(state, input.texCoords - (off2 / resolution)) * 0.0702702703; 85 | 86 | output.color = color; 87 | } 88 | else if (options == 2) 89 | { 90 | SDFResult result = RoundedRect(input.meta1.zw, input.meta1.xy, input.texCoords); 91 | float4 toColor = result.dist < 0.0 ? sprite.Sample(state, input.texCoords) * input.tint : (float4) 0; 92 | output.color = lerp(input.meta2, toColor, result.blendAmount); 93 | } 94 | else 95 | output.color = sprite.Sample(state, input.texCoords) * input.tint; 96 | 97 | return output; 98 | } -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/SpriteRenderer/Sprite_frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Graphics/Shaders/SpriteRenderer/Sprite_frag.spv -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/SpriteRenderer/Sprite_vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Graphics/Shaders/SpriteRenderer/Sprite_vert.spv -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Types.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef TYPES_HLSL 2 | #define TYPES_HLSL 3 | 4 | struct Material 5 | { 6 | float4 tiling; 7 | 8 | float4 albedo; 9 | float1 metallic; 10 | float1 roughness; 11 | float1 ao; 12 | }; 13 | 14 | struct DirectionalLight 15 | { 16 | float4 direction; 17 | float4 color; 18 | }; 19 | 20 | #endif -------------------------------------------------------------------------------- /Easel.Graphics/Shaders/Utils/Math.hlsl: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define PI 3.141592653589793 4 | -------------------------------------------------------------------------------- /Easel.Graphics/Skybox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using System.Reflection; 4 | using System.Runtime.InteropServices; 5 | using Easel.Graphics.Primitives; 6 | using Easel.Core; 7 | using Pie; 8 | using Pie.ShaderCompiler; 9 | using Pie.Utils; 10 | 11 | namespace Easel.Graphics; 12 | 13 | public class Skybox : IDisposable 14 | { 15 | public SamplerState SamplerState; 16 | 17 | public readonly Pie.Texture PieTexture; 18 | private GraphicsBuffer _vertexBuffer; 19 | private GraphicsBuffer _indexBuffer; 20 | private GraphicsBuffer _cameraBuffer; 21 | 22 | private DepthStencilState _depthState; 23 | private Pie.RasterizerState _rasterizerState; 24 | 25 | private Effect _effect; 26 | private InputLayout _inputLayout; 27 | 28 | private CameraInfo _cameraInfo; 29 | 30 | private GraphicsDevice _device; 31 | 32 | public Skybox(Bitmap right, Bitmap left, Bitmap top, Bitmap bottom, Bitmap front, Bitmap back, SamplerState samplerState = null) 33 | { 34 | _device = EaselGraphics.Instance.PieGraphics; 35 | 36 | byte[] data = PieUtils.Combine(right.Data, left.Data, top.Data, bottom.Data, front.Data, back.Data); 37 | 38 | PieTexture = 39 | _device.CreateTexture( 40 | new TextureDescription(TextureType.Cubemap, right.Size.Width, right.Size.Height, 0, right.Format, 1, 1, 41 | TextureUsage.ShaderResource), data); 42 | 43 | Cube cube = new Cube(); 44 | VertexPositionTextureNormalTangent[] vptnts = cube.Vertices; 45 | VertexPosition[] vps = new VertexPosition[vptnts.Length]; 46 | for (int i = 0; i < vptnts.Length; i++) 47 | { 48 | vps[i] = new VertexPosition((Vector3) vptnts[i].Position); 49 | } 50 | 51 | _vertexBuffer = _device.CreateBuffer(BufferType.VertexBuffer, vps); 52 | _indexBuffer = _device.CreateBuffer(BufferType.IndexBuffer, cube.Indices); 53 | 54 | _cameraBuffer = _device.CreateBuffer(BufferType.UniformBuffer, _cameraInfo, true); 55 | 56 | _depthState = _device.CreateDepthStencilState(DepthStencilStateDescription.LessEqual); 57 | _rasterizerState = _device.CreateRasterizerState(RasterizerStateDescription.CullCounterClockwise); 58 | 59 | _effect = Effect.FromPath("Easel.Graphics.Shaders.Skybox_vert.spv", "Easel.Graphics.Shaders.Skybox_frag.spv"); 60 | 61 | _inputLayout = 62 | _device.CreateInputLayout(new InputLayoutDescription(Format.R32G32B32_Float, 0, 0, InputType.PerVertex)); 63 | 64 | SamplerState = samplerState ?? SamplerState.LinearClamp; 65 | } 66 | 67 | internal void Draw(Matrix4x4 projection, Matrix4x4 view) 68 | { 69 | _cameraInfo.Projection = projection; 70 | _cameraInfo.View = view.To3x3Matrix(); 71 | 72 | _device.UpdateBuffer(_cameraBuffer, 0, _cameraInfo); 73 | 74 | _device.SetShader(_effect.PieShader); 75 | _device.SetPrimitiveType(PrimitiveType.TriangleList); 76 | _device.SetUniformBuffer(0, _cameraBuffer); 77 | _device.SetTexture(1, PieTexture, SamplerState.PieSamplerState); 78 | _device.SetDepthStencilState(_depthState); 79 | _device.SetRasterizerState(_rasterizerState); 80 | _device.SetVertexBuffer(0, _vertexBuffer, VertexPosition.SizeInBytes, _inputLayout); 81 | _device.SetIndexBuffer(_indexBuffer, IndexType.UInt); 82 | // 36 because cube 83 | _device.DrawIndexed(36); 84 | } 85 | 86 | [StructLayout(LayoutKind.Sequential)] 87 | private struct CameraInfo 88 | { 89 | public Matrix4x4 Projection; 90 | public Matrix4x4 View; 91 | } 92 | 93 | public void Dispose() 94 | { 95 | PieTexture.Dispose(); 96 | _vertexBuffer.Dispose(); 97 | _indexBuffer.Dispose(); 98 | _cameraBuffer.Dispose(); 99 | _depthState.Dispose(); 100 | _rasterizerState.Dispose(); 101 | _effect.Dispose(); 102 | _inputLayout.Dispose(); 103 | } 104 | } -------------------------------------------------------------------------------- /Easel.Graphics/Texture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Easel.Core; 3 | using Easel.Math; 4 | 5 | namespace Easel.Graphics; 6 | 7 | /// 8 | /// The base texture class, for objects that can be textured. 9 | /// 10 | public abstract class Texture : IDisposable 11 | { 12 | /// 13 | /// Returns if this has been disposed. 14 | /// 15 | public bool IsDisposed { get; protected set; } 16 | 17 | /// 18 | /// The native Pie . 19 | /// 20 | public Pie.Texture PieTexture { get; protected set; } 21 | 22 | public SamplerState SamplerState { get; set; } 23 | 24 | /// 25 | /// The size (resolution), in pixels of the texture. 26 | /// 27 | public Size Size => (Size) new Size(PieTexture.Description.Width, PieTexture.Description.Height); 28 | 29 | protected Texture(SamplerState state, bool autoDispose) 30 | { 31 | SamplerState = state; 32 | 33 | if (autoDispose) 34 | DisposeManager.AddItem(this); 35 | } 36 | 37 | public virtual void Dispose() 38 | { 39 | if (IsDisposed) 40 | return; 41 | IsDisposed = true; 42 | PieTexture.Dispose(); 43 | Logger.Debug("Texture disposed."); 44 | } 45 | } -------------------------------------------------------------------------------- /Easel.Graphics/Texture2D.cs: -------------------------------------------------------------------------------- 1 | using Pie; 2 | 3 | namespace Easel.Graphics; 4 | 5 | /// 6 | /// s are used to texture most 3D meshes, and 2D sprites. 7 | /// 8 | public class Texture2D : Texture 9 | { 10 | /// 11 | /// Create a new from the given path. 12 | /// 13 | /// The path to load from. 14 | /// If , this will be automatically disposed 15 | /// on scene change. 16 | public Texture2D(string path, SamplerState samplerState = null, bool autoDispose = true) 17 | : this(new Bitmap(path), samplerState, autoDispose) { } 18 | 19 | /// 20 | /// Create a new from the given . Useful for doing threaded loading. 21 | /// 22 | /// The to load from. 23 | /// If , this will be automatically disposed 24 | /// on scene change. 25 | public Texture2D(Bitmap bitmap, SamplerState samplerState = null, bool autoDispose = true) 26 | : this(bitmap.Size.Width, bitmap.Size.Height, bitmap.Data, samplerState, bitmap.Format, autoDispose) { } 27 | 28 | public Texture2D(int width, int height, byte[] data, SamplerState samplerState = null, 29 | Format format = Format.R8G8B8A8_UNorm, bool autoDispose = true) 30 | : base(samplerState ?? SamplerState.AnisotropicRepeat, autoDispose) 31 | { 32 | bool compressed = format >= Format.BC1_UNorm && format <= Format.BC7_UNorm_SRgb; 33 | 34 | // TODO: Better texture management for DDS. 35 | GraphicsDevice device = EaselGraphics.Instance.PieGraphics; 36 | TextureDescription description = TextureDescription.Texture2D(width, height, format, compressed ? 1 : 0, 1, 37 | TextureUsage.ShaderResource); 38 | PieTexture = device.CreateTexture(description, data); 39 | if (!compressed) 40 | device.GenerateMipmaps(PieTexture); 41 | } 42 | 43 | public void SetData(int x, int y, int width, int height, T[] data) where T : unmanaged 44 | { 45 | GraphicsDevice device = EaselGraphics.Instance.PieGraphics; 46 | device.UpdateTexture(PieTexture, 0, 0, x, y, 0, width, height, 0, data); 47 | } 48 | 49 | public static readonly Texture2D White = new Texture2D(1, 1, new byte[] { 255, 255, 255, 255 }, autoDispose: false); 50 | 51 | public static readonly Texture2D Black = new Texture2D(1, 1, new byte[] { 0, 0, 0, 255 }, autoDispose: false); 52 | 53 | public static readonly Texture2D EmptyNormal = 54 | new Texture2D(1, 1, new byte[] { 128, 128, 255, 255 }, autoDispose: false); 55 | 56 | public static readonly Texture2D Missing = new Texture2D(128, 128, Bitmap.GetMissingBitmap(128, 128), autoDispose: false); 57 | } -------------------------------------------------------------------------------- /Easel.Graphics/VertexPositionTextureNormalTangent.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Easel.Math; 3 | 4 | namespace Easel.Graphics; 5 | 6 | public struct VertexPositionTextureNormalTangent 7 | { 8 | public Vector3 Position; 9 | public Vector2 TexCoords; 10 | public Vector3 Normals; 11 | public Vector3 Tangents; 12 | 13 | public VertexPositionTextureNormalTangent(Vector3 position, Vector2 texCoords, Vector3 normals, Vector3 tangents) 14 | { 15 | Position = position; 16 | TexCoords = texCoords; 17 | Normals = normals; 18 | Tangents = tangents; 19 | } 20 | 21 | public const int SizeInBytes = 44; 22 | } -------------------------------------------------------------------------------- /Easel.ImGui/Easel.ImGui.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | disable 7 | true 8 | Easel.Imgui 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Easel.ImGui/FilePicker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using ImGuiNET; 6 | 7 | namespace Easel.Imgui; 8 | 9 | public class FilePicker 10 | { 11 | private string _currentPath; 12 | 13 | private int _selectedFile; 14 | private FileSystemEntry[] _files; 15 | 16 | public readonly FilePickerType Type; 17 | public bool ShowHidden; 18 | 19 | public FilePicker(FilePickerType type, string initialPath = null) 20 | { 21 | Type = type; 22 | 23 | _currentPath = initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.Personal); 24 | 25 | _files = Array.Empty(); 26 | FetchFiles(); 27 | 28 | ShowHidden = false; 29 | } 30 | 31 | public void Update() 32 | { 33 | if (ImGui.Begin("File Picker")) 34 | { 35 | if (ImGui.InputText("Path", ref _currentPath, 2000, ImGuiInputTextFlags.EnterReturnsTrue)) 36 | FetchFiles(); 37 | 38 | if (ImGui.BeginListBox("Stuff")) 39 | { 40 | int i = 0; 41 | foreach (FileSystemEntry file in _files) 42 | { 43 | if (ImGui.Selectable(file.Name, _selectedFile == i)) 44 | { 45 | if (file.IsDirectory) 46 | { 47 | _currentPath = file.FullPath; 48 | FetchFiles(); 49 | } 50 | else 51 | _selectedFile = i; 52 | } 53 | 54 | i++; 55 | } 56 | } 57 | 58 | ImGui.End(); 59 | } 60 | } 61 | 62 | private bool FetchFiles() 63 | { 64 | if (!Directory.Exists(_currentPath)) 65 | return false; 66 | 67 | List fileEntries = new List(); 68 | 69 | DirectoryInfo di = new DirectoryInfo(_currentPath); 70 | DirectoryInfo[] directories = di.GetDirectories(); 71 | Array.Sort(directories, (info, directoryInfo) => string.Compare(info.Name, directoryInfo.Name)); 72 | foreach (DirectoryInfo info in directories) 73 | { 74 | if ((info.Attributes & FileAttributes.Hidden) != 0 && !ShowHidden) 75 | continue; 76 | fileEntries.Add(new FileSystemEntry(info.FullName, true)); 77 | } 78 | 79 | FileInfo[] files = di.GetFiles(); 80 | Array.Sort(files, (info, fileInfo) => string.Compare(info.Name, fileInfo.Name)); 81 | foreach (FileInfo info in files) 82 | { 83 | if ((info.Attributes & FileAttributes.Hidden) != 0 && !ShowHidden) 84 | continue; 85 | fileEntries.Add(new FileSystemEntry(info.FullName, true)); 86 | } 87 | 88 | _files = fileEntries.ToArray(); 89 | 90 | return true; 91 | } 92 | 93 | private struct FileSystemEntry 94 | { 95 | public readonly string Name; 96 | 97 | public readonly string FullPath; 98 | 99 | public readonly bool IsDirectory; 100 | 101 | public FileSystemEntry(string fullPath, bool isDirectory) 102 | { 103 | Name = Path.GetFileName(fullPath); 104 | FullPath = fullPath; 105 | IsDirectory = isDirectory; 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /Easel.ImGui/FilePickerType.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.Imgui; 2 | 3 | public enum FilePickerType 4 | { 5 | Open, 6 | Save 7 | } -------------------------------------------------------------------------------- /Easel.ImGui/FontInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.Imgui; 2 | 3 | public struct FontInfo 4 | { 5 | public string Path; 6 | public float Size; 7 | 8 | public FontInfo(string path, float size) 9 | { 10 | Path = path; 11 | Size = size; 12 | } 13 | } -------------------------------------------------------------------------------- /Easel.Math/Easel.Math.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | disable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Easel.Math/MatrixT.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace Easel.Math; 4 | 5 | public struct MatrixT where T : INumber 6 | { 7 | public static MatrixT Identity => 8 | new MatrixT(Vector4T.UnitX, Vector4T.UnitY, Vector4T.UnitZ, Vector4T.UnitW); 9 | 10 | public Vector4T Row0; 11 | public Vector4T Row1; 12 | public Vector4T Row2; 13 | public Vector4T Row3; 14 | 15 | public Vector4T Column0 16 | { 17 | get => new Vector4T(Row0.X, Row1.X, Row2.X, Row3.X); 18 | set 19 | { 20 | Row0.X = value.X; 21 | Row1.X = value.Y; 22 | Row2.X = value.Z; 23 | Row3.X = value.W; 24 | } 25 | } 26 | 27 | public Vector4T Column1 28 | { 29 | get => new Vector4T(Row0.Y, Row1.Y, Row2.Y, Row3.Y); 30 | set 31 | { 32 | Row0.Y = value.X; 33 | Row1.Y = value.Y; 34 | Row2.Y = value.Z; 35 | Row3.Y = value.W; 36 | } 37 | } 38 | 39 | public Vector4T Column2 40 | { 41 | get => new Vector4T(Row0.Z, Row1.Z, Row2.Z, Row3.Z); 42 | set 43 | { 44 | Row0.Z = value.X; 45 | Row1.Z = value.Y; 46 | Row2.Z = value.Z; 47 | Row3.Z = value.W; 48 | } 49 | } 50 | 51 | public Vector4T Column3 52 | { 53 | get => new Vector4T(Row0.W, Row1.W, Row2.W, Row3.W); 54 | set 55 | { 56 | Row0.W = value.X; 57 | Row1.W = value.Y; 58 | Row2.W = value.Z; 59 | Row3.W = value.W; 60 | } 61 | } 62 | 63 | public MatrixT(Vector4T row0, Vector4T row1, Vector4T row2, Vector4T row3) 64 | { 65 | Row0 = row0; 66 | Row1 = row1; 67 | Row2 = row2; 68 | Row3 = row3; 69 | } 70 | 71 | /*public static MatrixT operator *(MatrixT left, MatrixT right) 72 | { 73 | 74 | }*/ 75 | } 76 | 77 | public static class MatrixT 78 | { 79 | 80 | } -------------------------------------------------------------------------------- /Easel.Math/QuaternionT.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace Easel.Math; 4 | 5 | public struct QuaternionT where T : INumber 6 | { 7 | 8 | } -------------------------------------------------------------------------------- /Easel.Math/Rectangle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using System.Xml.Serialization; 4 | 5 | namespace Easel.Math; 6 | 7 | public struct Rectangle : IEquatable> where T : INumber 8 | { 9 | [XmlAttribute] 10 | public T X; 11 | 12 | [XmlAttribute] 13 | public T Y; 14 | 15 | [XmlAttribute] 16 | public T Width; 17 | 18 | [XmlAttribute] 19 | public T Height; 20 | 21 | public Vector2T Location => new Vector2T(X, Y); 22 | 23 | public Size Size => new Size(Width, Height); 24 | 25 | public T Left => X; 26 | 27 | public T Top => Y; 28 | 29 | public T Right => X + Width; 30 | 31 | public T Bottom => Y + Height; 32 | 33 | public Rectangle(T x, T y, T width, T height) 34 | { 35 | X = x; 36 | Y = y; 37 | Width = width; 38 | Height = height; 39 | } 40 | 41 | public Rectangle(Vector2T location, Size size) : this(location.X, location.Y, size.Width, size.Height) { } 42 | 43 | public bool Equals(Rectangle other) 44 | { 45 | return X == other.X && Y == other.Y && Width == other.Width && Height == other.Height; 46 | } 47 | 48 | public override bool Equals(object obj) 49 | { 50 | return obj is Rectangle other && Equals(other); 51 | } 52 | 53 | public override int GetHashCode() 54 | { 55 | return HashCode.Combine(X, Y, Width, Height); 56 | } 57 | 58 | public static bool operator ==(Rectangle left, Rectangle right) 59 | { 60 | return left.Equals(right); 61 | } 62 | 63 | public static bool operator !=(Rectangle left, Rectangle right) 64 | { 65 | return !left.Equals(right); 66 | } 67 | 68 | public static explicit operator System.Drawing.Rectangle(Rectangle rectangle) 69 | { 70 | int x = Convert.ToInt32(rectangle.X); 71 | int y = Convert.ToInt32(rectangle.Y); 72 | int w = Convert.ToInt32(rectangle.Width); 73 | int h = Convert.ToInt32(rectangle.Height); 74 | 75 | return new System.Drawing.Rectangle(x, y, w, h); 76 | } 77 | 78 | public static explicit operator Rectangle(System.Drawing.Rectangle rectangle) => 79 | new Rectangle(T.CreateChecked(rectangle.X), T.CreateChecked(rectangle.Y), T.CreateChecked(rectangle.Width), 80 | T.CreateChecked(rectangle.Height)); 81 | 82 | public override string ToString() 83 | { 84 | return "Rectangle(X: " + X + ", Y: " + Y + ", Width: " + Width + ", Height: " + Height + ")"; 85 | } 86 | } -------------------------------------------------------------------------------- /Easel.Math/Size.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using System.Runtime.CompilerServices; 4 | using System.Xml.Serialization; 5 | 6 | namespace Easel.Math; 7 | 8 | public struct Size : IEquatable> where T : INumber 9 | { 10 | public static readonly Size Zero = new Size(T.Zero); 11 | 12 | [XmlAttribute] 13 | public T Width; 14 | 15 | [XmlAttribute] 16 | public T Height; 17 | 18 | public Size(T width, T height) 19 | { 20 | Width = width; 21 | Height = height; 22 | } 23 | 24 | public Size(T wh) 25 | { 26 | Width = wh; 27 | Height = wh; 28 | } 29 | 30 | public bool Equals(Size other) 31 | { 32 | return Width == other.Width && Height == other.Height; 33 | } 34 | 35 | public override bool Equals(object obj) 36 | { 37 | return obj is Size other && Equals(other); 38 | } 39 | 40 | public override int GetHashCode() 41 | { 42 | return HashCode.Combine(Width, Height); 43 | } 44 | 45 | public static bool operator ==(Size left, Size right) 46 | { 47 | return left.Equals(right); 48 | } 49 | 50 | public static bool operator !=(Size left, Size right) 51 | { 52 | return !left.Equals(right); 53 | } 54 | 55 | public static Size operator +(Size left, Size right) 56 | { 57 | return new Size(left.Width + right.Width, left.Height + right.Height); 58 | } 59 | 60 | public static Size operator -(Size left, Size right) 61 | { 62 | return new Size(left.Width - right.Width, left.Height - right.Height); 63 | } 64 | 65 | public static Size operator *(Size left, Size right) 66 | { 67 | return new Size(left.Width * right.Width, left.Height * right.Height); 68 | } 69 | 70 | public static Size operator *(Size left, T right) 71 | { 72 | return new Size(left.Width * right, left.Height * right); 73 | } 74 | 75 | public static Size operator /(Size left, Size right) 76 | { 77 | return new Size(left.Width / right.Width, left.Height / right.Height); 78 | } 79 | 80 | public static Size operator /(Size left, T right) 81 | { 82 | return new Size(left.Width / right, left.Height / right); 83 | } 84 | 85 | public static explicit operator System.Drawing.Size(Size size) 86 | { 87 | int width = Convert.ToInt32(size.Width); 88 | int height = Convert.ToInt32(size.Height); 89 | 90 | return new System.Drawing.Size(width, height); 91 | } 92 | 93 | public static explicit operator Size(System.Drawing.Size size) => 94 | new Size(T.CreateChecked(size.Width), T.CreateChecked(size.Height)); 95 | 96 | [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] 97 | public Size As() where TOther : INumber 98 | { 99 | return new Size(TOther.CreateChecked(Width), TOther.CreateChecked(Height)); 100 | } 101 | 102 | public override string ToString() 103 | { 104 | return Width + "x" + Height; 105 | } 106 | } -------------------------------------------------------------------------------- /Easel.Math/swizgen.py: -------------------------------------------------------------------------------- 1 | def process(components, num_components, text, final_list): 2 | if num_components <= 0: 3 | return text; 4 | 5 | for comp in components: 6 | final_list.append(text + comp) 7 | process(components, num_components - 1, text + comp, final_list) 8 | 9 | if __name__ == "__main__": 10 | components = list(input("Enter components you would like to generate swizzles for. (For example, RGBA) ")) 11 | 12 | max_components = int(input("Enter the maximum number of components. ")) 13 | 14 | struct_name = input("Enter a struct name.\nAvailable variables:\n\t\"{elem}\" - Element name\n\t\"{length}\" - The length of the element\n\t\"{params}\" - The elements in parameter form\n") 15 | 16 | output = input("Enter an output file name (leave blank for TTY output) (WARNING THIS WILL OVERWRITE!!!). ") 17 | 18 | final_list = [] 19 | print("Processing... (this may take a while!)") 20 | process(components, max_components, "", final_list) 21 | 22 | print("Prettyfying... (this may also take a while!)") 23 | final_list = sorted(final_list, key=lambda x: len(x)) 24 | 25 | # I hate the duplicate code but it was the only way I could make it "performant" (lol python moment) 26 | if output == '': 27 | for elem in final_list: 28 | split_elem = list(elem) 29 | length = len(split_elem) 30 | joj = ", ".join(split_elem) # oops, forgot to change this. guess it's joj now 31 | text = struct_name.replace("{elem}", elem).replace("{length}", str(length)).replace("{params}", joj).replace("\\n", "\n") 32 | print(text) 33 | else: 34 | prompt = input(f"Writing to output file \"{output}\"?? [Y/n] ") 35 | if prompt.lower() == 'y': 36 | with open(output, "w") as f: 37 | print("Writing... Please wait.") 38 | for elem in final_list: 39 | split_elem = list(elem) 40 | length = len(split_elem) 41 | joj = ", ".join(split_elem) # oops, forgot to change this. guess it's joj now 42 | text = struct_name.replace("{elem}", elem).replace("{length}", str(length)).replace("{params}", joj).replace("\\n", "\n") 43 | f.write(text + "\n") 44 | print("All done.") 45 | else: 46 | print("Ok.") -------------------------------------------------------------------------------- /Easel.Tests/Content/Abel-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/Abel-Regular.ttf -------------------------------------------------------------------------------- /Easel.Tests/Content/Audio/help.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/Audio/help.mp3 -------------------------------------------------------------------------------- /Easel.Tests/Content/Audio/help.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/Audio/help.ogg -------------------------------------------------------------------------------- /Easel.Tests/Content/Audio/help.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/Audio/help.wav -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/24bitcolor-BGRA8.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/24bitcolor-BGRA8.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/24bitcolor-RGBA8.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/24bitcolor-RGBA8.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/Compressed/24bitcolor-BC1.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/Compressed/24bitcolor-BC1.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/Compressed/24bitcolor-BC1_SRGB.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/Compressed/24bitcolor-BC1_SRGB.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/Compressed/24bitcolor-BC2.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/Compressed/24bitcolor-BC2.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/Compressed/24bitcolor-BC2_SRGB.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/Compressed/24bitcolor-BC2_SRGB.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/Compressed/24bitcolor-BC3.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/Compressed/24bitcolor-BC3.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/Compressed/24bitcolor-BC3_SRGB.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/Compressed/24bitcolor-BC3_SRGB.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/Compressed/24bitcolor-BC4.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/Compressed/24bitcolor-BC4.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/Compressed/24bitcolor-BC5.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/Compressed/24bitcolor-BC5.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/Compressed/24bitcolor-BC6H_SF16.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/Compressed/24bitcolor-BC6H_SF16.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/Compressed/24bitcolor-BC6H_UF16.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/Compressed/24bitcolor-BC6H_UF16.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/Compressed/24bitcolor-BC7.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/Compressed/24bitcolor-BC7.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/DDS/awesomeface.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/DDS/awesomeface.dds -------------------------------------------------------------------------------- /Easel.Tests/Content/Fox.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/Fox.bin -------------------------------------------------------------------------------- /Easel.Tests/Content/Texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/Texture.png -------------------------------------------------------------------------------- /Easel.Tests/Content/awesomeface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/awesomeface.png -------------------------------------------------------------------------------- /Easel.Tests/Content/back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/back.jpg -------------------------------------------------------------------------------- /Easel.Tests/Content/bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/bottom.jpg -------------------------------------------------------------------------------- /Easel.Tests/Content/fox.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/fox.glb -------------------------------------------------------------------------------- /Easel.Tests/Content/front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/front.jpg -------------------------------------------------------------------------------- /Easel.Tests/Content/left.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/left.jpg -------------------------------------------------------------------------------- /Easel.Tests/Content/right.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/right.jpg -------------------------------------------------------------------------------- /Easel.Tests/Content/top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Content/top.jpg -------------------------------------------------------------------------------- /Easel.Tests/Easel.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | disable 7 | disable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Easel.Tests/Easel.Tests.dxvk-cache: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel.Tests/Easel.Tests.dxvk-cache -------------------------------------------------------------------------------- /Easel.Tests/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Numerics; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.InteropServices; 6 | using System.Runtime.Intrinsics; 7 | using System.Runtime.Intrinsics.X86; 8 | using Easel; 9 | using Easel.Core; 10 | using Easel.Tests; 11 | using Easel.Tests.TestScenes; 12 | using Pie; 13 | using Pie.Windowing; 14 | 15 | 16 | GameSettings settings = new GameSettings() 17 | { 18 | Resizable = true, 19 | TitleBarFlags = TitleBarFlags.ShowFps | TitleBarFlags.ShowGraphicsApi, 20 | VSync = true, 21 | TargetFps = 200, 22 | Api = GraphicsApi.OpenGL 23 | //AutoGenerateContentDirectory = null 24 | }; 25 | 26 | Logger.UseConsoleLogs(); 27 | 28 | using TestGame game = new TestGame(settings, new TestShadow()); 29 | game.Run(); 30 | 31 | /*QuaternionT quat = QuaternionT.FromEuler(1f, 0.5f, 0.25f); 32 | Console.WriteLine(quat); 33 | 34 | Quaternion quat2 = Quaternion.CreateFromYawPitchRoll(1f, 0.5f, 0.25f); 35 | Console.WriteLine(quat2); 36 | 37 | Console.WriteLine(MatrixT.FromQuaternion(quat)); 38 | 39 | Console.WriteLine(Matrix4x4.CreateFromQuaternion(quat2));*/ -------------------------------------------------------------------------------- /Easel.Tests/TestGame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Easel.Graphics; 3 | using Easel.Imgui; 4 | using Easel.Scenes; 5 | using Pie.ShaderCompiler; 6 | using Pie.Windowing; 7 | 8 | namespace Easel.Tests; 9 | 10 | public class TestGame : EaselGame 11 | { 12 | public ImGuiRenderer ImGuiRenderer; 13 | 14 | public TestGame(GameSettings settings, Scene scene) : base(settings, scene) { } 15 | 16 | protected override void Initialize() 17 | { 18 | ImGuiRenderer = new ImGuiRenderer(); 19 | 20 | base.Initialize(); 21 | } 22 | 23 | protected override void Update() 24 | { 25 | ImGuiRenderer?.Update(); 26 | 27 | if (Input.KeyPressed(Key.F11)) 28 | { 29 | if (Window.FullscreenMode != FullscreenMode.Windowed) 30 | Window.FullscreenMode = FullscreenMode.Windowed; 31 | else 32 | Window.FullscreenMode = FullscreenMode.BorderlessFullscreen; 33 | } 34 | 35 | base.Update(); 36 | } 37 | 38 | protected override void Draw() 39 | { 40 | base.Draw(); 41 | 42 | ImGuiRenderer?.Draw(); 43 | } 44 | } -------------------------------------------------------------------------------- /Easel.Tests/TestScenes/TestAudio.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Easel.Audio; 4 | using Easel.Scenes; 5 | using Pie.Audio; 6 | 7 | namespace Easel.Tests.TestScenes; 8 | 9 | public class TestAudio : Scene 10 | { 11 | protected override unsafe void Initialize() 12 | { 13 | base.Initialize(); 14 | 15 | AudioDevice device = Audio.PieAudio; 16 | 17 | PCM pcm = PCM.LoadWav("/home/ollie/Music/strange airy.wav"); 18 | 19 | AudioBuffer buffer = device.CreateBuffer(new BufferDescription(DataType.Pcm, pcm.Format), pcm.Data); 20 | 21 | const int numIterations = 256; 22 | 23 | ChannelProperties properties = new ChannelProperties() 24 | { 25 | Volume = 1.0 / numIterations 26 | }; 27 | 28 | for (ushort i = 0; i < numIterations; i++) 29 | device.PlayBuffer(buffer, i, properties); 30 | } 31 | } -------------------------------------------------------------------------------- /Easel.Tests/TestScenes/TestCanvas.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Easel.Entities; 3 | using Easel.Entities.Components; 4 | using Easel.Graphics; 5 | using Easel.Math; 6 | using Easel.Scenes; 7 | 8 | namespace Easel.Tests.TestScenes; 9 | 10 | public class TestCanvas : Scene 11 | { 12 | private Texture2D _texture; 13 | private Canvas _canvas; 14 | 15 | private Bitmap _bitmap1; 16 | private Bitmap _bitmap2; 17 | 18 | protected override void Initialize() 19 | { 20 | base.Initialize(); 21 | 22 | _bitmap1 = new Bitmap("/home/ollie/Pictures/ball.png"); 23 | _bitmap2 = new Bitmap("/home/ollie/Pictures/awesomeface.png"); 24 | 25 | Camera.Main.UseOrtho2D(); 26 | 27 | _canvas = new Canvas(new Size(800, 600)); 28 | _canvas.Clear(Color.CornflowerBlue); 29 | 30 | _texture = new Texture2D(_canvas.ToBitmap()); 31 | 32 | Entity drawEntity = new Entity(null); 33 | drawEntity.AddComponent(new Sprite(_texture)); 34 | AddEntity(drawEntity); 35 | } 36 | 37 | protected override void Draw() 38 | { 39 | _canvas.Clear(Color.CornflowerBlue); 40 | 41 | //_canvas.DrawBitmap(0, 0, (int) EaselMath.Lerp(100, 800, (MathF.Sin(Time.TotalSeconds) + 1) / 2f), 600, _bitmap2); 42 | _canvas.DrawBitmap(0, 0, 800, 600, _bitmap2); 43 | 44 | _texture.SetData(0, 0, _canvas.Size.Width, _canvas.Size.Height, _canvas.ToBitmap().Data); 45 | 46 | base.Draw(); 47 | } 48 | } -------------------------------------------------------------------------------- /Easel.Tests/TestScenes/TestFont.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Easel.Graphics; 3 | using Easel.Graphics.Renderers; 4 | using Easel.Math; 5 | using Easel.Scenes; 6 | using Pie.Text; 7 | 8 | namespace Easel.Tests.TestScenes; 9 | 10 | public class TestFont : Scene 11 | { 12 | private Texture2D _texture; 13 | 14 | protected override void Initialize() 15 | { 16 | base.Initialize(); 17 | 18 | using FreeType freeType = new FreeType(); 19 | using Face face = freeType.CreateFace("Content/Abel-Regular.ttf", 24); 20 | Character character = face.Characters['A']; 21 | //_texture = new Texture2D(character.Width, character.Height, character.); 22 | } 23 | 24 | protected override void Draw() 25 | { 26 | base.Draw(); 27 | 28 | Graphics.SpriteRenderer.Begin(); 29 | Graphics.SpriteRenderer.Draw(_texture, Vector2.Zero, null, Color.White, 0, Vector2.Zero, Vector2.One, SpriteFlip.None); 30 | Graphics.SpriteRenderer.End(); 31 | } 32 | } -------------------------------------------------------------------------------- /Easel.Tests/TestScenes/TestGame.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Easel.Entities; 3 | using Easel.Entities.Components; 4 | using Easel.Graphics; 5 | using Easel.Math; 6 | using Easel.Scenes; 7 | 8 | namespace Easel.Tests.TestScenes; 9 | 10 | public class TestGameScene : Scene 11 | { 12 | private Vector2 _velocity; 13 | 14 | protected override void Initialize() 15 | { 16 | base.Initialize(); 17 | 18 | Camera.Main.UseOrtho2D(); 19 | 20 | Entity entity = new Entity("thing", new Transform() 21 | { 22 | Position = new Vector3(100, 100, 0) 23 | }); 24 | entity.AddComponent(new Sprite(Content.Load("awesomeface"))); 25 | AddEntity(entity); 26 | } 27 | 28 | protected override void Update() 29 | { 30 | base.Update(); 31 | 32 | Entity entity = GetEntity("thing"); 33 | 34 | _velocity.X = 0; 35 | 36 | while (entity.Transform.Position.X >= 0) 37 | _velocity.X = -3; 38 | 39 | entity.Transform.Position.X += _velocity.X; 40 | } 41 | } -------------------------------------------------------------------------------- /Easel.Tests/TestScenes/TestManyEntities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Numerics; 4 | using System.Text; 5 | using Easel.Entities; 6 | using Easel.Entities.Components; 7 | using Easel.Graphics; 8 | using Easel.GUI; 9 | using Easel.Math; 10 | using Easel.Scenes; 11 | 12 | namespace Easel.Tests.TestScenes; 13 | 14 | public class TestManyEntities : Scene 15 | { 16 | private Texture2D[] _textures; 17 | 18 | protected override void Initialize() 19 | { 20 | base.Initialize(); 21 | 22 | Camera.Main.UseOrtho2D(); 23 | 24 | string[] files = Directory.GetFiles(@"C:\Users\ollie\Pictures", "*.png"); 25 | _textures = new Texture2D[files.Length]; 26 | 27 | int i = 0; 28 | foreach (string file in files) 29 | { 30 | Console.Write($"Loading {file}... "); 31 | _textures[i] = new Texture2D(file); 32 | Console.WriteLine("Done!"); 33 | i++; 34 | } 35 | 36 | Console.WriteLine(_textures.Length); 37 | 38 | /*Texture2D texture = Content.Load("awesomeface"); 39 | Random random = new Random(); 40 | 41 | for (int i = 0; i < 13457; i++) 42 | { 43 | Entity entity = new Entity(null, new Transform() 44 | { 45 | Position = new Vector3(random.Next(0, 1280), random.Next(0, 720), 0), 46 | SpriteRotation = random.NextSingle() * MathF.PI * 2, 47 | Scale = new Vector3(random.NextSingle() * 0.5f, random.NextSingle() * 0.5f, 1.0f) 48 | }); 49 | entity.AddComponent(new Sprite(texture)); 50 | AddEntity(entity); 51 | }*/ 52 | 53 | UI.DefaultStyle.Font = new Font(@"C:\Windows\Fonts\Arial.ttf"); 54 | } 55 | 56 | protected override void Draw() 57 | { 58 | base.Draw(); 59 | 60 | Graphics.SpriteRenderer.Begin(); 61 | 62 | StringBuilder builder = new StringBuilder(); 63 | builder.AppendLine("text"); 64 | builder.AppendLine("more text"); 65 | 66 | string text = builder.ToString(); 67 | 68 | UI.DefaultStyle.Font.Draw(Graphics.SpriteRenderer, 48, text, new Vector2T(10, 10), 69 | Color.White, 0, Vector2.Zero, Vector2.One); 70 | 71 | Graphics.SpriteRenderer.End(); 72 | 73 | /*int i = 0; 74 | foreach (Texture2D texture in _textures) 75 | { 76 | Graphics.SpriteRenderer.Begin(); 77 | Graphics.SpriteRenderer.Draw(texture, new Vector2(i * 100), null, Color.White, 0, Vector2.Zero, 78 | Vector2.One); 79 | Graphics.SpriteRenderer.End(); 80 | 81 | i++; 82 | }*/ 83 | } 84 | } -------------------------------------------------------------------------------- /Easel.Tests/TestScenes/TestPhysics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Easel.Entities; 4 | using Easel.Entities.Components; 5 | using Easel.Graphics; 6 | using Easel.Graphics.Materials; 7 | using Easel.Graphics.Primitives; 8 | using Easel.Math; 9 | using Easel.Physics.Shapes; 10 | using Easel.Physics.Structs; 11 | using Easel.Scenes; 12 | using Pie.Windowing; 13 | 14 | namespace Easel.Tests.TestScenes; 15 | 16 | public class TestPhysics : Scene 17 | { 18 | protected override void Initialize() 19 | { 20 | base.Initialize(); 21 | 22 | Camera.Main.AddComponent(new NoClipCamera() { MoveSpeed = 10 }); 23 | 24 | Camera.Main.ClearColor = Color.CornflowerBlue; 25 | 26 | Material material = new StandardMaterial() { RasterizerState = RasterizerState.CullClockwise }; 27 | 28 | Entity cube = new Entity($"cube", new Transform() 29 | { 30 | Position = new Vector3(0, 5, -5), 31 | Rotation = Quaternion.CreateFromYawPitchRoll(0, 1, 0) 32 | }); 33 | cube.AddComponent(new Rigidbody(new BoxShape(new Vector3(0.5f)))); 34 | cube.AddComponent(new ModelRenderer(new Cube(), material)); 35 | AddEntity(cube); 36 | 37 | Entity cube2 = new Entity("cubes", new Transform() 38 | { 39 | Position = new Vector3(0, -5, -3), 40 | Scale = new Vector3(20, 0.25f, 20) 41 | }); 42 | cube2.AddComponent(new ModelRenderer(new Cube(), material)); 43 | cube2.AddComponent(new Rigidbody(new BoxShape(new Vector3(10, 0.25f / 2f, 10)), new RigidbodyInitSettings() { BodyType = BodyType.Static })); 44 | AddEntity(cube2); 45 | } 46 | } -------------------------------------------------------------------------------- /Easel.Tests/TestScenes/TestServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Easel.Entities; 3 | using Easel.Scenes; 4 | 5 | namespace Easel.Tests.TestScenes; 6 | 7 | public class TestServer : Scene 8 | { 9 | protected override void Initialize() 10 | { 11 | base.Initialize(); 12 | 13 | Entity entity = new Entity("test"); 14 | AddEntity(entity); 15 | } 16 | 17 | protected override void Update() 18 | { 19 | base.Update(); 20 | 21 | Entity test = GetEntity("test"); 22 | test.Transform.Position.X -= 1 * Time.DeltaTime; 23 | Console.WriteLine(Time.DeltaTime); 24 | } 25 | } -------------------------------------------------------------------------------- /Easel.Tests/imgui.ini: -------------------------------------------------------------------------------- 1 | [Window][Debug##Default] 2 | Pos=60,60 3 | Size=400,400 4 | Collapsed=0 5 | 6 | [Window][File Picker] 7 | Pos=60,60 8 | Size=75,187 9 | Collapsed=0 10 | 11 | -------------------------------------------------------------------------------- /Easel/Animations/Animation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Easel.Animations; 5 | 6 | public class Animation 7 | { 8 | public List Channels; 9 | 10 | private double _currentTime; 11 | 12 | public double TotalRunningTime { get; private set; } 13 | 14 | public double Time 15 | { 16 | get => _currentTime; 17 | // set 18 | } 19 | 20 | public Animation(int numChannels = 0) 21 | { 22 | Channels = new List(numChannels); 23 | } 24 | 25 | public void AddChannel(IAnimationChannel channel) 26 | { 27 | Channels.Add(channel); 28 | CalculateRunningTime(); 29 | } 30 | 31 | public void Update() 32 | { 33 | double dt = Easel.Time.DeltaTimeD; 34 | 35 | foreach (IAnimationChannel channel in Channels) 36 | channel.Update(dt); 37 | 38 | _currentTime += dt; 39 | 40 | if (_currentTime >= TotalRunningTime) 41 | { 42 | _currentTime = 0; 43 | foreach (IAnimationChannel channel in Channels) 44 | channel.Reset(); 45 | } 46 | } 47 | 48 | private void CalculateRunningTime() 49 | { 50 | double largestTime = 0; 51 | 52 | foreach (IAnimationChannel channel in Channels) 53 | { 54 | double time = channel.Keyframes[^1].Time; 55 | if (time > largestTime) 56 | largestTime = time; 57 | } 58 | 59 | TotalRunningTime = largestTime; 60 | } 61 | } -------------------------------------------------------------------------------- /Easel/Animations/IAnimationChannel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Easel.Animations; 4 | 5 | public interface IAnimationChannel 6 | { 7 | public List Keyframes { get; set; } 8 | 9 | protected internal void Update(double dt); 10 | 11 | protected internal void Reset(); 12 | } -------------------------------------------------------------------------------- /Easel/Animations/IAnimationKeyframe.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.Animations; 2 | 3 | public interface IAnimationKeyframe 4 | { 5 | public double Time { get; set; } 6 | } -------------------------------------------------------------------------------- /Easel/Animations/TransformChannel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Easel.Entities; 4 | 5 | namespace Easel.Animations; 6 | 7 | public class TransformChannel : IAnimationChannel 8 | { 9 | private int _currentKeyframe; 10 | private double _currentTime; 11 | 12 | public Transform Transform; 13 | private IAnimationChannel _animationChannelImplementation; 14 | 15 | public List Keyframes { get; set; } 16 | 17 | public TransformChannel(params TransformKeyframe[] keyframes) 18 | { 19 | Keyframes = new List(keyframes.Cast()); 20 | _currentKeyframe = 0; 21 | } 22 | 23 | public void Update(double dt) 24 | { 25 | if (_currentKeyframe + 1 >= Keyframes.Count) 26 | return; 27 | 28 | TransformKeyframe currentFrame = (TransformKeyframe) Keyframes[_currentKeyframe]; 29 | TransformKeyframe nextFrame = (TransformKeyframe) Keyframes[_currentKeyframe + 1]; 30 | 31 | double lerpAmount = (_currentTime - currentFrame.Time) / (nextFrame.Time - currentFrame.Time); 32 | 33 | Transform = Transform.Lerp(currentFrame.Transform, nextFrame.Transform, 34 | (float) double.Clamp(lerpAmount, 0.0, 1.0)); 35 | 36 | _currentTime += dt; 37 | 38 | if (_currentTime >= nextFrame.Time) 39 | { 40 | _currentKeyframe++; 41 | 42 | if (_currentKeyframe >= Keyframes.Count) 43 | _currentKeyframe = 0; 44 | } 45 | } 46 | 47 | public void Reset() 48 | { 49 | _currentKeyframe = 0; 50 | _currentTime = 0; 51 | } 52 | } -------------------------------------------------------------------------------- /Easel/Animations/TransformKeyframe.cs: -------------------------------------------------------------------------------- 1 | using Easel.Entities; 2 | 3 | namespace Easel.Animations; 4 | 5 | public struct TransformKeyframe : IAnimationKeyframe 6 | { 7 | public Transform Transform; 8 | 9 | public double Time { get; set; } 10 | 11 | public TransformKeyframe(Transform transform, double time) 12 | { 13 | Transform = transform; 14 | Time = time; 15 | } 16 | } -------------------------------------------------------------------------------- /Easel/Animations/Tween.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Easel.Math; 3 | 4 | namespace Easel.Animations; 5 | 6 | public struct Tween 7 | { 8 | public bool IsFinished; 9 | 10 | public int NumRepeats; 11 | 12 | public bool PingPong; 13 | 14 | public double Value 15 | { 16 | get 17 | { 18 | if (IsFinished) 19 | return 1.0; 20 | 21 | double time = CurrentTime / _endTime; 22 | 23 | if (PingPong && (_numRepeats & 1) == 1) 24 | time = 1.0 - time; 25 | 26 | return EaselMath.Clamp(time, 0.0, 1.0); 27 | } 28 | } 29 | 30 | private double _endTime; 31 | private int _numRepeats; 32 | 33 | public double CurrentTime; 34 | 35 | public Tween(double duration) 36 | { 37 | _endTime = duration; 38 | NumRepeats = 1; 39 | } 40 | 41 | public void Update() 42 | { 43 | if (!IsFinished && CurrentTime >= _endTime) 44 | { 45 | _numRepeats++; 46 | if (NumRepeats > 0 && _numRepeats >= NumRepeats) 47 | { 48 | IsFinished = true; 49 | return; 50 | } 51 | CurrentTime -= _endTime; 52 | } 53 | 54 | CurrentTime += Time.DeltaTimeD; 55 | } 56 | } -------------------------------------------------------------------------------- /Easel/Audio/AudioEffect.cs: -------------------------------------------------------------------------------- 1 | /*using System.Collections.Generic; 2 | using Easel.Math; 3 | 4 | namespace Easel.Audio; 5 | 6 | public static class AudioEffect 7 | { 8 | private static List _fades; 9 | 10 | static AudioEffect() 11 | { 12 | _fades = new List(); 13 | } 14 | 15 | public static void Fade(ISoundInstance instance, double startVolume, double endVolume, float time) 16 | { 17 | _fades.Add(new FadeEffect(instance, time, startVolume, endVolume)); 18 | } 19 | 20 | internal static void Update() 21 | { 22 | for (int i = 0; i < _fades.Count; i++) 23 | { 24 | float lerpValue = (Time.TotalSeconds - _fades[i].StartTime) / (_fades[i].EndTime - _fades[i].StartTime); 25 | 26 | _fades[i].Instance.Volume = EaselMath.Lerp(_fades[i].InitialVolume, _fades[i].EndVolume, lerpValue); 27 | if (lerpValue >= 1f) 28 | { 29 | _fades.Remove(_fades[i]); 30 | i--; 31 | } 32 | } 33 | } 34 | 35 | private struct FadeEffect 36 | { 37 | public ISoundInstance Instance; 38 | public double InitialVolume; 39 | public double EndVolume; 40 | public float StartTime; 41 | public float EndTime; 42 | 43 | public FadeEffect(ISoundInstance instance, float time, double initialVolume, double endVolume) 44 | { 45 | Instance = instance; 46 | InitialVolume = initialVolume; 47 | EndVolume = endVolume; 48 | 49 | StartTime = Time.TotalSeconds; 50 | EndTime = StartTime + time; 51 | } 52 | } 53 | }*/ -------------------------------------------------------------------------------- /Easel/Audio/EaselAudio.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Pie.Audio; 3 | 4 | namespace Easel.Audio; 5 | 6 | public class EaselAudio : IDisposable 7 | { 8 | public readonly AudioDevice PieAudio; 9 | 10 | public EaselAudio() 11 | { 12 | PieAudio = new AudioDevice(48000, 256); 13 | } 14 | 15 | public bool TryGetAvailableChannel(out ushort availableChannel) 16 | { 17 | for (ushort v = 0; v < PieAudio.NumVoices; v++) 18 | { 19 | // Paused voices should not be overwritten either. 20 | if (PieAudio.GetVoiceState(v) == PlayState.Stopped) 21 | { 22 | availableChannel = v; 23 | return true; 24 | } 25 | } 26 | 27 | availableChannel = ushort.MaxValue; 28 | return false; 29 | } 30 | 31 | public void Dispose() 32 | { 33 | PieAudio?.Dispose(); 34 | } 35 | } -------------------------------------------------------------------------------- /Easel/Content/BitmapProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Easel.Content.Builder; 3 | using Easel.Graphics; 4 | 5 | namespace Easel.Content; 6 | 7 | public class BitmapProcessor : IContentProcessor 8 | { 9 | public object Load(string contentDir, IContentType contentType) 10 | { 11 | ImageContent content = (ImageContent) contentType; 12 | return new Bitmap(Path.Combine(contentDir, content.Path)); 13 | } 14 | } -------------------------------------------------------------------------------- /Easel/Content/Builder/ContentDefinition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | 5 | namespace Easel.Content.Builder; 6 | 7 | public class ContentDefinition 8 | { 9 | public readonly string Name; 10 | 11 | public readonly Dictionary ContentTypes; 12 | 13 | internal ContentDefinition(string name, Dictionary contentTypes) 14 | { 15 | Name = name; 16 | ContentTypes = contentTypes; 17 | } 18 | 19 | public string SerializeToJson(Formatting formatting = Formatting.Indented) 20 | { 21 | List contentTypes = new List(ContentTypes.Count); 22 | 23 | foreach ((_, IContentType type) in ContentTypes) 24 | contentTypes.Add(type); 25 | 26 | SerializableContentDefinition serializable = new SerializableContentDefinition() 27 | { 28 | Name = Name, 29 | ContentTypes = contentTypes.ToArray() 30 | }; 31 | 32 | return JsonConvert.SerializeObject(serializable, formatting, new JsonSerializerSettings() 33 | { 34 | TypeNameHandling = TypeNameHandling.Auto 35 | }); 36 | } 37 | 38 | public static ContentDefinition FromJson(string json) 39 | { 40 | SerializableContentDefinition serializable = JsonConvert.DeserializeObject(json, new JsonSerializerSettings() 41 | { 42 | TypeNameHandling = TypeNameHandling.Auto 43 | }); 44 | Dictionary contentTypes = 45 | new Dictionary(serializable.ContentTypes.Length); 46 | 47 | foreach (IContentType type in serializable.ContentTypes) 48 | contentTypes.Add(type.FriendlyName, type); 49 | 50 | return new ContentDefinition(serializable.Name, contentTypes); 51 | } 52 | 53 | private class SerializableContentDefinition 54 | { 55 | public string Name; 56 | 57 | [JsonProperty("Content")] 58 | public IContentType[] ContentTypes; 59 | 60 | public SerializableContentDefinition() { } 61 | } 62 | } -------------------------------------------------------------------------------- /Easel/Content/Builder/ContentValidity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Easel.Content.Builder; 4 | 5 | public class ContentValidity 6 | { 7 | public readonly bool IsValid; 8 | 9 | public readonly Exception Exception; 10 | 11 | public ContentValidity(bool isValid, Exception exception) 12 | { 13 | IsValid = isValid; 14 | Exception = exception; 15 | } 16 | } -------------------------------------------------------------------------------- /Easel/Content/Builder/DuplicateHandling.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.Content.Builder; 2 | 3 | public enum DuplicateHandling 4 | { 5 | Error, 6 | Ignore, 7 | Overwrite 8 | } -------------------------------------------------------------------------------- /Easel/Content/Builder/FontContent.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Easel.GUI; 3 | 4 | namespace Easel.Content.Builder; 5 | 6 | public struct FontContent : IContentType 7 | { 8 | public string Path { get; set; } 9 | 10 | public FontOptions FontOptions; 11 | 12 | public FontContent() { } 13 | 14 | public FontContent(string path) : this(path, new FontOptions()) { } 15 | 16 | public FontContent(string path, FontOptions options) 17 | { 18 | Path = path; 19 | FriendlyName = System.IO.Path 20 | .Combine(System.IO.Path.GetDirectoryName(path), System.IO.Path.GetFileNameWithoutExtension(path)) 21 | .Replace('\\', '/'); 22 | FontOptions = options; 23 | } 24 | 25 | public string FriendlyName { get; set; } 26 | 27 | public bool AllowDuplicates => false; 28 | 29 | public ContentValidity CheckValidity(string contentPath) 30 | { 31 | if (!File.Exists(System.IO.Path.Combine(contentPath, Path))) 32 | return new ContentValidity(false, new FileNotFoundException($"The file at {Path} was not found.", Path)); 33 | 34 | return new ContentValidity(true, null); 35 | } 36 | } -------------------------------------------------------------------------------- /Easel/Content/Builder/IContentType.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Easel.Content.Builder; 4 | 5 | public interface IContentType 6 | { 7 | public string Path { get; set; } 8 | 9 | public string FriendlyName { get; set; } 10 | 11 | [JsonIgnore] 12 | public bool AllowDuplicates { get; } 13 | 14 | public ContentValidity CheckValidity(string contentPath); 15 | } -------------------------------------------------------------------------------- /Easel/Content/Builder/ImageContent.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Easel.Content.Builder; 4 | 5 | public struct ImageContent : IContentType 6 | { 7 | public string Path { get; set; } 8 | 9 | public ImageContent() { } 10 | 11 | public ImageContent(string path) 12 | { 13 | Path = path; 14 | FriendlyName = System.IO.Path 15 | .Combine(System.IO.Path.GetDirectoryName(path), System.IO.Path.GetFileNameWithoutExtension(path)) 16 | .Replace('\\', '/'); 17 | } 18 | 19 | public string FriendlyName { get; set; } 20 | 21 | public bool AllowDuplicates => false; 22 | 23 | public ContentValidity CheckValidity(string contentPath) 24 | { 25 | if (!File.Exists(System.IO.Path.Combine(contentPath, Path))) 26 | return new ContentValidity(false, new FileNotFoundException($"The file at {Path} was not found.", Path)); 27 | 28 | return new ContentValidity(true, null); 29 | } 30 | } -------------------------------------------------------------------------------- /Easel/Content/Builder/ModelContent.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Easel.Content.Builder; 4 | 5 | public struct ModelContent : IContentType 6 | { 7 | public string Path { get; set; } 8 | 9 | public bool FlipUvs; 10 | 11 | public ModelContent() { } 12 | 13 | public ModelContent(string path, bool flipUvs = true) 14 | { 15 | Path = path; 16 | FriendlyName = System.IO.Path 17 | .Combine(System.IO.Path.GetDirectoryName(path), System.IO.Path.GetFileNameWithoutExtension(path)) 18 | .Replace('\\', '/'); 19 | FlipUvs = flipUvs; 20 | } 21 | 22 | public string FriendlyName { get; set; } 23 | 24 | public bool AllowDuplicates => false; 25 | 26 | public ContentValidity CheckValidity(string contentPath) 27 | { 28 | if (!File.Exists(System.IO.Path.Combine(contentPath, Path))) 29 | return new ContentValidity(false, new FileNotFoundException($"The file at {Path} was not found.", Path)); 30 | 31 | return new ContentValidity(true, null); 32 | } 33 | } -------------------------------------------------------------------------------- /Easel/Content/Builder/SoundContent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Easel.Content.Builder; 5 | 6 | public struct SoundContent : IContentType 7 | { 8 | public string Path { get; set; } 9 | 10 | public SoundContent() { } 11 | 12 | public SoundContent(string path) 13 | { 14 | Path = path; 15 | FriendlyName = System.IO.Path 16 | .Combine(System.IO.Path.GetDirectoryName(path), System.IO.Path.GetFileNameWithoutExtension(path)) 17 | .Replace('\\', '/'); 18 | } 19 | 20 | public string FriendlyName { get; set; } 21 | 22 | public bool AllowDuplicates => false; 23 | 24 | public ContentValidity CheckValidity(string contentPath) 25 | { 26 | string fullPath = System.IO.Path.Combine(contentPath, Path); 27 | if (!File.Exists(fullPath)) 28 | return new ContentValidity(false, new FileNotFoundException($"The file at {Path} was not found.", Path)); 29 | 30 | using FileStream fs = File.OpenRead(fullPath); 31 | using BinaryReader reader = new BinaryReader(fs); 32 | 33 | uint identifier = reader.ReadUInt32(); 34 | if (identifier is 0x46464952 or 0x5367674F) 35 | return new ContentValidity(true, null); 36 | 37 | return new ContentValidity(false, 38 | new NotSupportedException( 39 | $"Unsupported sound type \"{System.IO.Path.GetExtension(Path)}\". Only .wav and .ogg supported.")); 40 | } 41 | } -------------------------------------------------------------------------------- /Easel/Content/ContentManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Timers; 8 | using Easel.Audio; 9 | using Easel.Content.Builder; 10 | using Easel.Content.Localization; 11 | using Easel.Core; 12 | using Easel.Data; 13 | using Easel.Formats; 14 | using Easel.Graphics; 15 | using Easel.GUI; 16 | 17 | namespace Easel.Content; 18 | 19 | public class ContentManager 20 | { 21 | private string _defaultName; 22 | 23 | private Dictionary _definitions; 24 | private Dictionary _processors; 25 | 26 | private string _location; 27 | 28 | public ContentManager() 29 | { 30 | _definitions = new Dictionary(); 31 | _location = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? ""; 32 | 33 | _processors = new Dictionary(); 34 | AddContentProcessor(typeof(Texture2D), new TextureProcessor()); 35 | //AddContentProcessor(typeof(Sound), new SoundProcessor()); 36 | AddContentProcessor(typeof(Model), new ModelProcessor()); 37 | AddContentProcessor(typeof(Font), new FontProcessor()); 38 | AddContentProcessor(typeof(Bitmap), new BitmapProcessor()); 39 | } 40 | 41 | /// 42 | /// Add content with a content definition, either from a file, or manually generated. 43 | /// 44 | /// The definition to initialize with. 45 | public void AddContent(ContentDefinition definition) 46 | { 47 | if (_definitions.Count == 0) 48 | _defaultName = definition.Name; 49 | _definitions.Add(definition.Name, definition); 50 | 51 | Logger.Debug($"Content definition with name \"{definition.Name}\" added."); 52 | } 53 | 54 | /*/// 55 | /// Add content to the content manager. This internally calls , 56 | /// and generates a content definition automatically. 57 | /// 58 | public void AddContent() 59 | { 60 | 61 | }*/ 62 | 63 | public bool TryLoad(string definitionName, string path, [NotNullWhen(true)] out T item) 64 | { 65 | item = default; 66 | 67 | Logger.Debug($"Loading content item \"{path}\" from definition \"{definitionName}\"..."); 68 | if (!_processors.TryGetValue(typeof(T), out IContentProcessor processor)) 69 | Logger.Fatal($"A content processor for type {typeof(T)} could not be found."); 70 | 71 | if (!_definitions[definitionName].ContentTypes.TryGetValue(path, out IContentType type)) 72 | return false; 73 | 74 | item = (T) processor!.Load(Path.Combine(_location, definitionName), type); 75 | return true; 76 | } 77 | 78 | public T Load(string definitionName, string path) 79 | { 80 | if (!TryLoad(definitionName, path, out T item)) 81 | Logger.Fatal($"No content file with name \"{path}\" could be found in definition \"{definitionName}\"."); 82 | 83 | return item; 84 | } 85 | 86 | public bool TryLoad(string path, [NotNullWhen(true)] out T item) 87 | { 88 | return TryLoad(_defaultName, path, out item); 89 | } 90 | 91 | public T Load(string path) 92 | { 93 | return Load(_defaultName, path); 94 | } 95 | 96 | public string[] GetAllFiles(string path, string searchPattern = "*", bool recursive = true) 97 | { 98 | return Directory.GetFiles(Path.Combine(_location, _defaultName, path), searchPattern, 99 | recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); 100 | } 101 | 102 | public void AddContentProcessor(Type type, IContentProcessor processor) 103 | { 104 | _processors.Add(type, processor); 105 | } 106 | } -------------------------------------------------------------------------------- /Easel/Content/FontProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Easel.Content.Builder; 3 | using Easel.GUI; 4 | 5 | namespace Easel.Content; 6 | 7 | public class FontProcessor : IContentProcessor 8 | { 9 | public object Load(string contentDir, IContentType contentType) 10 | { 11 | FontContent content = (FontContent) contentType; 12 | return new Font(Path.Combine(contentDir, content.Path), content.FontOptions); 13 | } 14 | } -------------------------------------------------------------------------------- /Easel/Content/IContentProcessor.cs: -------------------------------------------------------------------------------- 1 | using Easel.Content.Builder; 2 | 3 | namespace Easel.Content; 4 | 5 | public interface IContentProcessor 6 | { 7 | public object Load(string contentDir, IContentType contentType); 8 | } -------------------------------------------------------------------------------- /Easel/Content/Localization/Locale.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Xml.Serialization; 3 | using XmlSerializer = Easel.Data.XmlSerializer; 4 | 5 | namespace Easel.Content.Localization; 6 | 7 | public class Locale 8 | { 9 | public string Name; 10 | 11 | public Dictionary Strings; 12 | 13 | public Locale() 14 | { 15 | Name = "Unknown"; 16 | Strings = new Dictionary(); 17 | } 18 | 19 | public Locale(string name) 20 | { 21 | Name = name; 22 | 23 | Strings = new Dictionary(); 24 | } 25 | 26 | public string GetString(string key, params object[] format) 27 | { 28 | string text = Strings.TryGetValue(key, out string value) 29 | ? string.Format(value, format) 30 | : key; 31 | 32 | return text; 33 | } 34 | 35 | public string this[string key] => GetString(key, null); 36 | } -------------------------------------------------------------------------------- /Easel/Content/ModelProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Easel.Content.Builder; 3 | using Easel.Graphics; 4 | 5 | namespace Easel.Content; 6 | 7 | public class ModelProcessor : IContentProcessor 8 | { 9 | public object Load(string contentDir, IContentType contentType) 10 | { 11 | ModelContent content = (ModelContent) contentType; 12 | 13 | return new Model(Path.Combine(contentDir, content.Path), content.FlipUvs); 14 | } 15 | } -------------------------------------------------------------------------------- /Easel/Content/SoundProcessor.cs: -------------------------------------------------------------------------------- 1 | /*using System.IO; 2 | using Easel.Audio; 3 | using Easel.Content.Builder; 4 | 5 | namespace Easel.Content; 6 | 7 | public class SoundProcessor : IContentProcessor 8 | { 9 | public object Load(string contentDir, IContentType contentType) 10 | { 11 | SoundContent content = (SoundContent) contentType; 12 | 13 | return new Sound(Path.Combine(contentDir, content.Path)); 14 | } 15 | }*/ -------------------------------------------------------------------------------- /Easel/Content/TextureProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Easel.Content.Builder; 3 | using Easel.Formats; 4 | using Easel.Graphics; 5 | 6 | namespace Easel.Content; 7 | 8 | public class TextureProcessor : IContentProcessor 9 | { 10 | public object Load(string contentDir, IContentType contentType) 11 | { 12 | ImageContent content = (ImageContent) contentType; 13 | string extension = Path.GetExtension(content.Path); 14 | string fullPath = Path.Combine(contentDir, content.Path); 15 | switch (extension) 16 | { 17 | case ".dds": 18 | DDS dds = new DDS(File.ReadAllBytes(fullPath)); 19 | return new Texture2D(dds.Bitmaps[0][0]); 20 | 21 | default: 22 | return new Texture2D(fullPath); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Easel/Data/Data.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Easel.Core; 3 | using Easel.Data; 4 | using Easel.Math; 5 | 6 | namespace Easel.Configs; 7 | 8 | public static class Data 9 | { 10 | public static string AppBaseDir = "Data"; 11 | 12 | public static string ConfigFile = "Config.cfg"; 13 | 14 | public static string LogDir = "Logs"; 15 | 16 | /*public static EaselConfig LoadedConfig; 17 | 18 | public static bool LoadConfig() where T : EaselConfig 19 | { 20 | LoadedConfig = GetConfigFromFile(Path.Combine(AppBaseDir, ConfigFile)); 21 | if (LoadedConfig == null) 22 | return false; 23 | 24 | return true; 25 | } 26 | 27 | public static bool LoadConfig(ref GameSettings settings) where T : EaselConfig 28 | { 29 | if (!LoadConfig()) 30 | { 31 | settings.Size = new Size(1280, 720); 32 | settings.VSync = true; 33 | 34 | return false; 35 | } 36 | 37 | settings.Size = LoadedConfig.DisplayConfig.Size; 38 | settings.VSync = LoadedConfig.DisplayConfig.VSync; 39 | // TODO: GameSettings doesn't have a "start in fullscreen" option?? 40 | 41 | return true; 42 | } 43 | 44 | public static void SaveConfig() 45 | { 46 | SaveConfigFromFile(Path.Combine(AppBaseDir, ConfigFile), LoadedConfig); 47 | } 48 | 49 | public static void SaveConfigFromFile(string path, EaselConfig config) 50 | { 51 | Logger.Debug("Saving config file \"" + ConfigFile + "\"."); 52 | Directory.CreateDirectory(Path.GetDirectoryName(path)); 53 | File.WriteAllText(path, XmlSerializer.Serialize(config)); 54 | } 55 | 56 | public static T GetConfigFromFile(string path) where T : EaselConfig 57 | { 58 | Logger.Debug("Loading config file \"" + ConfigFile + "\"."); 59 | if (!File.Exists(path)) 60 | return null; 61 | return XmlSerializer.Deserialize(File.ReadAllText(path)); 62 | }*/ 63 | 64 | public static void InitializeLogFile(string name) 65 | { 66 | string loggerPath = Path.Combine(AppBaseDir, LogDir); 67 | 68 | Logger.Debug($"Attempting to initialize the log file \"{name}\" at \"{loggerPath}\"."); 69 | Directory.CreateDirectory(loggerPath); 70 | 71 | Logger.InitializeLogFile(Path.Combine(loggerPath, name)); 72 | Logger.Debug("Log file initialized!"); 73 | } 74 | } -------------------------------------------------------------------------------- /Easel/Data/DisplayConfig.cs: -------------------------------------------------------------------------------- 1 | using Easel.Math; 2 | 3 | namespace Easel.Configs; 4 | 5 | public struct DisplayConfig 6 | { 7 | public Size Size; 8 | 9 | public bool Fullscreen; 10 | 11 | public bool VSync; 12 | } -------------------------------------------------------------------------------- /Easel/Data/EaselConfig.cs: -------------------------------------------------------------------------------- 1 | using Easel.Configs; 2 | 3 | namespace Easel.Data; 4 | 5 | public abstract class EaselConfig 6 | { 7 | public static T LoadedConfig; 8 | 9 | public DisplayConfig DisplayConfig; 10 | 11 | protected EaselConfig() { } 12 | 13 | protected EaselConfig(DisplayConfig displayConfig) 14 | { 15 | DisplayConfig = displayConfig; 16 | } 17 | 18 | public static void LoadConfigFromXml(string xml) 19 | { 20 | LoadedConfig = XmlSerializer.Deserialize(xml); 21 | } 22 | } 23 | 24 | public class EaselConfig : EaselConfig 25 | { 26 | public EaselConfig(DisplayConfig displayConfig) : base(displayConfig) { } 27 | } -------------------------------------------------------------------------------- /Easel/Data/XmlSerializer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Xml; 3 | 4 | namespace Easel.Data; 5 | 6 | public static class XmlSerializer 7 | { 8 | public static string Serialize(object obj) 9 | { 10 | StringWriter writer = new StringWriter(); 11 | XmlTextWriter xmlWriter = new XmlTextWriter(writer); 12 | xmlWriter.Formatting = Formatting.Indented; 13 | System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType()); 14 | serializer.Serialize(xmlWriter, obj); 15 | return writer.ToString(); 16 | } 17 | 18 | public static T Deserialize(string text) 19 | { 20 | System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(T)); 21 | return (T) serializer.Deserialize(new StringReader(text)); 22 | } 23 | } -------------------------------------------------------------------------------- /Easel/Easel.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | disable 7 | true 8 | 9 | 10 | 11 | 1.0.0-pre 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | PreserveNewest 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Easel/EaselLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel/EaselLogo.png -------------------------------------------------------------------------------- /Easel/Entities/Components/Component.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Easel.Interfaces; 3 | using Easel.Physics; 4 | using Easel.Scenes; 5 | using Easel.Audio; 6 | using Easel.Content; 7 | using Easel.Graphics; 8 | 9 | namespace Easel.Entities.Components; 10 | 11 | /// 12 | /// The base class for all entity components and scripts. 13 | /// 14 | public abstract class Component : InheritableEntity, IDisposable 15 | { 16 | public bool Enabled; 17 | 18 | protected override EaselGame Game => EaselGame.Instance; 19 | 20 | protected override Simulation Simulation => EaselGame.Instance.Simulation; 21 | 22 | protected override EaselGraphics Graphics => EaselGame.Instance.GraphicsInternal; 23 | 24 | protected override EaselAudio Audio => EaselGame.Instance.AudioInternal; 25 | 26 | protected override ContentManager Content => EaselGame.Instance.Content; 27 | 28 | protected override Scene ActiveScene => SceneManager.ActiveScene; 29 | 30 | protected internal Entity Entity { get; internal set; } 31 | 32 | protected Component() 33 | { 34 | Enabled = true; 35 | } 36 | 37 | /// 38 | /// The of the current entity. 39 | /// 40 | protected Transform Transform => Entity.Transform; 41 | 42 | /// 43 | /// Called during entity initialization (usually when the entity is added to the scene), or, if the entity is already 44 | /// initialized, called when the component is added to the entity. 45 | /// 46 | protected internal virtual void Initialize() { } 47 | 48 | /// 49 | /// Called once per frame during update. 50 | /// 51 | protected internal virtual void Update() { } 52 | 53 | protected internal virtual void AfterUpdate() { } 54 | 55 | protected internal virtual void FixedUpdate() { } 56 | 57 | /// 58 | /// Called once per frame during draw. 59 | /// 60 | protected internal virtual void Draw() { } 61 | 62 | /// 63 | /// Called when an entity is removed from the scene, or when the scene changes. 64 | /// Use this to dispose of native resources. 65 | /// 66 | public virtual void Dispose() { } 67 | 68 | protected void AddComponent(Component component) => Entity.AddComponent(component); 69 | 70 | protected bool TryAddComponent(Component component) => Entity.TryAddComponent(component); 71 | 72 | /// 73 | /// Get the component with the given type on the current entity. 74 | /// 75 | /// The type of component to get. 76 | /// The found component. If not found, returns null. 77 | protected T GetComponent() where T : Component => Entity.GetComponent(); 78 | 79 | protected bool TryGetComponent(out T component) where T : Component => Entity.TryGetComponent(out component); 80 | 81 | protected bool HasComponent() where T : Component => Entity.HasComponent(); 82 | 83 | protected override void AddEntity(Entity entity) => SceneManager.ActiveScene.AddEntity( entity); 84 | 85 | protected override void RemoveEntity(string name) => SceneManager.ActiveScene.RemoveEntity(name); 86 | 87 | protected override void RemoveEntity(Entity entity) => SceneManager.ActiveScene.RemoveEntity(entity); 88 | 89 | protected override Entity GetEntity(string name) => SceneManager.ActiveScene.GetEntity(name); 90 | 91 | protected override T GetEntity(string name) => SceneManager.ActiveScene.GetEntity(name); 92 | 93 | protected override Entity[] GetEntitiesWithTag(string tag) => SceneManager.ActiveScene.GetEntitiesWithTag(tag); 94 | 95 | protected override Entity[] GetEntitiesWithComponent() => SceneManager.ActiveScene.GetEntitiesWithComponent(); 96 | 97 | protected override Entity[] GetAllEntities() => SceneManager.ActiveScene.GetAllEntities(); 98 | } -------------------------------------------------------------------------------- /Easel/Entities/Components/DirectionalLight.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Easel.Math; 3 | 4 | namespace Easel.Entities.Components; 5 | 6 | public class DirectionalLight : Component 7 | { 8 | internal Graphics.Lighting.DirectionalLight InternalLight; 9 | 10 | public Vector2 Direction 11 | { 12 | get => InternalLight.Direction; 13 | set => InternalLight.Direction = value; 14 | } 15 | 16 | public Color Color 17 | { 18 | get => InternalLight.Color; 19 | set => InternalLight.Color = value; 20 | } 21 | 22 | public DirectionalLight(Vector2 direction, Color color) 23 | { 24 | InternalLight = new Graphics.Lighting.DirectionalLight(direction, color); 25 | } 26 | } -------------------------------------------------------------------------------- /Easel/Entities/Components/ModelRenderer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Numerics; 3 | using Easel.Graphics; 4 | using Easel.Graphics.Primitives; 5 | using Easel.Graphics.Renderers; 6 | using Material = Easel.Graphics.Materials.Material; 7 | using Mesh = Easel.Graphics.Mesh; 8 | 9 | namespace Easel.Entities.Components; 10 | 11 | /// 12 | /// The bog-standard 3D model renderer for an entity. 13 | /// 14 | public class ModelRenderer : Component 15 | { 16 | private Renderable[] _renderables; 17 | private Matrix4x4[] _transforms; 18 | 19 | public ModelRenderer(Model model) 20 | { 21 | List renderables = new List(); 22 | List transforms = new List(); 23 | 24 | foreach (ModelMesh mmesh in model.Meshes) 25 | { 26 | foreach (Mesh mesh in mmesh.Meshes) 27 | { 28 | renderables.Add(Renderable.CreateFromMesh(mesh)); 29 | transforms.Add(mmesh.Transform); 30 | } 31 | } 32 | 33 | _renderables = renderables.ToArray(); 34 | _transforms = transforms.ToArray(); 35 | } 36 | 37 | public ModelRenderer(IPrimitive primitive, Material material) 38 | { 39 | _renderables = new[] { Renderable.CreateFromMesh(new Mesh(primitive.Vertices, primitive.Indices, material)) }; 40 | _transforms = new[] { Matrix4x4.Identity }; 41 | } 42 | 43 | protected internal override void Draw() 44 | { 45 | base.Draw(); 46 | Matrix4x4 world = Transform.TransformMatrix * 47 | (Entity.Parent?.Transform.TransformMatrix ?? Matrix4x4.Identity); 48 | for (int i = 0; i < _renderables.Length; i++) 49 | Graphics.Renderer.Draw(_renderables[i], Matrix4x4.Transpose(_transforms[i]) * world); 50 | } 51 | 52 | public override void Dispose() 53 | { 54 | base.Dispose(); 55 | 56 | for (int i = 0; i < _renderables.Length; i++) 57 | _renderables[i].Dispose(); 58 | } 59 | } -------------------------------------------------------------------------------- /Easel/Entities/Components/NoClipCamera.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Easel.Math; 4 | using Pie.Windowing; 5 | 6 | namespace Easel.Entities.Components; 7 | 8 | /// 9 | /// Provides a basic built-in no-clip FPS camera. 10 | /// 11 | public class NoClipCamera : Component 12 | { 13 | /// 14 | /// If enabled, the camera will behave exactly like an FPS camera. 15 | /// Y-rotation (pitch) is limited to -90 to +90 degrees, and Z-rotation (roll) is not allowed. 16 | /// If disabled, the camera can be rotated in every direction, and does not behave like an FPS camera. 17 | /// 18 | public bool FpsConstraints; 19 | 20 | public Key Forward; 21 | public Key Backward; 22 | public Key StrafeRight; 23 | public Key StrafeLeft; 24 | public Key Up; 25 | public Key Down; 26 | public Key Run; 27 | 28 | public float MouseSensitivity; 29 | public float MoveSpeed; 30 | public float RunSpeed; 31 | 32 | private Vector2 _rotation; 33 | 34 | public NoClipCamera() 35 | { 36 | FpsConstraints = true; 37 | 38 | Forward = Key.W; 39 | Backward = Key.S; 40 | StrafeLeft = Key.A; 41 | StrafeRight = Key.D; 42 | Up = Key.Space; 43 | Down = Key.LeftControl; 44 | Run = Key.LeftShift; 45 | 46 | MouseSensitivity = 0.01f; 47 | MoveSpeed = 50; 48 | RunSpeed = 100; 49 | 50 | _rotation = Vector2.Zero; 51 | } 52 | 53 | protected internal override void Update() 54 | { 55 | base.Update(); 56 | 57 | // TODO: Input contexts & configurations, no hardcoded keys 58 | 59 | float speed = MoveSpeed; 60 | if (Input.KeyDown(Run)) 61 | speed = RunSpeed; 62 | speed *= Time.DeltaTime; 63 | 64 | if (Input.KeyDown(Forward)) 65 | Transform.Position += Transform.Forward * speed; 66 | if (Input.KeyDown(Backward)) 67 | Transform.Position += Transform.Backward * speed; 68 | if (Input.KeyDown(StrafeLeft)) 69 | Transform.Position += Transform.Left * speed; 70 | if (Input.KeyDown(StrafeRight)) 71 | Transform.Position += Transform.Right * speed; 72 | if (Input.KeyDown(Up)) 73 | Transform.Position += Transform.Up * speed; 74 | if (Input.KeyDown(Down)) 75 | Transform.Position += Transform.Down * speed; 76 | 77 | Vector2 mouseDelta = Input.DeltaMousePosition * MouseSensitivity; 78 | if (FpsConstraints) 79 | { 80 | _rotation.X -= mouseDelta.X; 81 | _rotation.Y -= mouseDelta.Y; 82 | _rotation.Y = EaselMath.Clamp(_rotation.Y, -MathF.PI / 2, MathF.PI / 2); 83 | Transform.Rotation = Quaternion.CreateFromYawPitchRoll(_rotation.X, _rotation.Y, 0); 84 | } 85 | else 86 | { 87 | Transform.Rotation *= Quaternion.CreateFromAxisAngle(Vector3.UnitY, -mouseDelta.X); 88 | Transform.Rotation *= Quaternion.CreateFromAxisAngle(Vector3.UnitX, -mouseDelta.Y); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /Easel/Entities/Components/Rigidbody.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Easel.Physics.Internal; 4 | using Easel.Physics.Shapes; 5 | using Easel.Physics.Structs; 6 | using JoltPhysicsSharp; 7 | 8 | namespace Easel.Entities.Components; 9 | 10 | public class Rigidbody : Component 11 | { 12 | private ShapeSettings _settings; 13 | private RigidbodyInitSettings _initSettings; 14 | public BodyID Handle; 15 | 16 | public Vector3 LinearVelocity 17 | { 18 | get => Simulation.BodyInterface.GetLinearVelocity(Handle); 19 | set => Simulation.BodyInterface.SetLinearVelocity(Handle, value); 20 | } 21 | 22 | // TODO: Angular velocity in PR 23 | public Vector3 AngularVelocity 24 | { 25 | get => Simulation.BodyInterface.GetAngularVelocity(Handle); 26 | set => Simulation.BodyInterface.SetAngularVelocity(Handle, value); 27 | } 28 | 29 | public float Restitution 30 | { 31 | get => Simulation.BodyInterface.GetRestitution(Handle); 32 | set => Simulation.BodyInterface.SetRestitution(Handle, value); 33 | } 34 | 35 | public Rigidbody(IShape shape, RigidbodyInitSettings? settings = null) 36 | { 37 | _settings = shape.ShapeSettings; 38 | _initSettings = settings ?? new RigidbodyInitSettings(); 39 | } 40 | 41 | protected internal override void Initialize() 42 | { 43 | base.Initialize(); 44 | 45 | BodyCreationSettings settings = new BodyCreationSettings(_settings, Transform.Position, Transform.Rotation, 46 | (MotionType) _initSettings.BodyType, 47 | (ObjectLayer) (_initSettings.BodyType == BodyType.Static ? Layers.NonMoving : Layers.Moving)); 48 | 49 | Body body = Simulation.BodyInterface.CreateBody(settings); 50 | if (_initSettings.BodyType != BodyType.Static) 51 | { 52 | body.SetLinearVelocity(_initSettings.LinearVelocity); 53 | body.SetAngularVelocity(_initSettings.AngularVelocity); 54 | } 55 | 56 | body.Restitution = Restitution; 57 | 58 | Simulation.BodyInterface.AddBody(body, ActivationMode.Activate); 59 | Handle = body.ID; 60 | } 61 | 62 | protected internal override void AfterUpdate() 63 | { 64 | base.AfterUpdate(); 65 | 66 | Simulation.BodyInterface.SetPositionAndRotationWhenChanged(Handle, Transform.Position, Transform.Rotation, 67 | ActivationMode.Activate); 68 | } 69 | 70 | protected internal override void FixedUpdate() 71 | { 72 | base.FixedUpdate(); 73 | 74 | Transform.Position = Simulation.BodyInterface.GetPosition(Handle); 75 | Transform.Rotation = Simulation.BodyInterface.GetRotation(Handle); 76 | } 77 | 78 | public override void Dispose() 79 | { 80 | base.Dispose(); 81 | 82 | Simulation.BodyInterface.RemoveBody(Handle); 83 | Simulation.BodyInterface.DestroyBody(Handle); 84 | } 85 | 86 | public void ApplyForce(Vector3 force) 87 | { 88 | Simulation.BodyInterface.AddForce(Handle, force); 89 | } 90 | } -------------------------------------------------------------------------------- /Easel/Entities/Components/Sprite.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Easel.Core; 3 | using Easel.Graphics; 4 | using Easel.Graphics.Renderers; 5 | using Easel.Math; 6 | 7 | namespace Easel.Entities.Components; 8 | 9 | public class Sprite : Component 10 | { 11 | public Texture Texture; 12 | 13 | public Rectangle? SourceRectangle; 14 | 15 | public Color Tint; 16 | 17 | public SpriteFlip Flip; 18 | 19 | public Sprite(Texture texture) 20 | { 21 | Texture = texture; 22 | SourceRectangle = null; 23 | Tint = Color.White; 24 | Flip = SpriteFlip.None; 25 | } 26 | 27 | protected internal override void Draw() 28 | { 29 | base.Draw(); 30 | 31 | Graphics.Renderer.DrawSprite(new Graphics.Renderers.Sprite(Texture, (Vector3) Transform.Position, SourceRectangle, 32 | Tint, Transform.SpriteRotation, Transform.Origin.ToVector2(), Transform.Scale.ToVector2(), Flip)); 33 | } 34 | } -------------------------------------------------------------------------------- /Easel/Entities/Tags.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.Entities; 2 | 3 | /// 4 | /// Contains some commonly used tags for entities. 5 | /// 6 | public static class Tags 7 | { 8 | /// 9 | /// Assign this tag to an entity to make it the main camera of the scene (unless there is already a main camera, then 10 | /// it is ignored.) 11 | /// 12 | public const string MainCamera = "MainCamera"; 13 | } -------------------------------------------------------------------------------- /Easel/Formats/ETF.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using Easel.Graphics; 5 | using Easel.Math; 6 | using Pie; 7 | 8 | namespace Easel.Formats; 9 | 10 | public class ETF 11 | { 12 | public readonly int MipLevels; 13 | 14 | public readonly Size Size; 15 | 16 | public readonly Bitmap[][] Bitmaps; 17 | 18 | public ETF(byte[] data) 19 | { 20 | using MemoryStream stream = new MemoryStream(data); 21 | using BinaryReader reader = new BinaryReader(stream); 22 | 23 | if (reader.ReadUInt32() != 0x20465445) 24 | throw new InvalidDataException("Given file is not an ETF file (ETF identifier missing)."); 25 | 26 | reader.ReadUInt32(); // Version 27 | 28 | #region Header 29 | 30 | uint width = reader.ReadUInt32(); 31 | 32 | uint height = reader.ReadUInt32(); 33 | 34 | byte flags = reader.ReadByte(); 35 | 36 | bool mipMaps = (flags & 0x1) == 0x1; 37 | bool arrayTexture = (flags & 0x2) == 0x2; 38 | 39 | if ((flags & 0x4) == 0x4) 40 | { 41 | uint increaseBy = reader.ReadUInt32(); 42 | reader.BaseStream.Position += increaseBy; 43 | } 44 | 45 | Format format = (Format) reader.ReadByte(); 46 | 47 | int mipLevels = 1; 48 | int arraySize = 1; 49 | 50 | if (mipMaps) 51 | mipLevels = reader.ReadByte(); 52 | if (arrayTexture) 53 | arraySize = (int) reader.ReadUInt32(); 54 | 55 | uint dataSize = reader.ReadUInt32(); 56 | 57 | #endregion 58 | 59 | MipLevels = mipLevels; 60 | Size = new Size((int) width, (int) height); 61 | Bitmaps = new Bitmap[arraySize][]; 62 | 63 | for (int i = 0; i < arraySize; i++) 64 | { 65 | Bitmaps[i] = new Bitmap[mipLevels]; 66 | 67 | int w = (int) width; 68 | int h = (int) height; 69 | int dS = (int) dataSize; 70 | 71 | for (int m = 0; m < mipLevels; m++) 72 | { 73 | Bitmaps[i][m] = new Bitmap(w, h, format, reader.ReadBytes(dS)); 74 | 75 | w /= 2; 76 | h /= 2; 77 | dS /= 4; 78 | } 79 | } 80 | } 81 | 82 | public static byte[] CreateEtf(Bitmap bitmap, Format? desiredFormat = null, string customData = null) 83 | { 84 | int mipLevels = 1; 85 | int arraySize = 1; 86 | 87 | bool containsCustomData = customData != null; 88 | 89 | using MemoryStream stream = new MemoryStream(); 90 | using BinaryWriter writer = new BinaryWriter(stream); 91 | writer.Write(0x20465445); 92 | 93 | writer.Write(1); 94 | 95 | #region EtfHeader 96 | 97 | writer.Write(bitmap.Size.Width); 98 | 99 | writer.Write(bitmap.Size.Height); 100 | 101 | byte flags = 0; 102 | if (mipLevels > 1) 103 | flags |= 0x1; 104 | if (arraySize > 1) 105 | flags |= 0x2; 106 | if (containsCustomData) 107 | flags |= 0x4; 108 | 109 | writer.Write(flags); 110 | 111 | if (containsCustomData) 112 | { 113 | byte[] reservedBytes = Encoding.UTF8.GetBytes(customData); 114 | writer.Write(reservedBytes.Length); 115 | writer.Write(reservedBytes); 116 | } 117 | 118 | Format format = desiredFormat ?? bitmap.Format; 119 | writer.Write((byte) format); 120 | 121 | if (mipLevels > 1) 122 | writer.Write((byte) mipLevels); 123 | if (arraySize > 1) 124 | writer.Write(arraySize); 125 | 126 | writer.Write(bitmap.Size.Width * bitmap.Size.Height * format.BitsPerPixel()); 127 | 128 | #endregion 129 | 130 | 131 | for (int m = 0; m < mipLevels; m++) 132 | { 133 | writer.Write(bitmap.Data); 134 | } 135 | 136 | return stream.ToArray(); 137 | } 138 | } -------------------------------------------------------------------------------- /Easel/Formats/ETF.md: -------------------------------------------------------------------------------- 1 | # Easel Texture Format (.etf) 2 | Each ETF file consists of the following layout: 3 | 4 | ```rs 5 | struct EtfFile { 6 | file_id: u32, 7 | version: u32, 8 | header: EtfHeader, 9 | data: Vec 10 | } 11 | ``` 12 | 13 | ### file_id 14 | ETF file identifier. Contains the string 'ETF ' (0x20465445) 15 | 16 | ### version 17 | The version number of the ETF file. 18 | 19 | ### header 20 | The EtfHeader itself. 21 | 22 | ### data 23 | Contains the texture data. The length of this will be equal to the `array_size`. 24 | 25 | ## EtfHeader 26 | 27 | ```rs 28 | struct EtfHeader { 29 | width: u32, 30 | height: u32, 31 | flags: u8, 32 | reserved_len: Option, 33 | reserved: Vec, 34 | format: PieFormat, 35 | mip_levels: Option, 36 | array_size: Option, 37 | data_size: u32 38 | } 39 | ``` 40 | 41 | ### width 42 | The width of the texture in pixels. 43 | 44 | ### height 45 | The height of the texture in pixels. 46 | 47 | ### flags 48 | Various flags. 49 | 50 | | Name | Value | Description | 51 | |----------|-------|---------------------------------------------| 52 | | Mipmaps | 0x1 | This texture contains mipmaps. | 53 | | Array | 0x2 | This texture is an array texture. | 54 | | Reserved | 0x4 | This texture contains reserved/custom data. | 55 | 56 | ### reserved_len 57 | The length, in bytes, of the reserved data. **Only read this value if the reserved flag is set.** 58 | 59 | ### reserved 60 | The reserved/custom data itself. Custom data may be contained here, but can be ignored by most implementations. **Only read this value if the reserved flag is set.** 61 | 62 | ### format 63 | The pie format. 64 | 65 | ### mip_levels 66 | The number of mipmaps in this texture. **Only read this value if the mipmaps flag is set.** 67 | 68 | ### array_size 69 | The size of the array. **Only read this value if the array flag is set.** 70 | 71 | ### data_size 72 | The size, in bytes, of the most detailed image. 73 | 74 | ## MipmappedBitmap 75 | ```rs 76 | struct MipmappedBitmap { 77 | data: Vec> 78 | } 79 | ``` 80 | 81 | ### data 82 | The mipmapped data itself. The length of this Vec is equal to `mip_levels`. -------------------------------------------------------------------------------- /Easel/GUI/Anchor.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.GUI; 2 | 3 | public enum Anchor 4 | { 5 | TopLeft, 6 | TopCenter, 7 | TopRight, 8 | 9 | CenterLeft, 10 | CenterCenter, 11 | CenterRight, 12 | 13 | BottomLeft, 14 | BottomCenter, 15 | BottomRight 16 | } -------------------------------------------------------------------------------- /Easel/GUI/BBCode/BBCodeInstruction.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.GUI.BBCode; 2 | 3 | public class BBCodeInstruction 4 | { 5 | public InstructionType InstructionType; 6 | 7 | public bool IsExiting; 8 | 9 | public BBCodeInstruction(InstructionType instructionType, bool isExiting) 10 | { 11 | InstructionType = instructionType; 12 | IsExiting = isExiting; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return InstructionType.ToString().ToUpper() + (IsExiting ? " EXITING" : ""); 18 | } 19 | } -------------------------------------------------------------------------------- /Easel/GUI/BBCode/BBCodeParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using Easel.Math; 5 | 6 | namespace Easel.GUI.BBCode; 7 | 8 | public static class BBCodeParser 9 | { 10 | public static BBCodeInstruction[] Parse(string text) 11 | { 12 | List instructions = new List(); 13 | bool inBrackets = false; 14 | string bracketsText = ""; 15 | string tempText = ""; 16 | 17 | for (int c = 0; c < text.Length; c++) 18 | { 19 | char chr = text[c]; 20 | switch (chr) 21 | { 22 | case '[' when c <= 0 || text[c - 1] != '\\': 23 | instructions.Add(new TextInstruction(tempText)); 24 | tempText = ""; 25 | inBrackets = true; 26 | bracketsText += '['; 27 | break; 28 | case ']' when inBrackets: 29 | inBrackets = false; 30 | bracketsText += ']'; 31 | instructions.Add(ParseTag(bracketsText)); 32 | bracketsText = ""; 33 | break; 34 | case '\\' when c < text.Length - 1 && text[c + 1] == '[': 35 | break; 36 | default: 37 | if (inBrackets) 38 | bracketsText += chr; 39 | else 40 | tempText += chr; 41 | break; 42 | } 43 | } 44 | 45 | if (tempText.Length > 0) 46 | instructions.Add(new TextInstruction(tempText)); 47 | if (bracketsText.Length > 0) 48 | instructions.Add(new TextInstruction(bracketsText)); 49 | 50 | return instructions.ToArray(); 51 | } 52 | 53 | private static BBCodeInstruction ParseTag(string tag) 54 | { 55 | string trimTag = tag.Trim('[', ']'); 56 | bool exiting = trimTag.StartsWith("/"); 57 | int startPos = exiting ? 1 : 0; 58 | string tagObject = ""; 59 | 60 | for (int c = startPos; c < trimTag.Length; c++) 61 | { 62 | char chr = trimTag[c]; 63 | switch (chr) 64 | { 65 | case '=': 66 | case ' ': 67 | startPos = c + 1; 68 | goto END; 69 | default: 70 | tagObject += chr; 71 | break; 72 | } 73 | } 74 | 75 | END: ; 76 | 77 | switch (tagObject.ToLower()) 78 | { 79 | case "b": 80 | return new BBCodeInstruction(InstructionType.Bold, exiting); 81 | case "i": 82 | return new BBCodeInstruction(InstructionType.Italic, exiting); 83 | case "u": 84 | return new BBCodeInstruction(InstructionType.Underline, exiting); 85 | case "color": 86 | if (exiting) 87 | return new ColorInstruction(Color.Transparent, true); 88 | string colorText = trimTag[startPos..]; 89 | return new ColorInstruction(Color.FromString(colorText), false); 90 | } 91 | 92 | return new TextInstruction(tag); 93 | } 94 | } -------------------------------------------------------------------------------- /Easel/GUI/BBCode/ColorInstruction.cs: -------------------------------------------------------------------------------- 1 | using Easel.Math; 2 | 3 | namespace Easel.GUI.BBCode; 4 | 5 | public class ColorInstruction : BBCodeInstruction 6 | { 7 | public Color Color; 8 | 9 | public ColorInstruction(Color color, bool isExiting) : base(InstructionType.Color, isExiting) 10 | { 11 | Color = color; 12 | } 13 | 14 | public override string ToString() 15 | { 16 | return "COLOR" + (IsExiting ? " EXITING: " : ": ") + Color; 17 | } 18 | } -------------------------------------------------------------------------------- /Easel/GUI/BBCode/InstructionType.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.GUI.BBCode; 2 | 3 | public enum InstructionType 4 | { 5 | Text, 6 | Bold, 7 | Italic, 8 | Underline, 9 | Color 10 | } -------------------------------------------------------------------------------- /Easel/GUI/BBCode/TextInstruction.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.GUI.BBCode; 2 | 3 | public class TextInstruction : BBCodeInstruction 4 | { 5 | public string Text; 6 | 7 | public TextInstruction(string text) : base(InstructionType.Text, false) 8 | { 9 | Text = text; 10 | } 11 | 12 | public override string ToString() 13 | { 14 | return $"TEXT: {Text}"; 15 | } 16 | } -------------------------------------------------------------------------------- /Easel/GUI/Charmap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Easel.Graphics; 5 | using Easel.Math; 6 | 7 | namespace Easel.GUI; 8 | 9 | public class Charmap : IDisposable 10 | { 11 | public readonly Texture2D Texture; 12 | 13 | private readonly Dictionary _characters; 14 | 15 | public Charmap(Texture2D texture, Dictionary characters) 16 | { 17 | Texture = texture; 18 | _characters = characters; 19 | } 20 | 21 | public Character GetCharacter(char c) 22 | { 23 | if (!_characters.TryGetValue(c, out Character character)) 24 | character = _characters['?']; 25 | return character; 26 | } 27 | 28 | public struct Character 29 | { 30 | public Rectangle Source; 31 | public Vector2T Bearing; 32 | public int Advance; 33 | } 34 | 35 | public void Dispose() 36 | { 37 | Texture.Dispose(); 38 | } 39 | } -------------------------------------------------------------------------------- /Easel/GUI/FontHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Easel.Core; 4 | using Easel.Graphics; 5 | using Easel.Math; 6 | using Pie.Text; 7 | 8 | namespace Easel.GUI; 9 | 10 | public static class FontHelper 11 | { 12 | public static readonly FreeType FreeType; 13 | 14 | public static Charmap GenerateCharmapInRange(Face face, char rangeMin, char rangeMax, bool forcePowerOf2 = true) 15 | { 16 | // Padding reduces the risk of "bleed" by adding a gap between characters; 17 | const int padding = 2; 18 | 19 | int size = 0; 20 | int totalSize = (rangeMax - rangeMin) * (face.Size + padding) * (face.Size + padding); 21 | size = (int) MathF.Sqrt(totalSize); 22 | if (forcePowerOf2) 23 | { 24 | size--; 25 | size |= size >> 1; 26 | size |= size >> 2; 27 | size |= size >> 4; 28 | size |= size >> 8; 29 | size |= size >> 16; 30 | size++; 31 | } 32 | 33 | Logger.Debug($"Font texture size calculated to be {size}x{size} pixels."); 34 | int width = size; 35 | int height = size; 36 | byte[] initialData = new byte[width * height * 4]; 37 | Texture2D texture = new Texture2D(width, height, initialData, autoDispose: false); 38 | Dictionary characters = new Dictionary(); 39 | Vector2T pos = Vector2T.Zero; 40 | for (char c = rangeMin; c < rangeMax; c++) 41 | { 42 | AddCharacter(face, c, ref pos, ref texture, ref characters, padding); 43 | } 44 | 45 | if (!characters.ContainsKey('?')) 46 | { 47 | AddCharacter(face, '?', ref pos, ref texture, ref characters, padding); 48 | } 49 | 50 | return new Charmap(texture, characters); 51 | } 52 | 53 | private static void AddCharacter(Face face, char c, ref Vector2T pos, ref Texture2D texture, ref Dictionary characters, int padding) 54 | { 55 | Character chr = face.Characters[c]; 56 | if (pos.X + chr.Width >= texture.Size.Width) 57 | { 58 | pos.X = 0; 59 | pos.Y += face.Size + padding; 60 | if (pos.Y + face.Size >= texture.Size.Height) 61 | throw new EaselException("Font texture is too small to fit all characters in range."); 62 | } 63 | texture.SetData(pos.X, pos.Y, chr.Width, chr.Height, chr.Bitmap); 64 | characters.Add(c, new Charmap.Character() 65 | { 66 | Source = new Rectangle(pos.X, pos.Y, chr.Width, chr.Height), 67 | Bearing = new Vector2T(chr.BitmapLeft, chr.BitmapTop), 68 | Advance = chr.Advance 69 | }); 70 | pos.X += chr.Advance + padding; 71 | } 72 | 73 | static FontHelper() 74 | { 75 | FreeType = new FreeType(); 76 | } 77 | } -------------------------------------------------------------------------------- /Easel/GUI/FontOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.GUI; 2 | 3 | public struct FontOptions 4 | { 5 | public bool IsAntialiased; 6 | 7 | public FontOptions() 8 | { 9 | IsAntialiased = true; 10 | } 11 | } -------------------------------------------------------------------------------- /Easel/GUI/Justification.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.GUI; 2 | 3 | public enum Justification 4 | { 5 | Left, 6 | Center, 7 | Right 8 | } -------------------------------------------------------------------------------- /Easel/GUI/Panel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Easel.Graphics.Renderers; 4 | using Easel.Math; 5 | 6 | namespace Easel.GUI; 7 | 8 | public class Panel : UIElement 9 | { 10 | public Panel(string name, Position position, Size size) : base(name, position, size) { } 11 | 12 | protected internal override void Draw(SpriteRenderer renderer) 13 | { 14 | Color bg = Style.Panel.BackgroundColor; 15 | Color border = Style.Panel.BorderColor; 16 | float radius = Style.Panel.BorderRadius; 17 | int width = Style.Panel.BorderWidth; 18 | 19 | float scale = UI.Scale; 20 | Size size = new Size((int) (Size.Width * scale), (int) (Size.Height * scale)); 21 | 22 | /*if (Style.BackgroundTexture != null) 23 | { 24 | renderer.Draw(Style.BackgroundTexture, (Vector2) CalculatedScreenPos, 25 | new Rectangle(CalculatedScreenPos, size), Color.White, 0, Vector2.Zero, 26 | Vector2.One); 27 | }*/ 28 | 29 | renderer.DrawRectangle((Vector2) CalculatedScreenPos, size.As(), width, radius, bg, border, 0, 30 | Vector2.Zero); 31 | } 32 | } -------------------------------------------------------------------------------- /Easel/GUI/Position.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Easel.Math; 4 | 5 | namespace Easel.GUI; 6 | 7 | public struct Position 8 | { 9 | /// 10 | /// The anchor point of the element. 11 | /// 12 | public Anchor Anchor; 13 | 14 | /// 15 | /// The offset from the anchor point of the element. 16 | /// 17 | public Vector2T Offset; 18 | 19 | public Position(Anchor anchor, Vector2T offset) 20 | { 21 | Anchor = anchor; 22 | Offset = offset; 23 | } 24 | 25 | public Position(Anchor anchor) : this(anchor, Vector2T.Zero) { } 26 | 27 | public Position(Vector2T offset) : this(Anchor.TopLeft, offset) { } 28 | 29 | /// 30 | /// Calculate the screen position of the UI element. 31 | /// 32 | /// The bounds that the element will abide by. 33 | /// The size in pixels of this UI element. 34 | /// The calculated position. 35 | public readonly Vector2T CalculatePosition(Rectangle viewport, Size elementSize) 36 | { 37 | float scale = UI.Scale; 38 | 39 | Vector2T pos = Vector2T.Zero; 40 | 41 | elementSize = new Size((int) (elementSize.Width * scale), (int) (elementSize.Height * scale)); 42 | 43 | switch (Anchor) 44 | { 45 | case Anchor.TopLeft: 46 | break; 47 | case Anchor.TopCenter: 48 | pos.X = (viewport.Width / 2) - (elementSize.Width / 2); 49 | break; 50 | case Anchor.TopRight: 51 | pos.X = viewport.Width - elementSize.Width; 52 | break; 53 | case Anchor.CenterLeft: 54 | pos.Y = (viewport.Height / 2) - (elementSize.Height / 2); 55 | break; 56 | case Anchor.CenterCenter: 57 | pos.X = (viewport.Width / 2) - (elementSize.Width / 2); 58 | pos.Y = (viewport.Height / 2) - (elementSize.Height / 2); 59 | break; 60 | case Anchor.CenterRight: 61 | pos.X = viewport.Width - elementSize.Width; 62 | pos.Y = (viewport.Height / 2) - (elementSize.Height / 2); 63 | break; 64 | case Anchor.BottomLeft: 65 | pos.Y = viewport.Height - elementSize.Height; 66 | break; 67 | case Anchor.BottomCenter: 68 | pos.X = (viewport.Width / 2) - (elementSize.Width / 2); 69 | pos.Y = viewport.Height - elementSize.Height; 70 | break; 71 | case Anchor.BottomRight: 72 | pos.X = viewport.Width - elementSize.Width; 73 | pos.Y = viewport.Height - elementSize.Height; 74 | break; 75 | default: 76 | throw new ArgumentOutOfRangeException(); 77 | } 78 | 79 | pos += (Offset.As() * scale).As(); 80 | 81 | return pos; 82 | } 83 | } -------------------------------------------------------------------------------- /Easel/GUI/Style.cs: -------------------------------------------------------------------------------- 1 | using Easel.Graphics; 2 | using Easel.Math; 3 | 4 | namespace Easel.GUI; 5 | 6 | public struct Style 7 | { 8 | public Color TextColor; 9 | 10 | public PanelStyle Panel; 11 | 12 | public Texture BackgroundTexture; 13 | 14 | public Font Font; 15 | 16 | public Style() 17 | { 18 | TextColor = Color.Black; 19 | Panel = new PanelStyle(); 20 | 21 | Font = null; 22 | } 23 | 24 | public struct PanelStyle 25 | { 26 | public Color BackgroundColor; 27 | 28 | public Color BorderColor; 29 | 30 | public int BorderWidth; 31 | 32 | public float BorderRadius; 33 | 34 | public PanelStyle() 35 | { 36 | BackgroundColor = Color.White; 37 | BorderColor = Color.Black; 38 | BorderWidth = 2; 39 | BorderRadius = 20f; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Easel/GUI/UI.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Easel.Graphics.Renderers; 3 | using Easel.Math; 4 | 5 | namespace Easel.GUI; 6 | 7 | public static class UI 8 | { 9 | private static Dictionary _elements; 10 | 11 | private static List _elementsList; 12 | 13 | private static Size? _targetSize; 14 | private static float _scaleMultiplier; 15 | private static float _calculatedScale; 16 | 17 | public static Style DefaultStyle; 18 | 19 | public static Size? TargetSize 20 | { 21 | get => _targetSize; 22 | set 23 | { 24 | _targetSize = value; 25 | CalculateScale(); 26 | } 27 | } 28 | 29 | public static float Scale 30 | { 31 | get 32 | { 33 | CalculateScale(); 34 | return _calculatedScale * _scaleMultiplier; 35 | } 36 | set => _scaleMultiplier = value; 37 | } 38 | 39 | static UI() 40 | { 41 | DefaultStyle = new Style(); 42 | TargetSize = null; 43 | 44 | _elements = new Dictionary(); 45 | _elementsList = new List(); 46 | 47 | _scaleMultiplier = 1.0f; 48 | } 49 | 50 | public static void Clear() 51 | { 52 | _elements.Clear(); 53 | _elementsList.Clear(); 54 | } 55 | 56 | public static void Add(UIElement element) 57 | { 58 | _elements.Add(element.Name, element); 59 | _elementsList.Add(element); 60 | } 61 | 62 | internal static void Update() 63 | { 64 | Rectangle viewport = 65 | new Rectangle(Vector2T.Zero, EaselGame.Instance.GraphicsInternal.MainTarget.Size); 66 | 67 | bool mouseCaptured = false; 68 | for (int i = _elementsList.Count - 1; i >= 0; i--) 69 | _elementsList[i].Update(viewport, ref mouseCaptured); 70 | } 71 | 72 | internal static void Draw(SpriteRenderer renderer) 73 | { 74 | renderer.Begin(); 75 | 76 | for (int i = 0; i < _elements.Count; i++) 77 | _elementsList[i].Draw(renderer); 78 | 79 | renderer.End(); 80 | } 81 | 82 | private static void CalculateScale() 83 | { 84 | if (_targetSize == null) 85 | { 86 | _calculatedScale = 1f; 87 | return; 88 | } 89 | 90 | Size size = EaselGame.Instance.GraphicsInternal.MainTarget.Size; 91 | _calculatedScale = size.Height / (float) _targetSize.Value.Height; 92 | } 93 | } -------------------------------------------------------------------------------- /Easel/GUI/UIElement.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Easel.Graphics.Renderers; 3 | using Easel.Math; 4 | using Pie.Windowing; 5 | 6 | namespace Easel.GUI; 7 | 8 | public abstract class UIElement 9 | { 10 | public event OnClick Click; 11 | 12 | public string Name; 13 | 14 | public Position Position; 15 | 16 | public Size Size; 17 | 18 | public Style Style; 19 | 20 | public bool MouseTransparent; 21 | 22 | protected Vector2T CalculatedScreenPos; 23 | 24 | protected bool IsHovered; 25 | 26 | protected bool IsClicked; 27 | 28 | public UIElement(string name, Position position, Size size) 29 | { 30 | Name = name; 31 | Position = position; 32 | Size = size; 33 | Style = UI.DefaultStyle; 34 | } 35 | 36 | protected internal virtual void Update(Rectangle viewport, ref bool mouseCaptured) 37 | { 38 | CalculatedScreenPos = Position.CalculatePosition(viewport, Size); 39 | 40 | Vector2 mousePosition = Input.MousePosition; 41 | 42 | if (!mouseCaptured && 43 | mousePosition.X >= CalculatedScreenPos.X && mousePosition.X < CalculatedScreenPos.X + Size.Width && 44 | mousePosition.Y >= CalculatedScreenPos.Y && mousePosition.Y < CalculatedScreenPos.Y + Size.Height) 45 | { 46 | mouseCaptured = true; 47 | 48 | IsHovered = true; 49 | 50 | if (Input.MouseButtonDown(MouseButton.Left)) 51 | IsClicked = true; 52 | else if (IsClicked) 53 | { 54 | IsClicked = false; 55 | Click?.Invoke(this); 56 | } 57 | } 58 | else 59 | { 60 | IsClicked = false; 61 | IsHovered = false; 62 | } 63 | } 64 | 65 | protected internal abstract void Draw(SpriteRenderer renderer); 66 | 67 | public delegate void OnClick(UIElement element); 68 | } -------------------------------------------------------------------------------- /Easel/Interfaces/InheritableEntity.cs: -------------------------------------------------------------------------------- 1 | using Easel.Entities; 2 | using Easel.Entities.Components; 3 | using Easel.Physics; 4 | using Easel.Scenes; 5 | using Easel.Audio; 6 | using Easel.Content; 7 | using Easel.Graphics; 8 | 9 | namespace Easel.Interfaces; 10 | 11 | // Not actually an interface but the only way to have PROTECTED METHODS ARGH 12 | /// 13 | /// Provides helpful functions for entities and components that can be inherited from. 14 | /// 15 | public abstract class InheritableEntity 16 | { 17 | /// 18 | /// The current instance. 19 | /// 20 | protected abstract EaselGame Game { get; } 21 | 22 | protected abstract Simulation Simulation { get; } 23 | 24 | /// 25 | /// The current instance. 26 | /// 27 | protected abstract EaselGraphics Graphics { get; } 28 | 29 | /// 30 | /// The current instance. 31 | /// 32 | protected abstract EaselAudio Audio { get; } 33 | 34 | protected abstract ContentManager Content { get; } 35 | 36 | /// 37 | /// The currently active . 38 | /// 39 | protected abstract Scene ActiveScene { get; } 40 | 41 | /// 42 | /// Add an entity to the current scene. 43 | /// 44 | /// The entity to add. 45 | protected abstract void AddEntity(Entity entity); 46 | 47 | /// 48 | /// Remove an entity from the scene. 49 | /// 50 | /// The name of the entity. 51 | protected abstract void RemoveEntity(string name); 52 | 53 | /// 54 | /// Remove an entity from the scene. 55 | /// 56 | /// The entity reference. 57 | protected abstract void RemoveEntity(Entity entity); 58 | 59 | /// 60 | /// Get the entity reference with the given name. 61 | /// 62 | /// The name of the entity. 63 | /// The entity with the given name. If not found, returns null. 64 | protected abstract Entity GetEntity(string name); 65 | 66 | /// 67 | /// Get the entity with the given name. 68 | /// 69 | /// The name of the entity. 70 | /// The entity type, for example . 71 | /// The entity with the given name. If not found, returns null. 72 | protected abstract T GetEntity(string name) where T : Entity; 73 | 74 | /// 75 | /// Get all entities in the current scene with the given tag. 76 | /// 77 | /// The tag to search for. 78 | /// All entities with the given tag. 79 | protected abstract Entity[] GetEntitiesWithTag(string tag); 80 | 81 | protected abstract Entity[] GetEntitiesWithComponent() where T : Component; 82 | 83 | /// 84 | /// Get all entities in the current scene. 85 | /// 86 | /// The entity array. 87 | protected abstract Entity[] GetAllEntities(); 88 | } -------------------------------------------------------------------------------- /Easel/Metrics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Pie; 3 | 4 | namespace Easel; 5 | 6 | public static class Metrics 7 | { 8 | internal static event OnUpdate MetricsUpdate; 9 | 10 | private static ulong _totalFrames; 11 | private static float _secondsCounter; 12 | private static int _framesInSecond; 13 | private static int _fps; 14 | 15 | public static ulong TotalFrames => _totalFrames; 16 | 17 | // hec u rider 18 | // ReSharper disable once InconsistentNaming 19 | public static int FPS => _fps; 20 | 21 | public static string GetString() 22 | { 23 | return $"FPS: {FPS} (dt: {MathF.Round((Time.DeltaTime) * 1000, 1)}ms)\nFrame: {TotalFrames}\nTotal VBuffers: {PieMetrics.VertexBufferCount}\nTotal IBuffers: {PieMetrics.IndexBufferCount}\nTotal CBuffers: {PieMetrics.UniformBufferCount}\nDraws: {PieMetrics.DrawCalls}\nTris: {PieMetrics.TriCount}\nBackend: {EaselGame.Instance.GraphicsInternal.PieGraphics.Api.ToFriendlyString()}\nPie {PieMetrics.Version}\nEasel {EaselGame.Version}"; 24 | } 25 | 26 | internal static void Update() 27 | { 28 | _totalFrames++; 29 | _secondsCounter += Time.DeltaTime; 30 | _framesInSecond++; 31 | if (_secondsCounter >= 1) 32 | { 33 | _secondsCounter = 0; 34 | _fps = _framesInSecond; 35 | _framesInSecond = 0; 36 | MetricsUpdate?.Invoke(); 37 | } 38 | } 39 | 40 | internal delegate void OnUpdate(); 41 | } -------------------------------------------------------------------------------- /Easel/Physics/Internal/BroadPhaseLayerInterfaceImpl.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using JoltPhysicsSharp; 3 | 4 | namespace Easel.Physics.Internal; 5 | 6 | internal class BroadPhaseLayerInterfaceImpl : BroadPhaseLayerInterface 7 | { 8 | private BroadPhaseLayer[] _objectToBroadPhaseLayers; 9 | 10 | public BroadPhaseLayerInterfaceImpl() 11 | { 12 | _objectToBroadPhaseLayers = new BroadPhaseLayer[Layers.NumLayers]; 13 | _objectToBroadPhaseLayers[Layers.NonMoving] = BroadPhaseLayers.NonMoving; 14 | _objectToBroadPhaseLayers[Layers.Moving] = BroadPhaseLayers.Moving; 15 | } 16 | 17 | protected override int GetNumBroadPhaseLayers() 18 | { 19 | return BroadPhaseLayers.NumLayers; 20 | } 21 | 22 | protected override BroadPhaseLayer GetBroadPhaseLayer(ObjectLayer layer) 23 | { 24 | Debug.Assert(layer < Layers.NumLayers); 25 | return _objectToBroadPhaseLayers[layer]; 26 | } 27 | 28 | protected override string GetBroadPhaseLayerName(BroadPhaseLayer layer) 29 | { 30 | return string.Empty; 31 | } 32 | } -------------------------------------------------------------------------------- /Easel/Physics/Internal/Layers.cs: -------------------------------------------------------------------------------- 1 | using JoltPhysicsSharp; 2 | 3 | namespace Easel.Physics.Internal; 4 | 5 | internal static class Layers 6 | { 7 | public const int NonMoving = 0; 8 | public const int Moving = 1; 9 | public const int NumLayers = 2; 10 | } 11 | 12 | internal static class BroadPhaseLayers 13 | { 14 | public static BroadPhaseLayer NonMoving = new BroadPhaseLayer(0); 15 | public static BroadPhaseLayer Moving = new BroadPhaseLayer(1); 16 | public static int NumLayers = 2; 17 | } -------------------------------------------------------------------------------- /Easel/Physics/Internal/ObjectLayerPairFilterImpl.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using JoltPhysicsSharp; 3 | 4 | namespace Easel.Physics.Internal; 5 | 6 | public class ObjectLayerPairFilterImpl : ObjectLayerPairFilter 7 | { 8 | protected override bool ShouldCollide(ObjectLayer object1, ObjectLayer object2) 9 | { 10 | switch (object1) 11 | { 12 | case Layers.NonMoving: 13 | return object2 == Layers.Moving; 14 | case Layers.Moving: 15 | return true; 16 | default: 17 | Debug.Assert(false); 18 | return false; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Easel/Physics/Internal/ObjectVsBroadPhaseLayerFilterImpl.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using JoltPhysicsSharp; 3 | 4 | namespace Easel.Physics.Internal; 5 | 6 | internal class ObjectVsBroadPhaseLayerFilterImpl : ObjectVsBroadPhaseLayerFilter 7 | { 8 | protected override bool ShouldCollide(ObjectLayer layer1, BroadPhaseLayer layer2) 9 | { 10 | switch (layer1) 11 | { 12 | case Layers.NonMoving: 13 | return layer2 == BroadPhaseLayers.Moving; 14 | case Layers.Moving: 15 | return true; 16 | default: 17 | Debug.Assert(false); 18 | return false; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Easel/Physics/PhysicsInitSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace Easel.Physics; 4 | 5 | public struct PhysicsInitSettings 6 | { 7 | public Vector3 Gravity; 8 | 9 | public uint MaxPhysicsObjects; 10 | 11 | public PhysicsInitSettings() 12 | { 13 | Gravity = new Vector3(0, -9.81f, 0); 14 | MaxPhysicsObjects = 65536; 15 | } 16 | 17 | public PhysicsInitSettings(Vector3 gravity, uint maxPhysicsObjects) 18 | { 19 | Gravity = gravity; 20 | MaxPhysicsObjects = maxPhysicsObjects; 21 | } 22 | } -------------------------------------------------------------------------------- /Easel/Physics/Shapes/BoxShape.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using JoltPhysicsSharp; 3 | 4 | namespace Easel.Physics.Shapes; 5 | 6 | public struct BoxShape : IShape 7 | { 8 | public Vector3 HalfExtents; 9 | 10 | public BoxShape(Vector3 halfExtents) 11 | { 12 | HalfExtents = halfExtents; 13 | } 14 | 15 | public ShapeSettings ShapeSettings => new BoxShapeSettings(HalfExtents); 16 | } -------------------------------------------------------------------------------- /Easel/Physics/Shapes/IShape.cs: -------------------------------------------------------------------------------- 1 | using JoltPhysicsSharp; 2 | 3 | namespace Easel.Physics.Shapes; 4 | 5 | public interface IShape 6 | { 7 | internal ShapeSettings ShapeSettings { get; } 8 | } -------------------------------------------------------------------------------- /Easel/Physics/Simulation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Easel.Physics.Internal; 4 | using JoltPhysicsSharp; 5 | 6 | namespace Easel.Physics; 7 | 8 | public class Simulation 9 | { 10 | public PhysicsSystem PhysicsSystem; 11 | public BodyInterface BodyInterface => PhysicsSystem.BodyInterface; 12 | 13 | private TempAllocator _allocator; 14 | private JobSystemThreadPool _jobSystem; 15 | 16 | private BroadPhaseLayerInterfaceImpl _broadPhaseInterface; 17 | private ObjectLayerPairFilterImpl _objectLayerPair; 18 | private ObjectVsBroadPhaseLayerFilterImpl _objectVsBroadPhase; 19 | 20 | public Simulation(PhysicsInitSettings initSettings) 21 | { 22 | Foundation.Init(); 23 | 24 | _allocator = new TempAllocator(10 * 1024 * 1024); 25 | 26 | _jobSystem = new JobSystemThreadPool(Foundation.MaxPhysicsJobs, Foundation.MaxPhysicsBarriers, 27 | Environment.ProcessorCount); 28 | 29 | _broadPhaseInterface = new BroadPhaseLayerInterfaceImpl(); 30 | _objectLayerPair = new ObjectLayerPairFilterImpl(); 31 | _objectVsBroadPhase = new ObjectVsBroadPhaseLayerFilterImpl(); 32 | 33 | PhysicsSystem = new PhysicsSystem(); 34 | PhysicsSystem.Init(initSettings.MaxPhysicsObjects, 0, initSettings.MaxPhysicsObjects, 1024, 35 | _broadPhaseInterface, _objectVsBroadPhase, _objectLayerPair); 36 | PhysicsSystem.Gravity = initSettings.Gravity; 37 | } 38 | 39 | public void Timestep(float deltaTime) 40 | { 41 | PhysicsSystem.Update(deltaTime, 1, 1, _allocator, _jobSystem); 42 | } 43 | } -------------------------------------------------------------------------------- /Easel/Physics/Structs/BodyType.cs: -------------------------------------------------------------------------------- 1 | namespace Easel.Physics.Structs; 2 | 3 | public enum BodyType 4 | { 5 | Static, 6 | Kinematic, 7 | Dynamic 8 | } -------------------------------------------------------------------------------- /Easel/Physics/Structs/RigidbodyInitSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace Easel.Physics.Structs; 4 | 5 | public struct RigidbodyInitSettings 6 | { 7 | public BodyType BodyType; 8 | 9 | public float Restitution; 10 | 11 | public Vector3 LinearVelocity; 12 | 13 | public Vector3 AngularVelocity; 14 | 15 | public RigidbodyInitSettings() 16 | { 17 | BodyType = BodyType.Dynamic; 18 | Restitution = 0; 19 | LinearVelocity = Vector3.Zero; 20 | AngularVelocity = Vector3.Zero; 21 | } 22 | 23 | public RigidbodyInitSettings(BodyType bodyType, float restitution, Vector3 linearVelocity, Vector3 angularVelocity) 24 | { 25 | BodyType = bodyType; 26 | Restitution = restitution; 27 | LinearVelocity = linearVelocity; 28 | AngularVelocity = angularVelocity; 29 | } 30 | } -------------------------------------------------------------------------------- /Easel/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel/Roboto-Regular.ttf -------------------------------------------------------------------------------- /Easel/Scenes/SceneManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Easel.Core; 3 | using Easel.GUI; 4 | 5 | namespace Easel.Scenes; 6 | 7 | public static class SceneManager 8 | { 9 | private static Scene _activeScene; 10 | private static Scene _switchScene; 11 | 12 | public static Scene ActiveScene => _activeScene; 13 | 14 | internal static void InitializeScene(Scene scene) 15 | { 16 | _activeScene = scene; 17 | } 18 | 19 | internal static void Initialize() 20 | { 21 | Logger.Debug("Initializing SceneManager..."); 22 | if (_activeScene == null) 23 | Logger.Info("Scene was null, will not use a scene by default."); 24 | else 25 | _activeScene.Initialize(); 26 | _switchScene = null; 27 | } 28 | 29 | internal static void Update() 30 | { 31 | if (_switchScene != null) 32 | { 33 | _activeScene?.Dispose(); 34 | _activeScene = null; 35 | GC.Collect(); 36 | UI.Clear(); 37 | _activeScene = _switchScene; 38 | _activeScene.Initialize(); 39 | _switchScene = null; 40 | } 41 | 42 | _activeScene?.Update(); 43 | } 44 | 45 | internal static void AfterUpdate() 46 | { 47 | _activeScene?.AfterUpdate(); 48 | } 49 | 50 | internal static void FixedUpdate() 51 | { 52 | _activeScene?.FixedUpdate(); 53 | } 54 | 55 | internal static void Draw() 56 | { 57 | if (_activeScene != null) 58 | { 59 | _activeScene.Draw(); 60 | } 61 | } 62 | 63 | /// 64 | /// Set the scene that will be transitioned to. 65 | /// 66 | /// The scene to use. 67 | /// Transitioning occurs at the start of the update cycle. 68 | public static void SetScene(Scene scene) 69 | { 70 | _switchScene = scene; 71 | } 72 | } -------------------------------------------------------------------------------- /Easel/SystemInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.Win32; 4 | 5 | namespace Easel; 6 | 7 | public static class SystemInfo 8 | { 9 | public static string CpuInfo 10 | { 11 | get 12 | { 13 | if (OperatingSystem.IsWindows()) 14 | { 15 | return Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0") 16 | ?.GetValue("ProcessorNameString")?.ToString() ?? 17 | "Unknown (expected value was not found in the registry)"; 18 | } 19 | 20 | if (OperatingSystem.IsLinux()) 21 | { 22 | string[] lines = File.ReadAllLines("/proc/cpuinfo"); 23 | string modelName = "Unknown (cpuinfo doesn't contain expected result..)"; 24 | foreach (string line in lines) 25 | { 26 | if (line.Trim().ToLower().StartsWith("model name")) 27 | { 28 | modelName = line.Split(':')[1].Trim(); 29 | break; 30 | } 31 | } 32 | 33 | return modelName; 34 | } 35 | 36 | return "Unknown (unsupported OS)."; 37 | } 38 | } 39 | 40 | public static string MemoryInfo 41 | { 42 | get 43 | { 44 | if (OperatingSystem.IsWindows()) 45 | return "(not implemented for windows)"; 46 | if (OperatingSystem.IsLinux()) 47 | { 48 | string[] lines = File.ReadAllLines("/proc/meminfo"); 49 | string total = "Unknown (meminfo doesn't contain expected result..)"; 50 | string free = "Unknown (meminfo doesn't contain expected result..)"; 51 | foreach (string line in lines) 52 | { 53 | string tLine = line.Trim().ToLower(); 54 | if (tLine.StartsWith("memtotal")) 55 | total = line.Split(':')[1].Trim(); 56 | else if (tLine.StartsWith("memavailable")) 57 | free = line.Split(':')[1].Trim(); 58 | } 59 | 60 | return "Total: " + total + ", Available: "+ free; 61 | } 62 | 63 | return "Unknown (unsupported OS)."; 64 | } 65 | } 66 | 67 | public static int LogicalThreads => Environment.ProcessorCount; 68 | } -------------------------------------------------------------------------------- /Easel/Time.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Easel; 4 | 5 | /// 6 | /// Provides engine time functions. 7 | /// 8 | public static class Time 9 | { 10 | private static double _deltaTime; 11 | 12 | internal static Stopwatch InternalStopwatch; 13 | private static Stopwatch _timerWatch; 14 | 15 | /// 16 | /// Get the amount of time passed since the previous frame. Use to create framerate independent actions. 17 | /// 18 | public static float DeltaTime => (float) _deltaTime; 19 | 20 | public static double DeltaTimeD => _deltaTime; 21 | 22 | public static float TotalSeconds => (float) _timerWatch.Elapsed.TotalSeconds; 23 | 24 | public static double TotalSecondsD => _timerWatch.Elapsed.TotalSeconds; 25 | 26 | internal static void Initialize() 27 | { 28 | InternalStopwatch = Stopwatch.StartNew(); 29 | _timerWatch = Stopwatch.StartNew(); 30 | } 31 | 32 | internal static void Update() 33 | { 34 | _deltaTime = (float) InternalStopwatch.Elapsed.TotalSeconds; 35 | InternalStopwatch.Restart(); 36 | 37 | //double time = view.Time; 38 | //double time = _stopwatch.Elapsed.TotalSeconds; 39 | //_deltaTime = time - _lastTime; 40 | //_lastTime = time; 41 | } 42 | } -------------------------------------------------------------------------------- /Easel/TitleBarFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Easel; 4 | 5 | [Flags] 6 | public enum TitleBarFlags 7 | { 8 | None = 1 << 0, 9 | ShowEasel = 1 << 1, 10 | ShowFps = 1 << 2, 11 | ShowGraphicsApi = 1 << 3 12 | } -------------------------------------------------------------------------------- /Easel/joltc.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel/joltc.dll -------------------------------------------------------------------------------- /Easel/libjoltc.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piegfx/Easel/ac64ca6e3f8ca57257f0613dfc58b6d797db92c5/Easel/libjoltc.so -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easel 2 | 3 | ## Notice of archival 4 | Hi everyone, effective from 2023-06-24, Easel is now archived. We are moving our efforts to a new closed-source in-house engine to power the likes of SpaceBox and beyond. We hope to maybe release the engine someday, but for the forseeable it will remain closed source. Thanks for understanding! 5 | 6 | Want to discuss more? [Join the discord server.](https://discord.gg/ygUpYkUstz) 7 | -------------------------------------------------------------------------------- /Update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | git submodule update --remote --merge --------------------------------------------------------------------------------