diff --git a/Core/Audio/Audio.cs b/Core/Audio/Audio.cs
new file mode 100644
index 0000000..7d9d171
--- /dev/null
+++ b/Core/Audio/Audio.cs
@@ -0,0 +1,28 @@
+using System;
+using System.IO;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Handles Audio related operations.
+///
+public static class Audio
+{
+ ///
+ /// Plays an audio file if it is in the WAV format.
+ ///
+ /// Specifies the location of the audio file to be played.
+ /// Thrown when the file format is not supported, such as when it is not a WAV file.
+ public static void Play(string filePath)
+ {
+ switch (Path.GetExtension(filePath))
+ {
+ case ".wav":
+ new WavPlayer().Play(filePath);
+ break;
+
+ default:
+ throw new NotSupportedException("Unsupported file format.");
+ }
+ }
+}
diff --git a/Core/Audio/AudioBuffer.cs b/Core/Audio/AudioBuffer.cs
new file mode 100644
index 0000000..506f426
--- /dev/null
+++ b/Core/Audio/AudioBuffer.cs
@@ -0,0 +1,43 @@
+using Silk.NET.OpenAL;
+using System;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Represents an audio buffer with a unique identifier.
+///
+public class AudioBuffer
+{
+ /// Gets the identifier to the audio buffer.
+ public uint BufferId => _buffer;
+
+ private readonly uint _buffer;
+ private readonly AL _al;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The OpenAL API context.
+ public AudioBuffer(AL al)
+ {
+ _al = al;
+ _buffer = _al.GenBuffer();
+ }
+
+ ///
+ /// Loads the given into the buffer.
+ ///
+ /// The data to be loaded into the buffer.
+ /// Details about the data.
+ public void LoadData(ReadOnlySpan data, WavData wavData)
+ {
+ unsafe
+ {
+ fixed (byte* pData = data)
+ {
+ _al.BufferData(_buffer, wavData.Format, pData, data.Length, wavData.SampleRate);
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Core/Audio/AudioDevice.cs b/Core/Audio/AudioDevice.cs
new file mode 100644
index 0000000..aa0560d
--- /dev/null
+++ b/Core/Audio/AudioDevice.cs
@@ -0,0 +1,46 @@
+using Silk.NET.OpenAL;
+using System;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Manages an audio device and its context for audio operations.
+///
+public unsafe class AudioDevice : IDisposable
+{
+ /// Gets the OpenAL context.
+ public AL Al { get; }
+
+ private readonly ALContext _alc;
+ private readonly Device* _device;
+ private readonly Context* _context;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// Thrown when unable to create the audio device.
+ public AudioDevice()
+ {
+ _alc = ALContext.GetApi();
+ Al = AL.GetApi();
+ _device = _alc.OpenDevice("");
+
+ if (_device == null)
+ throw new InvalidOperationException("Could not create device.");
+
+ _context = _alc.CreateContext(_device, null);
+ _alc.MakeContextCurrent(_context);
+ Al.GetError();
+ }
+
+ ///
+ public void Dispose()
+ {
+ _alc.DestroyContext(_context);
+ _alc.CloseDevice(_device);
+ Al.Dispose();
+ _alc.Dispose();
+
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/Core/Audio/AudioFileExtensions.cs b/Core/Audio/AudioFileExtensions.cs
new file mode 100644
index 0000000..93160e2
--- /dev/null
+++ b/Core/Audio/AudioFileExtensions.cs
@@ -0,0 +1,16 @@
+namespace SharpEngine.Core.Audio;
+
+///
+/// Contains all the supported audio file extensions.
+///
+public static class AudioFileExtensions
+{
+ /// The file extension of the Waveform audio file.
+ public const string WavExtension = ".wav";
+
+ /// The file extension of the MPEG-1 Audio Layer III or MPEG-2 Audio Layer III (MP3) file.
+ public const string Mp3Extension = ".mp3";
+
+ /// The file extension of the MPEG-4 (MP4) file.
+ public const string Mp4Extension = ".mp4";
+}
diff --git a/Core/Audio/AudioPlayerBase.cs b/Core/Audio/AudioPlayerBase.cs
new file mode 100644
index 0000000..5778672
--- /dev/null
+++ b/Core/Audio/AudioPlayerBase.cs
@@ -0,0 +1,67 @@
+using SharpEngine.Core.Scenes;
+
+using System;
+using System.IO;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Represents a base class for all audio players.
+///
+public abstract class AudioPlayerBase : SceneNode
+{
+ /// Gets the extension accepted by the audio player.
+ protected abstract string FileExtension { get; }
+
+ /// Gets or sets the properties for the player.
+ public AudioProperties AudioProperties { get; set; }
+
+ /// Contains the audio data of the media file.
+ protected readonly WavData Data;
+
+ /// Represents a container for the .
+ protected readonly AudioBuffer AudioBuffer;
+
+ /// The source of the audio.
+ protected readonly AudioSource AudioSource;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ protected AudioPlayerBase()
+ {
+ Data = new WavData();
+
+ var audioDevice = new AudioDevice();
+ AudioBuffer = new AudioBuffer(audioDevice.Al);
+ AudioSource = new AudioSource(audioDevice.Al);
+
+ AudioProperties = new AudioProperties(audioDevice.Al, AudioSource);
+ }
+
+ ///
+ /// Plays a media file specified by the given path.
+ ///
+ /// The location of the media file to be played.
+ public abstract void Play(string filePath);
+
+ ///
+ /// Validates the specified file by checking its extension and existence.
+ ///
+ /// The path to the file that needs to be validated for its type and existence.
+ /// Thrown when the file does not have the allowed extension.
+ /// Thrown when the specified file does not exist in the file system.
+ protected virtual void ValidateFile(string filePath)
+ {
+ if (Path.GetExtension(filePath) != FileExtension)
+ throw new ArgumentException($"Given file is not a {FileExtension} file", nameof(filePath));
+
+ if (!File.Exists(filePath))
+ throw new FileNotFoundException("Given file does not exist", filePath);
+ }
+
+ ///
+ /// Stops the audio playback of the associated AudioSource.
+ ///
+ public virtual void Stop() => AudioSource.Stop();
+}
diff --git a/Core/Audio/AudioProperties.cs b/Core/Audio/AudioProperties.cs
new file mode 100644
index 0000000..8a57383
--- /dev/null
+++ b/Core/Audio/AudioProperties.cs
@@ -0,0 +1,36 @@
+using Silk.NET.OpenAL;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Contains properties used to control how the audio should be played.
+///
+public class AudioProperties
+{
+ private readonly AL _al;
+ private readonly uint _audioSourceId;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The context where the audio should be played.
+ /// The source at which the audio should be played.
+ public AudioProperties(AL al, AudioSource audioSource)
+ {
+ _al = al;
+ _audioSourceId = audioSource.Get();
+ }
+
+ /// Gets or sets whether the audio should be looping.
+ public bool IsLooping
+ {
+ get => _isLooping;
+ set
+ {
+ _isLooping = value;
+ _al.SetSourceProperty(_audioSourceId, SourceBoolean.Looping, value);
+ }
+ }
+
+ private bool _isLooping;
+}
diff --git a/Core/Audio/AudioSource.cs b/Core/Audio/AudioSource.cs
new file mode 100644
index 0000000..9e59179
--- /dev/null
+++ b/Core/Audio/AudioSource.cs
@@ -0,0 +1,47 @@
+using Silk.NET.OpenAL;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Represents a source for audio
+///
+public class AudioSource
+{
+ private readonly uint _source;
+ private readonly AL _al;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// An accessor to the OpenAL context.
+ public AudioSource(AL al)
+ {
+ _al = al;
+ _source = _al.GenSource();
+ }
+
+ ///
+ /// Gets the source identifier.
+ ///
+ /// The identifier to the source.
+ public uint Get() => _source;
+
+ ///
+ /// Plays the given audio from the .
+ ///
+ /// A buffer containing the audio data.
+ public void Play(AudioBuffer buffer)
+ {
+ _al.SetSourceProperty(_source, SourceInteger.Buffer, buffer.BufferId);
+ _al.SourcePlay(_source);
+ }
+
+ ///
+ /// Stops playing the audio.
+ ///
+ public void Stop()
+ {
+ _al.SourceStop(_source);
+ _al.DeleteSource(_source);
+ }
+}
\ No newline at end of file
diff --git a/Core/Audio/WavData.cs b/Core/Audio/WavData.cs
new file mode 100644
index 0000000..80ac937
--- /dev/null
+++ b/Core/Audio/WavData.cs
@@ -0,0 +1,22 @@
+using Silk.NET.OpenAL;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Represents audio data in WAV format.
+///
+public class WavData
+{
+ /// Gets or sets the number of channels used in the data.
+ public short NumChannels { get; set; }
+
+ /// Gets or sets the sample rate of the data.
+ public int SampleRate { get; set; }
+
+ /// Gets or sets the number of bits per sample.
+ public short BitsPerSample { get; set; }
+
+ /// Gets or sets the format of the buffer.
+ /// Defines how data is organized in the buffer.
+ public BufferFormat Format { get; set; }
+}
\ No newline at end of file
diff --git a/Core/Audio/WavPlayer.cs b/Core/Audio/WavPlayer.cs
new file mode 100644
index 0000000..02f9ed7
--- /dev/null
+++ b/Core/Audio/WavPlayer.cs
@@ -0,0 +1,115 @@
+using Silk.NET.OpenAL;
+
+using System;
+using System.Buffers.Binary;
+using System.IO;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Represents a player for .wav files.
+///
+public class WavPlayer : AudioPlayerBase
+{
+ ///
+ protected override string FileExtension => ".wav";
+
+ private static class WavConstants
+ {
+ public static ReadOnlySpan RiffHeader => "RIFF"u8;
+ public static ReadOnlySpan WaveHeader => "WAVE"u8;
+ public static ReadOnlySpan FmtHeader => "fmt "u8;
+ public static ReadOnlySpan DataHeader => "data"u8;
+
+ public const int ChunkSizeLength = 4;
+ public const int HeaderLength = 4;
+ public const int FormatChunkSkipLength = 6;
+ public const int AudioFormatLength = 2;
+ public const int NumChannelsLength = 2;
+ public const int SampleRateLength = 4;
+ public const int BitsPerSampleLength = 2;
+ }
+
+ ///
+ public override void Play(string filePath)
+ {
+ ValidateFile(filePath);
+
+ ReadOnlySpan file = File.ReadAllBytes(filePath);
+ int index = 0;
+
+ if (!CheckHeader(file, ref index, WavConstants.RiffHeader))
+ {
+ Console.WriteLine("Given file is not in RIFF format");
+ return;
+ }
+
+ index += WavConstants.ChunkSizeLength; // Skip chunk size
+
+ if (!CheckHeader(file, ref index, WavConstants.WaveHeader))
+ {
+ Console.WriteLine("Given file is not in WAVE format");
+ return;
+ }
+
+ while (index + WavConstants.HeaderLength <= file.Length)
+ {
+ var identifier = file.Slice(index, WavConstants.HeaderLength);
+ index += WavConstants.HeaderLength;
+
+ var size = BinaryPrimitives.ReadInt32LittleEndian(file.Slice(index, WavConstants.ChunkSizeLength));
+ index += WavConstants.ChunkSizeLength;
+
+ var data = file.Slice(index, size);
+ switch (identifier)
+ {
+ case var id when id.SequenceEqual(WavConstants.FmtHeader):
+ ParseFormat(data, Data);
+ break;
+
+ case var id when id.SequenceEqual(WavConstants.DataHeader):
+ AudioBuffer.LoadData(data, Data);
+ break;
+ }
+
+ index += size;
+ }
+
+ AudioSource.Play(AudioBuffer);
+ }
+
+ private static bool CheckHeader(ReadOnlySpan file, ref int index, ReadOnlySpan header)
+ {
+ if (!file.Slice(index, header.Length).SequenceEqual(header))
+ return false;
+
+ index += header.Length;
+ return true;
+ }
+
+ private static void ParseFormat(ReadOnlySpan formatChunk, WavData wavData)
+ {
+ int index = 0;
+ var audioFormat = BinaryPrimitives.ReadInt16LittleEndian(formatChunk.Slice(index, WavConstants.AudioFormatLength));
+ index += WavConstants.AudioFormatLength;
+
+ if (audioFormat != 1)
+ throw new InvalidOperationException($"Unknown Audio Format with ID {audioFormat}");
+
+ wavData.NumChannels = BinaryPrimitives.ReadInt16LittleEndian(formatChunk.Slice(index, WavConstants.NumChannelsLength));
+ index += WavConstants.NumChannelsLength;
+
+ wavData.SampleRate = BinaryPrimitives.ReadInt32LittleEndian(formatChunk.Slice(index, WavConstants.SampleRateLength));
+ index += WavConstants.SampleRateLength;
+
+ index += WavConstants.FormatChunkSkipLength; // Skip byteRate and blockAlign
+ wavData.BitsPerSample = BinaryPrimitives.ReadInt16LittleEndian(formatChunk.Slice(index, WavConstants.BitsPerSampleLength));
+
+ wavData.Format = wavData.NumChannels switch
+ {
+ 1 => wavData.BitsPerSample == 8 ? BufferFormat.Mono8 : BufferFormat.Mono16,
+ 2 => wavData.BitsPerSample == 8 ? BufferFormat.Stereo8 : BufferFormat.Stereo16,
+ _ => throw new InvalidOperationException($"Can't play audio with {wavData.NumChannels} channels")
+ };
+ }
+}
diff --git a/Core/Entities/Views/CameraView.cs b/Core/Entities/Views/CameraView.cs
index f3eb785..69af67c 100644
--- a/Core/Entities/Views/CameraView.cs
+++ b/Core/Entities/Views/CameraView.cs
@@ -24,20 +24,6 @@ public CameraView(Vector3 position, IViewSettings settings) : base(settings)
AspectRatio = settings.WindowOptions.Size.X / (float)settings.WindowOptions.Size.Y;
}
- // Those vectors are directions pointing outwards from the camera to define how it rotated.
- private Vector3 _front = -Vector3.UnitZ;
- private Vector3 _up = Vector3.UnitY;
- private Vector3 _right = Vector3.UnitX;
-
- /// Gets the front vector of the camera.
- public Vector3 Front => _front;
-
- /// Gets the up vector of the camera.
- public Vector3 Up => _up;
-
- /// Gets the right vector of the camera.
- public Vector3 Right => _right;
-
// Rotation around the X axis (radians)
private float _pitch;
diff --git a/Core/Entities/Views/Settings/DefaultViewSettings.cs b/Core/Entities/Views/Settings/DefaultViewSettings.cs
index 5a777e1..cef7852 100644
--- a/Core/Entities/Views/Settings/DefaultViewSettings.cs
+++ b/Core/Entities/Views/Settings/DefaultViewSettings.cs
@@ -1,5 +1,5 @@
using SharpEngine.Core.Renderers;
-
+using Silk.NET.Input;
using Silk.NET.Maths;
using Silk.NET.Windowing;
@@ -19,7 +19,7 @@ public DefaultViewSettings()
}
///
- /// Convenience wrapper around creating a new WindowProperties with sensible defaults.
+ /// Convenience wrapper around creating a new WindowProperties with sensible defaults.
///
public static ViewSettings Default { get; private set; }
@@ -41,4 +41,10 @@ public DefaultViewSettings()
Title = "SharpEngine",
Size = new Vector2D(1280, 720),
};
+
+ ///
+ public MouseButton PrimaryButton { get; set; } = MouseButton.Left;
+
+ ///
+ public MouseButton SecondaryButton { get; set; } = MouseButton.Right;
}
diff --git a/Core/Entities/Views/Settings/ViewSettings.cs b/Core/Entities/Views/Settings/ViewSettings.cs
index a28ffe3..09c65cb 100644
--- a/Core/Entities/Views/Settings/ViewSettings.cs
+++ b/Core/Entities/Views/Settings/ViewSettings.cs
@@ -1,6 +1,5 @@
-using SharpEngine.Core.Interfaces;
-using SharpEngine.Core.Renderers;
-using Silk.NET.Maths;
+using SharpEngine.Core.Renderers;
+using Silk.NET.Input;
using Silk.NET.Windowing;
namespace SharpEngine.Core.Entities.Views.Settings;
@@ -47,4 +46,10 @@ public ViewSettings()
///
public WindowOptions WindowOptions { get; set; }
+
+ ///
+ public MouseButton PrimaryButton { get; set; } = MouseButton.Left;
+
+ ///
+ public MouseButton SecondaryButton { get; set; } = MouseButton.Right;
}
diff --git a/Core/Entities/Views/View.cs b/Core/Entities/Views/View.cs
index a6f5bf6..8f78b02 100644
--- a/Core/Entities/Views/View.cs
+++ b/Core/Entities/Views/View.cs
@@ -1,5 +1,8 @@
using SharpEngine.Core.Entities.Views.Settings;
+using SharpEngine.Core.Scenes;
using SharpEngine.Core.Shaders;
+using System;
+using System.Collections.Generic;
using System.Numerics;
namespace SharpEngine.Core.Entities.Views;
@@ -18,6 +21,20 @@ public View(IViewSettings settings)
Settings = settings;
}
+ // Those vectors are directions pointing outwards from the camera to define how it rotated.
+ protected Vector3 _front = -Vector3.UnitZ;
+ protected Vector3 _up = Vector3.UnitY;
+ protected Vector3 _right = Vector3.UnitX;
+
+ /// Gets the front vector of the camera.
+ public Vector3 Front => _front;
+
+ /// Gets the up vector of the camera.
+ public Vector3 Up => _up;
+
+ /// Gets the right vector of the camera.
+ public Vector3 Right => _right;
+
/// Gets or sets the settings for the view.
public IViewSettings Settings { get; set; }
@@ -65,4 +82,18 @@ public virtual void UpdateMousePosition(Vector2 mousePosition)
firstMove = false;
}
}
+
+ ///
+ /// Checks whether a game object is in view of the camera.
+ ///
+ ///
+ /// Uses a step based ray marching algorithm to check if any object is in view of the camera.
+ ///
+ /// The scene where the check should be applied to.
+ /// The types that are allowed to be hit.
+ /// The first object that the ray intersected.
+ /// The position where the object was hit.
+ /// if any object was hit; otherwise, .
+ public bool IsInView(Scene scene, out GameObject? intersectingObject, out Vector3 hitPosition, params Type[] allowedTypes)
+ => new Ray(Position, Front).IsGameObjectInView(scene, out intersectingObject, out hitPosition, allowedTypes);
}
\ No newline at end of file
diff --git a/Core/Interfaces/ISettings.cs b/Core/Interfaces/ISettings.cs
index e2ee475..17aa676 100644
--- a/Core/Interfaces/ISettings.cs
+++ b/Core/Interfaces/ISettings.cs
@@ -1,4 +1,5 @@
using SharpEngine.Core.Renderers;
+using Silk.NET.Input;
using Silk.NET.Windowing;
namespace SharpEngine.Core.Interfaces;
@@ -20,6 +21,9 @@ public interface ISettings
/// Gets or sets which renderer should be enabled for the current window.
public RenderFlags RendererFlags { get; set; }
+
+ public MouseButton PrimaryButton { get; set; }
+ public MouseButton SecondaryButton { get; set; }
}
///
@@ -33,4 +37,10 @@ public class DefaultSettings : ISettings
///
public RenderFlags RendererFlags { get; set; } = RenderFlags.All;
+
+ ///
+ public MouseButton PrimaryButton { get; set; } = MouseButton.Left;
+
+ ///
+ public MouseButton SecondaryButton { get; set; } = MouseButton.Right;
}
diff --git a/Core/Ray.cs b/Core/Ray.cs
index 68f1dee..42bae6e 100644
--- a/Core/Ray.cs
+++ b/Core/Ray.cs
@@ -13,14 +13,10 @@ namespace SharpEngine.Core;
///
public class Ray
{
- ///
- /// Gets or sets the ray origin.
- ///
+ /// Gets or sets the ray origin.
public Vector3 Origin { get; set; }
- ///
- /// Gets or sets the ray direction.
- ///
+ /// Gets or sets the ray direction.
public Vector3 Direction { get; set; }
///
@@ -35,7 +31,7 @@ public Ray(Vector3 origin, Vector3 direction)
}
///
- /// Checks if the ray intersects with any objects in the scene.
+ /// Checks if the ray intersects with any objects in the scene.
///
/// The scene to check for intersections.
/// The intersecting object; if the ray did not intersect an object.
@@ -49,7 +45,7 @@ public bool IsGameObjectInView(Scene scene, out GameObject? intersectingObject,
var sceneNodes = scene.GetAllGameObjects();
- if (allowedTypes.Any())
+ if (allowedTypes.Length != 0)
sceneNodes = [.. sceneNodes.Where(go => allowedTypes.Any(type => type.IsInstanceOfType(go)))];
for (float t = 0; t < maxDistance; t += stepSize)
@@ -70,7 +66,7 @@ public bool IsGameObjectInView(Scene scene, out GameObject? intersectingObject,
}
///
- /// Determines if a point is inside a given object.
+ /// Determines if a point is inside a given object.
///
/// The point to check.
/// The object to check against.
@@ -86,7 +82,7 @@ public static bool IsPointInsideObject(Vector3 point, GameObject obj)
}
///
- /// Gets the normal of the closest face of an object to a given point.
+ /// Gets the normal of the closest face of an object to a given point.
///
/// The point to check.
/// The object to check against.
diff --git a/Core/SharpEngine.Core.csproj b/Core/SharpEngine.Core.csproj
index 1489ce2..5ec1e72 100644
--- a/Core/SharpEngine.Core.csproj
+++ b/Core/SharpEngine.Core.csproj
@@ -1,4 +1,4 @@
-
+
Everything needed to build games using a purely C# game engine.
@@ -11,18 +11,23 @@
$(AssemblyName).NET
- Debug;Release;LauncherNonWindows
+ Debug;Release;LauncherNonWindows
+ true
+
+
+
+ 2.22.0
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/Core/SilkWindow.cs b/Core/SilkWindow.cs
index cbb0cb8..bcc1936 100644
--- a/Core/SilkWindow.cs
+++ b/Core/SilkWindow.cs
@@ -190,14 +190,14 @@ public IWindow CreateWindow(WindowOptions opts)
return CurrentWindow;
}
- public static T Create(WindowOptions opts) where T : SilkWindow, new()
- {
- var instance = new T();
- var winndow = instance.CreateWindow(opts);
-
- instance.CurrentWindow = winndow;
- return instance;
- }
+ // public static T Create(WindowOptions opts) where T : SilkWindow, new()
+ // {
+ // var instance = new T();
+ // var winndow = instance.CreateWindow(opts);
+ //
+ // instance.CurrentWindow = winndow;
+ // return instance;
+ // }
///
protected virtual void Dispose(bool disposing) { }
diff --git a/Core/Window.cs b/Core/Window.cs
index af4cd49..cb1d7e3 100644
--- a/Core/Window.cs
+++ b/Core/Window.cs
@@ -29,13 +29,14 @@ namespace SharpEngine.Core;
///
public class Window : SilkWindow
{
- private readonly IViewSettings _settings;
- private readonly CameraView _camera;
-
private bool _initialized;
private IEnumerable _renderers = [];
private ImGuiController? _imGuiController;
+ public readonly CameraView Camera;
+
+ public IViewSettings Settings;
+
/// The event executed when mouse events are executed.
public event Action? OnHandleMouse;
@@ -77,8 +78,8 @@ public Window() { }
public Window(CameraView camera, Scene scene, IViewSettings settings)
{
Scene = scene;
- _settings = settings;
- _camera = camera;
+ Settings = settings;
+ Camera = camera;
Initialize();
}
@@ -90,8 +91,9 @@ public Window(CameraView camera, Scene scene, IViewSettings settings)
public Window(Scene scene, IViewSettings settings)
{
Scene = scene;
- _settings = settings;
- _camera = new(Vector3.One, settings);
+ Settings = settings;
+ Camera = new(Vector3.One, settings);
+
Initialize();
}
@@ -100,7 +102,7 @@ public Window(Scene scene, IViewSettings settings)
// public void Initialize() where T : SilkWindow, new()
private void Initialize()
{
- CurrentWindow = CreateWindow(_settings.WindowOptions);
+ CurrentWindow = CreateWindow(Settings.WindowOptions);
CurrentWindow.Update += OnUpdateFrame;
CurrentWindow.Render += RenderFrame;
CurrentWindow.Resize += OnResize;
@@ -157,7 +159,7 @@ public override void OnLoad()
{
// Make sure the renderer has the correct constructor parameters!
// TODO: The static reference to the context will not work when multiple windows are implemented, since the context will be different.
- var requiredArguments = new object[] { _camera, _settings, Scene };
+ var requiredArguments = new object[] { Camera, Settings, Scene };
var renderer = (RendererBase)Activator.CreateInstance(type, requiredArguments)!;
_renderers = _renderers.Append(renderer);
@@ -197,11 +199,11 @@ protected void RenderFrame(double deltaTime)
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
- ToggleWireFrame(_settings.UseWireFrame);
+ ToggleWireFrame(Settings.UseWireFrame);
UseShaders();
- var renderTasks = _renderers.Where(renderer => _settings.RendererFlags.HasFlag(renderer.RenderFlag))
+ var renderTasks = _renderers.Where(renderer => Settings.RendererFlags.HasFlag(renderer.RenderFlag))
.Select(renderer => renderer.Render())
.ToList();
@@ -249,14 +251,14 @@ protected void OnUpdateFrame(double deltaTime)
// if (!_window.IsFocused)
// return;
- if (_settings.PrintFrameRate)
+ if (Settings.PrintFrameRate)
Console.WriteLine($"FPS: {1f / deltaTime}");
// TODO: Handle multiple mice?
var mouse = Input?.Mice[0];
if (mouse is not null)
{
- _camera.UpdateMousePosition(mouse.Position);
+ Camera.UpdateMousePosition(mouse.Position);
OnHandleMouse?.Invoke(mouse);
}
@@ -311,14 +313,14 @@ protected void OnMouseWheel(IMouse mouse, ScrollWheel sw)
};
HandleMouseWheel?.Invoke(direction, sw);
- _camera.Fov -= sw.Y;
+ Camera.Fov -= sw.Y;
}
///
protected void OnResize(Vector2D size)
{
GL.Viewport(size);
- _camera.AspectRatio = size.X / size.Y;
+ Camera.AspectRatio = size.X / size.Y;
}
///
diff --git a/Editor/EditorSettings.cs b/Editor/EditorSettings.cs
index 12e447e..6b92693 100644
--- a/Editor/EditorSettings.cs
+++ b/Editor/EditorSettings.cs
@@ -1,5 +1,6 @@
using SharpEngine.Core.Entities.Views.Settings;
using SharpEngine.Core.Renderers;
+using Silk.NET.Input;
namespace SharpEngine.Editor;
@@ -22,4 +23,10 @@ public class EditorSettings : IViewSettings
///
public float MouseSensitivity { get; set; }
+
+ ///
+ public MouseButton PrimaryButton { get; set; } = MouseButton.Left;
+
+ ///
+ public MouseButton SecondaryButton { get; set; } = MouseButton.Right;
}
\ No newline at end of file
diff --git a/Editor/EditorWindow.cs b/Editor/EditorWindow.cs
index f5bc365..d46c5b5 100644
--- a/Editor/EditorWindow.cs
+++ b/Editor/EditorWindow.cs
@@ -1,13 +1,16 @@
using ImGuiNET;
using Launcher.UI;
-using Silk.NET.Input;
using SharpEngine.Core;
using SharpEngine.Core.Entities.Views.Settings;
using SharpEngine.Core.Primitives;
using SharpEngine.Core.Scenes;
+using SharpEngine.Core.Entities;
+
using SharpEngine.Editor.Windows;
+using Silk.NET.Input;
+
using System.Numerics;
using System.Reflection;
@@ -24,8 +27,6 @@ public class EditorWindow : Window
private ContextMenuWindow? _contextMenuWindow;
private ActionsMenuWindow? _actionsMenuWindow;
- public EditorWindow() { }
-
///
/// Initializes an editor window with a specified scene and view settings.
///
@@ -132,7 +133,31 @@ public override void OnMouseClick(IMouse mouse, MouseButton button, Vector2 vect
{
base.OnMouseClick(mouse, button, vector);
- if (button == MouseButton.Right)
- _contextMenuWindow?.ShowContextMenu();
+ if (button == Settings.PrimaryButton)
+ {
+ if (ImGui.IsAnyItemHovered())
+ {
+ // TODO: Right-clicked on an ImGui component
+ }
+ else
+ {
+ // TODO: Right-clicked outside of ImGui components
+ Camera.IsInView(Scene, out GameObject? intersectingObject, out Vector3 hitPosition);
+ _contextMenuWindow?.ShowContextMenu(intersectingObject);
+ }
+ }
+
+ if (button == Settings.SecondaryButton)
+ {
+ if (ImGui.IsAnyItemHovered())
+ {
+ // TODO: Left-clicked on an ImGui component
+ }
+ else
+ {
+ Camera.IsInView(Scene, out GameObject? intersectingObject, out Vector3 hitPosition);
+ Scene.ActiveElement = intersectingObject;
+ }
+ }
}
}
diff --git a/Editor/Windows/ActionsMenuWindow.cs b/Editor/Windows/ActionsMenuWindow.cs
index 7fb73bd..af5e223 100644
--- a/Editor/Windows/ActionsMenuWindow.cs
+++ b/Editor/Windows/ActionsMenuWindow.cs
@@ -1,6 +1,7 @@
using ImGuiNET;
using Launcher.UI;
using SharpEngine.Core;
+using SharpEngine.Core.Audio;
using SharpEngine.Core.Scenes;
using SharpEngine.Shared;
@@ -22,8 +23,6 @@ public class ActionsMenuWindow : ImGuiWindowBase
///
public override void Render()
{
- ImGui.Text("some text");
-
if (ImGui.Button("Save"))
SaveScene(Scene);
@@ -32,6 +31,26 @@ public override void Render()
if (ImGui.Button("Play"))
StartGame();
+
+ if (ImGui.Button("Test audio"))
+ PlayTestAudio();
+ }
+
+ private void PlayTestAudio()
+ {
+ try
+ {
+ string filePath = ""; // TODO: Replace with absolute path to .wav file.
+
+ var audioPlayer = new WavPlayer();
+ audioPlayer.AudioProperties.IsLooping = false;
+
+ audioPlayer.Play(filePath);
+ }
+ catch (Exception ex)
+ {
+ Debug.LogInformation($"Error during audio playback: {ex.Message}");
+ }
}
private void StartGame()
diff --git a/Editor/Windows/AssetsWindow.cs b/Editor/Windows/AssetsWindow.cs
index d8f3dd9..b9b7051 100644
--- a/Editor/Windows/AssetsWindow.cs
+++ b/Editor/Windows/AssetsWindow.cs
@@ -1,7 +1,5 @@
using ImGuiNET;
using Launcher.UI;
-using SharpEngine.Core;
-using SharpEngine.Core.Scenes;
namespace SharpEngine.Editor.Windows
{
diff --git a/Editor/Windows/ContextMenuWindow.cs b/Editor/Windows/ContextMenuWindow.cs
index d7ea5ad..8b10d97 100644
--- a/Editor/Windows/ContextMenuWindow.cs
+++ b/Editor/Windows/ContextMenuWindow.cs
@@ -1,5 +1,6 @@
using ImGuiNET;
using Launcher.UI;
+using SharpEngine.Core.Entities;
using SharpEngine.Core.Primitives;
using SharpEngine.Core.Scenes;
using System.Numerics;
@@ -13,49 +14,74 @@ public class ContextMenuWindow : ImGuiWindowBase
{
private bool _showContextMenu;
private bool _updateContextMenuLocation;
+ private GameObject? IntersectedObject;
///
public override string Name => "Context Menu";
///
- public override ImGuiWindowFlags ImGuiWindowFlags => ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove;
+ public override ImGuiWindowFlags ImGuiWindowFlags => ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.AlwaysAutoResize;
///
/// Shows the context menu.
///
- public void ShowContextMenu()
+ /// The object the cursor right-clicked on.
+ public void ShowContextMenu(GameObject? intersectedObject = null)
{
+ IntersectedObject = intersectedObject;
_updateContextMenuLocation = true;
_showContextMenu = true;
}
+ ///
+ public override void PreRender()
+ {
+ if (_updateContextMenuLocation)
+ {
+ ImGui.SetNextWindowPos(ImGui.GetMousePos());
+ _updateContextMenuLocation = false;
+ }
+ }
+
///
public override void Render()
{
if (!_showContextMenu)
+ {
+ IntersectedObject = null;
return;
+ }
- if (Scene is null)
- throw new InvalidOperationException("Scene is not set.");
+ if (Scene is not null)
+ RenderSceneActions();
- if (_updateContextMenuLocation)
+ if (IntersectedObject is not null)
+ RenderIntersectedObjectActions();
+ }
+
+ private void RenderIntersectedObjectActions()
+ {
+ if (ImGui.Button("Delete"))
{
- ImGui.SetNextWindowPos(ImGui.GetMousePos());
- _updateContextMenuLocation = false;
+ Scene.Root.RemoveChild(IntersectedObject);
+ IntersectedObject = null;
}
+ }
- ImGui.Begin("Context Menu", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove);
-
- if (ImGui.Button("Create cube"))
+ private void RenderSceneActions()
+ {
+ var values = Enum.GetValues();
+ foreach (var value in values)
{
- var cube = PrimitiveFactory.Create(PrimitiveType.Cube, new Vector3(0, 0, 0));
- cube.Name = "Cube" + Scene.Root.Children.Count;
+ if (ImGui.Button($"Create {value}"))
+ {
+ var cube = PrimitiveFactory.Create(value, new Vector3(0, 0, 0));
+ cube.Name = value.ToString() + Scene.Root.Children.Count;
- Scene.ActiveElement = cube;
- Scene.Root.AddChild(cube);
+ Scene.ActiveElement = cube;
+ Scene.Root.AddChild(cube);
+ }
}
-
- ImGui.End();
}
}
}
diff --git a/Editor/Windows/ImGuiWindowBase.cs b/Editor/Windows/ImGuiWindowBase.cs
index c48c182..a75b2b3 100644
--- a/Editor/Windows/ImGuiWindowBase.cs
+++ b/Editor/Windows/ImGuiWindowBase.cs
@@ -1,13 +1,8 @@
using ImGuiNET;
using Launcher.UI;
+
using SharpEngine.Core.Entities.Views.Settings;
using SharpEngine.Core.Scenes;
-using Silk.NET.Input;
-using Silk.NET.Maths;
-using Silk.NET.OpenGL;
-using Silk.NET.OpenGL.Extensions.ImGui;
-using Silk.NET.Windowing;
-using System.Threading;
namespace SharpEngine.Editor.Windows
{
@@ -51,11 +46,18 @@ public ImGuiWindowBase()
/// The new project.
public void SetProject(Project project) => Project = project;
+ ///
+ /// Executes operations required before rendering the ImGui window.
+ ///
+ public virtual void PreRender() { }
+
///
/// Renders a new ImGui window to the main window.
///
public void RenderWindow()
{
+ PreRender();
+
ImGui.Begin(Name, ImGuiWindowFlags);
Render();
@@ -77,7 +79,10 @@ public void RenderWindow()
protected virtual void OnWindowUndocked()
{
Console.WriteLine($"Window {Name} was undocked.");
- CreateSilkWindow();
+
+ // TODO: This method of doing this does not work (at least in .Net 8).
+ // See issue #44 for more information.
+ //CreateSilkWindow();
}
private void CreateSilkWindow()
diff --git a/Editor/Windows/PropertiesWindow.cs b/Editor/Windows/PropertiesWindow.cs
index 9faf495..6f998ff 100644
--- a/Editor/Windows/PropertiesWindow.cs
+++ b/Editor/Windows/PropertiesWindow.cs
@@ -11,13 +11,20 @@ namespace SharpEngine.Editor.Windows;
public class PropertiesWindow : ImGuiWindowBase
{
///
- public override string Name => "Properties";
+ public override string Name => Scene.ActiveElement is null ? "Properties" : Scene.ActiveElement!.Name + " properties";
///
public override void Render()
{
if (Scene.ActiveElement is not null)
RenderActiveElementProperties(Scene.ActiveElement);
+
+ if (Scene.ActiveElement is null)
+ {
+ ImGui.Text("No properties to display.");
+ ImGui.End();
+ return;
+ }
}
///
@@ -28,16 +35,6 @@ public override void Render()
/// Marks the starting level or the currently rendered level of properties.
public void RenderActiveElementProperties(object obj, int maxRecursionDepth = 5, int currentDepth = 0)
{
- if (currentDepth == 0)
- ImGui.Begin(obj is null ? "Properties" : Scene.ActiveElement!.Name + " properties");
-
- if (obj is null)
- {
- ImGui.Text("No properties to display.");
- ImGui.End();
- return;
- }
-
if (currentDepth > maxRecursionDepth)
return;
@@ -49,6 +46,10 @@ public void RenderActiveElementProperties(object obj, int maxRecursionDepth = 5,
foreach (var property in properties)
{
+ // Skip properties with index parameters
+ if (property.GetIndexParameters().Length > 0)
+ continue;
+
var inspectorAttribute = property.GetCustomAttribute();
if (inspectorAttribute != null && !inspectorAttribute.DisplayInInspector)
{
@@ -83,8 +84,5 @@ public void RenderActiveElementProperties(object obj, int maxRecursionDepth = 5,
{
Console.WriteLine(e);
}
-
- if (currentDepth == 0)
- ImGui.End();
}
}
diff --git a/Editor/Windows/SceneWindow.cs b/Editor/Windows/SceneWindow.cs
index aa4275c..e47065a 100644
--- a/Editor/Windows/SceneWindow.cs
+++ b/Editor/Windows/SceneWindow.cs
@@ -1,5 +1,4 @@
using ImGuiNET;
-using Launcher.UI;
using SharpEngine.Core.Scenes;
namespace SharpEngine.Editor.Windows
diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs
index 4c6ebf7..e1311bd 100644
--- a/Examples/MinecraftClone/Minecraft.cs
+++ b/Examples/MinecraftClone/Minecraft.cs
@@ -209,7 +209,7 @@ public override void HandleMouseDown(IMouse mouse, MouseButton button)
private BlockType DestroyBlock()
{
- if (!IsBlockInView(out GameObject? intersectingObject, out Vector3 _))
+ if (!Camera.IsInView(_scene, out GameObject? intersectingObject, out Vector3 _, allowedTypes: typeof(BlockBase)))
return BlockType.None;
var block = (BlockBase)intersectingObject!;
@@ -220,7 +220,7 @@ private BlockType DestroyBlock()
private void PlaceBlock()
{
- if (!IsBlockInView(out GameObject? intersectingObject, out Vector3 hitPosition))
+ if (!Camera.IsInView(_scene, out GameObject? intersectingObject, out Vector3 hitPosition, allowedTypes: typeof(BlockBase)))
return;
var newBlockPosition = GetNewBlockPosition(hitPosition, intersectingObject!);
@@ -240,18 +240,6 @@ private static Vector3 GetNewBlockPosition(Vector3 hitPosition, GameObject inter
return intersectingObject.Transform.Position + (normal * intersectingObject.Transform.Scale);
}
- ///
- /// Checks whether a block is in view of the camera.
- ///
- ///
- ///
- ///
- public bool IsBlockInView(out GameObject? intersectingObject, out Vector3 hitPosition)
- {
- Ray ray = new Ray(Camera.Position, Camera.Front);
- return ray.IsGameObjectInView(_scene, out intersectingObject, out hitPosition, allowedTypes: typeof(BlockBase));
- }
-
///
public override void HandleMouseWheel(MouseWheelScrollDirection direction, ScrollWheel scrollWheel)
{
diff --git a/Examples/MultipleWindows/Program.cs b/Examples/MultipleWindows/Program.cs
index 73a0908..0f3a748 100644
--- a/Examples/MultipleWindows/Program.cs
+++ b/Examples/MultipleWindows/Program.cs
@@ -19,27 +19,21 @@ static void Main()
static void StartWindow(IWindow window)
{
- var thread = new Thread(() =>
+ window.Load += () =>
{
- window.Load += () =>
- {
- Console.WriteLine($"Hello from window: " + window.Title);
-
- var gl = GL.GetApi(window);
+ Console.WriteLine($"Hello from window: " + window.Title);
- unsafe
- {
- var versionPtr = gl.GetString(StringName.Version);
- string? version = SilkMarshal.PtrToString((nint)versionPtr);
- Console.WriteLine($"OpenGL version: {version}");
- }
+ var gl = GL.GetApi(window);
- };
+ unsafe
+ {
+ var versionPtr = gl.GetString(StringName.Version);
+ string? version = SilkMarshal.PtrToString((nint)versionPtr);
+ Console.WriteLine($"OpenGL version: {version}");
+ }
- window.Run();
- });
+ };
- thread.SetApartmentState(ApartmentState.STA);
- thread.Start();
+ window.Run();
}
}
diff --git a/SharpEngine.sln b/SharpEngine.sln
index 8989e1a..3c5d13b 100644
--- a/SharpEngine.sln
+++ b/SharpEngine.sln
@@ -87,4 +87,7 @@ Global
{1BEC06D8-094D-8BC2-288E-E2C4C13F3D2E} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
{0AF16D15-5BD2-4FED-9C6A-227DE6A56C5E} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {AA03EC4B-4E7B-4B73-BEA4-60C539332773}
+ EndGlobalSection
EndGlobal