Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,26 @@ It targets .NET 10+ desktop and web apps, providing a clean and flexible API tha
```csharp
using KappaDuck.Quack.Core;
using KappaDuck.Quack.Events;
using KappaDuck.Quack.Graphics.Rendering;
using KappaDuck.Quack.Input;
using KappaDuck.Quack.Video.Pixels;
using KappaDuck.Quack.Windows;

using EngineScope _ = QuackEngine.Init(Subsystem.Video);

using Window window = new("Quack!", 1920, 1080)
{
Resizable = true
};
using Window window = new("Quack!", 1920, 1080) { Resizable = true };
using Renderer renderer = new(window);

while (window.IsOpen)
{
while (window.Poll(out Event e))
{
if (e is QuitRequestedEvent or KeyPressedEvent { Key: Key.Escape })
{
return;
}
}

renderer.Clear(Colors.Black);
renderer.Present();
}
```

Expand Down Expand Up @@ -102,7 +103,7 @@ During active development, `KappaDuck.Quack` references the **pre-release** vers

| Quack! | Runtimes | SDL3 | SDL_image | SDL_ttf | SDL_mixer |
| :------: | :------------: | :------: | :-------: | :-----: | :-------: |
| `source` | `0.1.0-beta.2` | `3.4.10` | `3.4.4` | `3.2.2` | `3.2.2` |
| `source` | `0.1.0-beta.4` | `3.4.12` | `3.4.4` | `3.2.2` | `3.2.4` |

## Development & Sandbox

Expand All @@ -129,20 +130,19 @@ The sandbox project requires at least one `.cs` file to compile. All `.cs` files
```csharp
using KappaDuck.Quack.Core;
using KappaDuck.Quack.Events;
using KappaDuck.Quack.Input;
using KappaDuck.Quack.Windows;

using EngineScope _ = QuackEngine.Init(Subsystem.Video);
using Window window = new("Quack! Sandbox", 1280, 720);

using Window window = new("Quack!", 1920, 1080) { Resizable = true };

while (window.IsOpen)
{
while (window.Poll(out Event e))
{
if (e is QuitEvent || e is KeyEvent { Key: Key.Escape })
{
window.Close();
if (e is QuitRequestedEvent or KeyPressedEvent { Key: Key.Escape })
return;
}
}
}
```
Expand Down
4 changes: 2 additions & 2 deletions runtimes/utils/sdl.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SDL3=3.4.10
SDL3=3.4.12
SDL3_IMAGE=3.4.4
SDL3_TTF=3.2.2
SDL3_MIXER=3.2.2
SDL3_MIXER=3.2.4
25 changes: 25 additions & 0 deletions src/KappaDuck.Quack/Graphics/Drawing/IDrawable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) KappaDuck.
// Licensed under the MIT license.

using KappaDuck.Quack.Graphics.Rendering;

namespace KappaDuck.Quack.Graphics.Drawing;

/// <summary>
/// Represents an object that can draw itself onto an <see cref="IRenderTarget"/>, such as a sprite, shape or anything.
/// </summary>
public interface IDrawable
{
/// <summary>
/// Draws this object onto <paramref name="target"/>.
/// </summary>
/// <remarks>
/// Implementations fold their own transform into <paramref name="state"/> (for example with
/// <c>state = state with { Transform = state.Transform * localTransform }</c>), set the texture they need, then
/// lower themselves to the target's vertex draws. Call <see cref="IRenderTarget.Draw(IDrawable)"/> rather than
/// this method directly.
/// </remarks>
/// <param name="target">The target to draw onto.</param>
/// <param name="state">The render state to draw with, carrying the accumulated transform, blend mode and texture.</param>
void Draw(IRenderTarget target, RenderState state);
}
47 changes: 47 additions & 0 deletions src/KappaDuck.Quack/Graphics/Drawing/IRenderTarget.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) KappaDuck.
// Licensed under the MIT license.

using KappaDuck.Quack.Graphics.Primitives;
using KappaDuck.Quack.Graphics.Rendering;

namespace KappaDuck.Quack.Graphics.Drawing;

/// <summary>
/// Represents a surface that can be drawn onto, such as a <see cref="Renderer"/>.
/// </summary>
/// <remarks>
/// Drawables are rendered with <see cref="Draw(IDrawable)"/>; they in turn lower themselves to the vertex overloads,
/// which apply the render state's transform, blend mode and texture.
/// </remarks>
public interface IRenderTarget
{
/// <summary>
/// Draws <paramref name="drawable"/> onto this target using the <see cref="RenderState.Default"/> state.
/// </summary>
/// <param name="drawable">The object to draw.</param>
void Draw(IDrawable drawable);

/// <summary>
/// Draws <paramref name="drawable"/> onto this target using the given render state.
/// </summary>
/// <param name="drawable">The object to draw.</param>
/// <param name="state">The render state to draw with.</param>
void Draw(IDrawable drawable, RenderState state);

/// <summary>
/// Draws a sequence of vertices as triangles onto this target.
/// </summary>
/// <remarks>The render state's transform is applied to each vertex, and its texture, when set, textures the triangles.</remarks>
/// <param name="vertices">The vertices to draw, taken three at a time as triangles.</param>
/// <param name="state">The render state to draw with.</param>
void Draw(ReadOnlySpan<Vertex> vertices, RenderState state);

/// <summary>
/// Draws indexed vertices as triangles onto this target.
/// </summary>
/// <remarks>The render state's transform is applied to each vertex, and its texture, when set, textures the triangles.</remarks>
/// <param name="vertices">The vertices to draw.</param>
/// <param name="indices">Indices into <paramref name="vertices"/>, taken three at a time as triangles.</param>
/// <param name="state">The render state to draw with.</param>
void Draw(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<int> indices, RenderState state);
}
145 changes: 145 additions & 0 deletions src/KappaDuck.Quack/Graphics/Drawing/Sprite.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright (c) KappaDuck.
// Licensed under the MIT license.

using KappaDuck.Quack.Geometry;
using KappaDuck.Quack.Graphics.Primitives;
using KappaDuck.Quack.Graphics.Rendering;
using KappaDuck.Quack.Video.Pixels;

namespace KappaDuck.Quack.Graphics.Drawing;

/// <summary>
/// A drawable image, or a region of one, that can be positioned, rotated, scaled and tinted.
/// </summary>
/// <remarks>
/// A sprite is a lightweight view onto a <see cref="Texture"/>: many sprites can share one texture. Draw it with
/// <see cref="IRenderTarget.Draw(IDrawable)"/>.
/// </remarks>
public class Sprite : Transformable, IDrawable
{
private static readonly int[] _indices = [0, 1, 2, 0, 2, 3];
private readonly Vertex[] _vertices = new Vertex[4];

private Texture _texture;
private RectI _region;
private Color _color = Colors.White;

/// <summary>
/// Creates a sprite that displays the whole of <paramref name="texture"/>.
/// </summary>
/// <param name="texture">The texture to display.</param>
public Sprite(Texture texture) : this(texture, new RectI(0, 0, texture.Width, texture.Height))
{
}

/// <summary>
/// Creates a sprite that displays a region of <paramref name="texture"/>.
/// </summary>
/// <param name="texture">The texture to display.</param>
/// <param name="region">The region of the texture to display, in texture pixels.</param>
public Sprite(Texture texture, RectI region)
{
_texture = texture;
_region = region;

UpdateVertices();
}

/// <summary>
/// Gets or sets the texture the sprite displays.
/// </summary>
/// <remarks>The current <see cref="Region"/> is kept; assign it again if the new texture has different dimensions.</remarks>
public Texture Texture
{
get => _texture;
set
{
_texture = value;
UpdateVertices();
}
}

/// <summary>
/// Gets or sets the region of the <see cref="Texture"/> the sprite displays, in texture pixels.
/// </summary>
public RectI Region
{
get => _region;
set
{
_region = value;
UpdateVertices();
}
}

/// <summary>
/// Gets or sets a color multiplied into the sprite, tinting it.
/// </summary>
/// <remarks>Defaults to <see cref="Colors.White"/>, which leaves the texture unchanged; the color's alpha sets the sprite's opacity.</remarks>
public Color Color
{
get => _color;
set
{
_color = value;
UpdateColors();
}
}

/// <summary>
/// Gets the sprite's bounding rectangle in its own local space, before its transform is applied.
/// </summary>
/// <remarks>Its size matches <see cref="Region"/>, with the top-left corner at (0, 0).</remarks>
public Rect LocalBounds => new(0f, 0f, _region.Width, _region.Height);

/// <summary>
/// Gets the sprite's axis-aligned bounding rectangle in its parent's space, after its transform is applied.
/// </summary>
/// <remarks>When the sprite is rotated, this is the smallest upright rectangle that contains it.</remarks>
public Rect GlobalBounds => Transform.TransformRect(LocalBounds);

/// <inheritdoc/>
public void Draw(IRenderTarget target, RenderState state)
{
state = state with
{
Transform = state.Transform * Transform,
Texture = _texture
};

target.Draw(_vertices, _indices, state);
}

private void UpdateVertices()
{
float width = _region.Width;
float height = _region.Height;

_vertices[0].Position = new PointF(0f, 0f);
_vertices[1].Position = new PointF(width, 0f);
_vertices[2].Position = new PointF(width, height);
_vertices[3].Position = new PointF(0f, height);

float left = (float)_region.X / _texture.Width;
float top = (float)_region.Y / _texture.Height;
float right = (float)(_region.X + _region.Width) / _texture.Width;
float bottom = (float)(_region.Y + _region.Height) / _texture.Height;

_vertices[0].TextureCoordinate = new PointF(left, top);
_vertices[1].TextureCoordinate = new PointF(right, top);
_vertices[2].TextureCoordinate = new PointF(right, bottom);
_vertices[3].TextureCoordinate = new PointF(left, bottom);

UpdateColors();
}

private void UpdateColors()
{
ColorF color = _color.ToColorF();

_vertices[0].Color = color;
_vertices[1].Color = color;
_vertices[2].Color = color;
_vertices[3].Color = color;
}
}
50 changes: 50 additions & 0 deletions src/KappaDuck.Quack/Graphics/Rendering/RenderState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) KappaDuck.
// Licensed under the MIT license.

using KappaDuck.Quack.Geometry;
using KappaDuck.Quack.Graphics.Drawing;

namespace KappaDuck.Quack.Graphics.Rendering;

/// <summary>
/// The set of parameters that control how a drawable is rendered onto an <see cref="IRenderTarget"/>: the transform
/// applied to its vertices, the blend mode, and the texture it is drawn with.
/// </summary>
/// <remarks>
/// Create a render state with an object initializer, for example <c>new RenderState { Texture = texture }</c>, or from
/// <see cref="Default"/>, then layer on additional transforms with a <c>with</c> expression. Avoid
/// <c>default(RenderState)</c>: it leaves the <see cref="Transform"/> unset rather than at
/// <see cref="Transform.Identity"/>.
/// </remarks>
public readonly record struct RenderState
{
/// <summary>
/// Creates a render state with an identity <see cref="Transform"/>, alpha blending and no texture.
/// </summary>
public RenderState()
{
Transform = Transform.Identity;
BlendMode = BlendMode.Blend;
Texture = null;
}

/// <summary>
/// Gets the default render state: an identity transform, alpha blending and no texture.
/// </summary>
public static RenderState Default { get; } = new();

/// <summary>
/// Gets the transform applied to the vertices before they are drawn. Defaults to <see cref="Transform.Identity"/>.
/// </summary>
public Transform Transform { get; init; }

/// <summary>
/// Gets the blend mode used to combine the drawing with the target. Defaults to <see cref="BlendMode.Blend"/>.
/// </summary>
public BlendMode BlendMode { get; init; }

/// <summary>
/// Gets the texture the vertices are drawn with, or <see langword="null"/> to draw untextured geometry.
/// </summary>
public Texture? Texture { get; init; }
}
Loading