Skip to content
This repository has been archived by the owner on Nov 25, 2024. It is now read-only.

Commit

Permalink
Reorder members to be less messy
Browse files Browse the repository at this point in the history
  • Loading branch information
EtheraelEspeon committed Jan 24, 2024
1 parent f5153bd commit 29f6abf
Showing 1 changed file with 139 additions and 107 deletions.
246 changes: 139 additions & 107 deletions Client/Gui/GuiCanvas.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.Encodings.Web;
using GlmSharp;
using SixLabors.ImageSharp;
using Veldrid.OpenGLBinding;
using Veldrid.Sdl2;
using Voxel.Client.Rendering.Gui;
using Voxel.Client.Rendering.Texture;
using Voxel.Client.Rendering.VertexTypes;
Expand All @@ -16,36 +12,8 @@ namespace Voxel.Client.Gui;
public static class GuiCanvas {
// TODO: Update this as the window changes size
public static ivec2 ReferenceResolution { get; private set; } = ivec2.Zero;
public static vec2 ScreenToPixel(vec2 s, vec2 referenceResolution)
=> (s + vec2.Ones) / 2 * referenceResolution;
public static vec2 PixelToScreen(vec2 p, vec2 referenceResolution)
=> p / referenceResolution * 2 - vec2.Ones;

private static GuiRenderer renderer;
// The root of the GUI tree
public static GuiRect screen;

public static void Init(GuiRenderer renderer) {
GuiCanvas.renderer = renderer;
ReferenceResolution = new(renderer.Client.NativeWindow.Width, renderer.Client.NativeWindow.Height);
screen = new GuiRect(-vec2.Ones, -vec2.Ones, vec2.Ones);

var healthbar = screen.AddChild(new GuiRect(new(1, 1), new(1, 1), new vec2(0.8f, 0.1f), "test"));
for (int i = 0; i < 7; i++) {
healthbar.AddChild(new(new(1, 0), new(1 - 0.11f * i, 0), GuiRect.FromPixelAspectRatioAndHeight(9, 8, 1), "test"));
}
}

internal static Atlas.Sprite GetSprite(string spriteName) {
if (renderer.GuiAtlas.TryGetSprite($"gui/{spriteName}", out var sprite)) {
return sprite;
} else {
// TODO: Log warning
Console.WriteLine($"GUI sprite {spriteName} does not exist");
return null;
}
}

// internal so GuiRect.Rebuild() can bypass the needs rebuilt check
internal static readonly GuiVertex[] _QuadCache = new GuiVertex[1024];
public static GuiVertex[] QuadCache {
Expand All @@ -69,19 +37,47 @@ public static GuiVertex[] QuadCache {
}
public static uint QuadCount { get; internal set; }

private static bool quadCacheNeedsRebuilt = true;
internal static SortedSet<GuiRect> branchesToRebuild = new SortedSet<GuiRect>(new GuiRect.ByTreeDepth());

// The root of the GUI tree
public static GuiRect screen;

public static vec2 ScreenToPixel(vec2 s, vec2 referenceResolution)
=> (s + vec2.Ones) / 2 * referenceResolution;
public static vec2 PixelToScreen(vec2 p, vec2 referenceResolution)
=> p / referenceResolution * 2 - vec2.Ones;

public static void Init(GuiRenderer renderer) {
GuiCanvas.renderer = renderer;
ReferenceResolution = new(renderer.Client.NativeWindow.Width, renderer.Client.NativeWindow.Height);
screen = new GuiRect(-vec2.Ones, -vec2.Ones, vec2.Ones);

var healthbar = screen.AddChild(new GuiRect(new(1, 1), new(1, 1), new vec2(0.8f, 0.1f)));
for (int i = 0; i < 7; i++) {
healthbar.AddChild(new(new(1, 0), new(1 - 0.11f * i, 0), GuiRect.FromPixelAspectRatioAndHeight(9, 8, 1), "heart"));
}
}

internal static Atlas.Sprite GetSprite(string spriteName) {
if (renderer.GuiAtlas.TryGetSprite($"gui/{spriteName}", out var sprite)) {
return sprite;
} else {
// TODO: Log warning
Console.WriteLine($"GUI sprite {spriteName} does not exist");
return null;
}
}

public static void InvalidateQuadCache() {
quadCacheNeedsRebuilt = true;
}
// Assign each GuiRect a new quadIdx, then rebuild all of them
internal static void RebuildQuadCache() {
QuadCount = 0;
branchesToRebuild.Clear();
screen!.Rebuild(-vec2.Ones, vec2.Ones, true);
}

private static bool quadCacheNeedsRebuilt = true;
public static void InvalidateQuadCache() {
quadCacheNeedsRebuilt = true;
}

internal static SortedSet<GuiRect> branchesToRebuild = new SortedSet<GuiRect>(new GuiRect.ByTreeDepth());
}

public class GuiRect {
Expand Down Expand Up @@ -161,10 +157,21 @@ public class ByTreeDepth : IComparer<GuiRect>
public int Compare(GuiRect? lhs, GuiRect? rhs)
=> (int)(lhs!.treeDepth - rhs!.treeDepth);
}

// modifies rect based on the parent
public delegate void SizeInitializer(GuiRect parent, GuiRect rect);

/// <returns>
/// a closure that encapsulates these parameters and will give return a GuiRect with the proper dimensions
/// </returns>
public static SizeInitializer FromPixelAspectRatioAndHeight(float ratioWidth, float ratioHeight, float screenHeightConstraint) {
return (GuiRect parent, GuiRect rect) => {
float heightToWidth = ratioWidth / ratioHeight;
float pixelHeight = parent.ReferenceResolution.y * screenHeightConstraint;
float pixelWidth = heightToWidth * pixelHeight;

rect.pixelSize = new(pixelWidth, pixelHeight);
};
}

public GuiRect(vec2 screenAnchor, vec2 localScreenPosition, vec2 localScreenSize, string image = "") {
this.screenAnchor = screenAnchor;
this.localScreenPosition = localScreenPosition;
Expand All @@ -177,51 +184,37 @@ public GuiRect(vec2 screenAnchor, vec2 localScreenPosition, SizeInitializer size
this.sizeInitializer = sizeInitializer;
this.image = image;
}

// returns a closure that encapsulates these parameters and will give return a GuiRect with the proper dimensions
public static SizeInitializer FromPixelAspectRatioAndHeight(float ratioWidth, float ratioHeight, float screenHeightConstraint) {
return (GuiRect parent, GuiRect rect) => {
float heightToWidth = ratioWidth / ratioHeight;
float pixelHeight = parent.ReferenceResolution.y * screenHeightConstraint;
float pixelWidth = heightToWidth * pixelHeight;

rect.pixelSize = new(pixelWidth, pixelHeight);
};
}

/// <summary>
/// index into the GuiCanvas._QuadCache
/// </summary>
private uint quadIdx = 0;
/// <summary>
/// used in GuiCanvas for rebuilding individual branches
/// </summary>
private uint treeDepth = 0;

public GuiRect? parent = null;
public List<GuiRect> children = new();
private readonly SizeInitializer? sizeInitializer = null;

// returns the added child
public GuiRect AddChild(GuiRect rect) {
children.Add(rect);
rect.parent = this;
rect.treeDepth = treeDepth + 1;

GuiCanvas.InvalidateQuadCache();
// this needs a complete rebuild to keep indices contiguous when recursively iterating through the gui tree
// contiguous indices ensure the draw order of GUI elements is correct

if (rect.sizeInitializer != null) rect!.sizeInitializer(this, rect);

return rect;
}

// add to a deletion queue in GuiCanvas
public void Delete() {
throw new NotImplementedException();
}
/// <summary>
/// Takes information from this GuiRect's parent to initialize its size when added to the GUI tree.
/// </summary>
private SizeInitializer? sizeInitializer = null;

// allows for partial pixel resolutions. Note that GuiCanvas.ReferenceResolution must still be in whole pixels
/// <summary>
/// The pixel resolution of this GuiRect
/// </summary>
public vec2 ReferenceResolution {
get => (parent?.ReferenceResolution ?? GuiCanvas.ReferenceResolution) * localScreenSize;
}

// name of the texture rendered onto this GuiRect
// a GuiRect with an image of "" will not be rendered
// setting this updates the uv coordinates of this GuiRect's vertices
private string _image = "";

/// <summary>
/// name of the texture rendered onto this GuiRect<br/>
/// a GuiRect with an image of "" will not be rendered<br/>
/// setting this updates the uv coordinates of this GuiRect's vertices.
/// </summary>
public string image {
get => _image;
set {
Expand All @@ -230,32 +223,39 @@ public string image {
}

}

private string _image = "";

// Screen members are used for pixel member calculations along with ReferenceResolution

// x and y are both bound between -1 and 1.
// The anchor is a point inside (or on the edge of) a GuiRect;
// it defines how the width and height of the rect make it expand,
// and is what gets moved to the GuiRect's position
private vec2 _screenAnchor;
/// <summary>
/// x and y are both bound between -1 and 1.<br/>
/// The anchor is a point inside (or on the edge of) a GuiRect;
/// it defines how the width and height of the rect make it expand,
/// and is what gets moved to the GuiRect's position
/// </summary>
public vec2 screenAnchor {
get => _screenAnchor;
set {
GuiCanvas.branchesToRebuild.Add(this);
_screenAnchor = value;
}
}
private vec2 _screenAnchor;

// x and y can be any real number
// The position of this GuiRect in its parent GuiRect
private vec2 _localScreenPosition;
/// <summary>
/// x and y can be any real number<br/>
/// The position of this GuiRect in its parent GuiRect.
/// The origin is the bottom left corner of the parent.
/// </summary>
public vec2 localScreenPosition {
get => _localScreenPosition;
set {
GuiCanvas.branchesToRebuild.Add(this);
_localScreenPosition = value;
}
}
private vec2 _localScreenPosition;

// reading and setting this is expensive, and caching it is impractical for now.
public vec2 globalScreenPosition {
get {
Expand All @@ -273,69 +273,104 @@ public vec2 globalScreenPosition {
}
}

// x and y can be any real number
// The width and height of the GuiRect, from the anchor.
// 1 is the width of the entire parent GuiRect
// if the anchor's x position is 0.5, and the width is 12,
// the rect will extend 9 units left and 3 units right from the anchor
private vec2 _localScreenSize;
/// <summary>
/// x and y can be any real number<br/>
/// The width and height of the GuiRect, from the anchor. ex:<br/>
/// 1 is the width of the entire parent GuiRect
/// if the anchor's x position is 0.5, and the width is 12,
/// the rect will extend 9 units left and 3 units right from the anchor
/// </summary>
public vec2 localScreenSize {
get => _localScreenSize;
set {
GuiCanvas.branchesToRebuild.Add(this);
_localScreenSize = value;
}
}
private vec2 _localScreenSize;

// reading and setting this is expensive, and caching it is impractical for now.
public vec2 globalScreenSize {
get => localScreenSize * parent?.globalScreenSize ?? vec2.Ones;
set => localScreenSize = value / parent?.globalScreenSize ?? vec2.Ones;
}

// Pixel members are derived from screen values
/* ----- Pixel members are derived from screen values ----- */

// TODO: The pixel size changes with window resolution. Implement some mechanism for keeping GuiRects at a constant pixel size
// These dont have a local/global distinction, because they refer to physical size on the monitor

public vec2 pixelPosition {
get => GuiCanvas.ScreenToPixel(globalScreenPosition, GuiCanvas.ReferenceResolution);
set => globalScreenPosition = GuiCanvas.PixelToScreen(value, GuiCanvas.ReferenceResolution);
}
public vec2 pixelSize {
get => localScreenSize * ReferenceResolution;
set => localScreenSize = value / GuiCanvas.ReferenceResolution;
get => localScreenSize * (parent?.ReferenceResolution ?? GuiCanvas.ReferenceResolution);
set => localScreenSize = value / (parent?.ReferenceResolution ?? GuiCanvas.ReferenceResolution);
}

public Extents extents { get => new Extents(this); }

// Completely rebuilds this node of the GUI tree and all of its children
internal void Rebuild(vec2 globalParentPosition, vec2 globalParentSize, bool rebuildingEntireQuadCache = false) {
/// <summary>
/// Adds a child into the GUI tree.
/// </summary>
/// <returns>
/// the added child
/// </returns>
public GuiRect AddChild(GuiRect rect) {
children.Add(rect);
rect.parent = this;
rect.treeDepth = treeDepth + 1;

GuiCanvas.InvalidateQuadCache();
// this needs a complete rebuild to keep indices contiguous when recursively iterating through the gui tree
// contiguous indices ensure the draw order of GUI elements is correct

if (rect.sizeInitializer != null) rect!.sizeInitializer(this, rect);

return rect;
}

/// <summary>
/// add this node and all its children to a deletion queue in GuiCanvas
/// </summary>
public void Delete() {
throw new NotImplementedException();
}

/// <summary>
/// Completely rebuilds this node of the GUI tree and all of its children
/// </summary>
internal void Rebuild(vec2 globalParentBottomLeftPosition, vec2 globalParentSize, bool rebuildingEntireQuadCache = false) {
if (rebuildingEntireQuadCache)
quadIdx = GuiCanvas.QuadCount++ * 4;
else // we're in the middle of a partial rebuild
GuiCanvas.branchesToRebuild.Remove(this);

var globalSize = localScreenSize * globalParentSize;
var globalPos = globalParentPosition + (localScreenPosition + 1) * globalParentSize; // TODO: Add rotation
var globalPos = globalParentBottomLeftPosition + (localScreenPosition + 1) * globalParentSize; // TODO: Add rotation

//if (image == "") image = "test"; // TODO: test code, delete later

var e = new Extents(screenAnchor, globalPos, globalSize);
Console.WriteLine($"Sprite {image} at {e.PixelBottomLeft} to {e.PixelTopRight}");

GuiCanvas._QuadCache[quadIdx + 0] = new GuiVertex(e.ScreenBottomRight);
GuiCanvas._QuadCache[quadIdx + 1] = new GuiVertex(e.ScreenBottomLeft);
GuiCanvas._QuadCache[quadIdx + 2] = new GuiVertex(e.ScreenTopLeft);
GuiCanvas._QuadCache[quadIdx + 3] = new GuiVertex(e.ScreenTopRight);
GuiCanvas._QuadCache[quadIdx + 0] = new GuiVertex(e.ScreenBottomRight, GuiCanvas._QuadCache[quadIdx + 0].uv);
GuiCanvas._QuadCache[quadIdx + 1] = new GuiVertex(e.ScreenBottomLeft , GuiCanvas._QuadCache[quadIdx + 1].uv);
GuiCanvas._QuadCache[quadIdx + 2] = new GuiVertex(e.ScreenTopLeft , GuiCanvas._QuadCache[quadIdx + 2].uv);
GuiCanvas._QuadCache[quadIdx + 3] = new GuiVertex(e.ScreenTopRight , GuiCanvas._QuadCache[quadIdx + 3].uv);

// After the quadIdx has been set and the vertices have been set up
if(rebuildingEntireQuadCache) UpdateVertexUVs();

foreach (var c in children) {
c.Rebuild(globalPos, globalSize, rebuildingEntireQuadCache);
c.Rebuild(e.ScreenBottomLeft, globalSize, rebuildingEntireQuadCache);
}
}

// Relies on quadIdx being correct, and the GuiVertices in QuadCache having correct screen coordinates
/// <summary>
/// Relies on quadIdx being correct, and the GuiVertices in QuadCache having correct screen coordinates
/// </summary>
private void UpdateVertexUVs() {
// GuiRects without images are still included in the QuadCache
// This just sets their screen position outside of clip space so they'll be discarded
Expand All @@ -358,7 +393,4 @@ private void UpdateVertexUVs() {
GuiCanvas._QuadCache[quadIdx + 2] = new GuiVertex(GuiCanvas._QuadCache[quadIdx + 2].position, uvTopLeft );
GuiCanvas._QuadCache[quadIdx + 3] = new GuiVertex(GuiCanvas._QuadCache[quadIdx + 3].position, uvTopRight );
}

private uint quadIdx = 0; // index into the GuiCanvas._QuadCache
private uint treeDepth = 0; // used in GuiCanvas for rebuilding individual branches
}

0 comments on commit 29f6abf

Please sign in to comment.