diff --git a/src/Core/Game.bf b/src/Core/Game.bf index d3ce23d..6ae8032 100644 --- a/src/Core/Game.bf +++ b/src/Core/Game.bf @@ -25,7 +25,10 @@ namespace Strawberry private Scene scene; private Scene switchToScene; private bool updating; + private Dictionary> entityAssignableLists; + private Dictionary> componentAssignableLists; + //SDL Vars public SDL.Renderer* Renderer { get; private set; } public Color ClearColor = .Black; @@ -78,7 +81,7 @@ namespace Strawberry SDLMixer.OpenAudio(44100, SDLMixer.MIX_DEFAULT_FORMAT, 2, 4096); SDLTTF.Init(); - TypeTree.[Friend]Build(); + BuildTypeLists(); Input.[Friend]Init(gamepadLimit); } @@ -99,7 +102,7 @@ namespace Strawberry delete VirtualInputs; } - TypeTree.[Friend]Dispose(); + DisposeTypeLists(); Input.[Friend]Dispose(); Game = null; } @@ -210,5 +213,57 @@ namespace Strawberry switchToScene = value; } } + + // Type assignable caching + + private void BuildTypeLists() + { + /* + For each Type that extends Entity, we build a list of all the other Entity Types that it is assignable to. + We cache these lists, and use them later to bucket Entities as they are added to the Scene. + This allows us to retrieve Entities by type very easily. + */ + + entityAssignableLists = new Dictionary>(); + for (let type in Type.Enumerator()) + { + if (type != typeof(Entity) && type.IsSubtypeOf(typeof(Entity))) + { + let list = new List(); + for (let check in Type.Enumerator()) + if (check != typeof(Entity) && check.IsSubtypeOf(typeof(Entity)) && type.IsSubtypeOf(check)) + list.Add(check); + entityAssignableLists.Add(type, list); + } + } + + /* + And then we also do this for components + */ + + componentAssignableLists = new Dictionary>(); + for (let type in Type.Enumerator()) + { + if (type != typeof(Component) && type.IsSubtypeOf(typeof(Component))) + { + let list = new List(); + for (let check in Type.Enumerator()) + if (check != typeof(Component) && check.IsSubtypeOf(typeof(Component)) && type.IsSubtypeOf(check)) + list.Add(check); + componentAssignableLists.Add(type, list); + } + } + } + + private void DisposeTypeLists() + { + for (let list in entityAssignableLists.Values) + delete list; + delete entityAssignableLists; + + for (let list in componentAssignableLists.Values) + delete list; + delete componentAssignableLists; + } } } diff --git a/src/Core/Scene.bf b/src/Core/Scene.bf index 063d796..a4e9523 100644 --- a/src/Core/Scene.bf +++ b/src/Core/Scene.bf @@ -8,6 +8,7 @@ namespace Strawberry public float TimeStarted { get; private set; } public Grid SolidGrid; public Rect Bounds; + public Vector Camera; private List entities; private HashSet toRemove; @@ -22,11 +23,11 @@ namespace Strawberry toRemove = new HashSet(); entityTracker = new Dictionary>(); - for (let type in TypeTree.[Friend]EntityAssignableLists.Keys) + for (let type in Game.[Friend]entityAssignableLists.Keys) entityTracker.Add(type, new List()); componentTracker = new Dictionary>(); - for (let type in TypeTree.[Friend]ComponentAssignableLists.Keys) + for (let type in Game.[Friend]componentAssignableLists.Keys) componentTracker.Add(type, new List()); } @@ -63,6 +64,8 @@ namespace Strawberry public virtual void Update() { + Camera.X += 20 * Time.Delta; + UpdateLists(); for (var e in entities) if (e.Active) @@ -132,7 +135,7 @@ namespace Strawberry private void TrackEntity(Entity e) { - for (let t in TypeTree.[Friend]EntityAssignableLists[e.GetType()]) + for (let t in Game.[Friend]entityAssignableLists[e.GetType()]) entityTracker[t].Add(e); for (let c in e.[Friend]components) @@ -141,7 +144,7 @@ namespace Strawberry private void UntrackEntity(Entity e) { - for (let t in TypeTree.[Friend]EntityAssignableLists[e.GetType()]) + for (let t in Game.[Friend]entityAssignableLists[e.GetType()]) entityTracker[t].Remove(e); for (let c in e.[Friend]components) @@ -150,13 +153,13 @@ namespace Strawberry private void TrackComponent(Component c) { - for (let t in TypeTree.[Friend]ComponentAssignableLists[c.GetType()]) + for (let t in Game.[Friend]componentAssignableLists[c.GetType()]) componentTracker[t].Add(c); } private void UntrackComponent(Component c) { - for (let t in TypeTree.[Friend]ComponentAssignableLists[c.GetType()]) + for (let t in Game.[Friend]componentAssignableLists[c.GetType()]) componentTracker[t].Remove(c); } diff --git a/src/Static/Draw.bf b/src/Static/Draw.bf index d06ba79..4eef889 100644 --- a/src/Static/Draw.bf +++ b/src/Static/Draw.bf @@ -4,10 +4,12 @@ namespace Strawberry { static public class Draw { + static public Point Camera => Game.Scene != null ? Game.Scene.Camera.Round() : Point.Zero; + static public void Rect(int x, int y, int w, int h, Color color) { SDL.SetRenderDrawColor(Game.Renderer, color.R, color.G, color.B, color.A); - SDL.RenderFillRect(Game.Renderer, &SDL.Rect((int32)x, (int32)y, (int32)w, (int32)h)); + SDL.RenderFillRect(Game.Renderer, &SDL.Rect((int32)(x - Camera.X), (int32)(y - Camera.Y), (int32)w, (int32)h)); } static public void Rect(Rect rect, Color color) @@ -18,7 +20,7 @@ namespace Strawberry static public void HollowRect(int x, int y, int w, int h, Color color) { SDL.SetRenderDrawColor(Game.Renderer, color.R, color.G, color.B, color.A); - SDL.RenderDrawRect(Game.Renderer, &SDL.Rect((int32)x, (int32)y, (int32)w, (int32)h)); + SDL.RenderDrawRect(Game.Renderer, &SDL.Rect((int32)(x - Camera.X), (int32)(y - Camera.Y), (int32)w, (int32)h)); } static public void HollowRect(Rect rect, Color color) @@ -28,8 +30,11 @@ namespace Strawberry static public void Line(Point from, Point to, Color color) { + let fromn = (Point)(from - Camera); + let ton = (Point)(to - Camera); + SDL.SetRenderDrawColor(Game.Renderer, color.R, color.G, color.B, color.A); - SDL.RenderDrawLine(Game.Renderer, (int32)from.X, (int32)from.Y, (int32)to.X, (int32)to.Y); + SDL.RenderDrawLine(Game.Renderer, (int32)fromn.X, (int32)fromn.Y, (int32)ton.X, (int32)ton.Y); } } } diff --git a/src/Static/TypeTree.bf b/src/Static/TypeTree.bf deleted file mode 100644 index f62740b..0000000 --- a/src/Static/TypeTree.bf +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections; -using System; - -namespace Strawberry -{ - static public class TypeTree - { - static private Dictionary> EntityAssignableLists; - static private Dictionary> ComponentAssignableLists; - - static private void Build() - { - /* - For each Type that extends Entity, we build a list of all the other Entity Types that it is assignable to. - We cache these lists, and use them later to bucket Entities as they are added to the Scene. - This allows us to retrieve Entities by type very easily. - */ - - EntityAssignableLists = new Dictionary>(); - for (let type in Type.Enumerator()) - { - if (type != typeof(Entity) && type.IsSubtypeOf(typeof(Entity))) - { - let list = new List(); - for (let check in Type.Enumerator()) - if (check != typeof(Entity) && check.IsSubtypeOf(typeof(Entity)) && type.IsSubtypeOf(check)) - list.Add(check); - EntityAssignableLists.Add(type, list); - } - } - - /* - And then we also do this for components - */ - - ComponentAssignableLists = new Dictionary>(); - for (let type in Type.Enumerator()) - { - if (type != typeof(Component) && type.IsSubtypeOf(typeof(Component))) - { - let list = new List(); - for (let check in Type.Enumerator()) - if (check != typeof(Component) && check.IsSubtypeOf(typeof(Component)) && type.IsSubtypeOf(check)) - list.Add(check); - ComponentAssignableLists.Add(type, list); - } - } - } - - static private void Dispose() - { - for (let list in EntityAssignableLists.Values) - delete list; - delete EntityAssignableLists; - - for (let list in ComponentAssignableLists.Values) - delete list; - delete ComponentAssignableLists; - } - } -} diff --git a/src/Struct/Matrix.bf b/src/Struct/Matrix.bf new file mode 100644 index 0000000..e356912 --- /dev/null +++ b/src/Struct/Matrix.bf @@ -0,0 +1,508 @@ +using System; +namespace Strawberry +{ + /* + Based on Matrix3x2.cs by Microsoft, under MIT license + Source: https://github.com/microsoft/referencesource/blob/master/System.Numerics/System/Numerics/Matrix3x2.cs + + Copyright (c) Microsoft. All rights reserved. + Licensed under the MIT license. + */ + + public struct Matrix + { + static public readonly Matrix Identity = Matrix(1, 0, 0, 1, 0, 0); + + // Row--vv--Column + public float M11; + public float M12; + public float M21; + public float M22; + public float M31; + public float M32; + + public this(float m11, float m12, float m21, float m22, float m31, float m32) + { + M11 = m11; + M12 = m12; + M21 = m21; + M22 = m22; + M31 = m31; + M32 = m32; + } + + public bool IsIdentity + { + get + { + return M11 == 1f && M22 == 1f && // Check diagonal element first for early out. + M12 == 0f && + M21 == 0f && + M31 == 0f && M32 == 0f; + } + } + + public Vector Translation + { + get + { + return .(M31, M32); + } + + set mut + { + M31 = value.X; + M32 = value.Y; + } + } + + public Vector Scale + { + get + { + return .(M11, M22); + } + + set mut + { + M11 = value.X; + M22 = value.Y; + } + } + + public Vector Up + { + get + { + return .(M21, -M22); + } + + set mut + { + M21 = value.X; + M22 = -value.Y; + } + } + + public Vector Down + { + get + { + return .(M21, M22); + } + + set mut + { + M21 = -value.X; + M22 = value.Y; + } + } + + public Vector Right + { + get + { + return .(M11, -M12); + } + + set mut + { + M11 = value.X; + M12 = -value.Y; + } + } + + public Vector Left + { + get + { + return .(-M11, M12); + } + + set mut + { + M11 = -value.X; + M12 = value.Y; + } + } + + public Result Inverse + { + get => Invert(this); + + set mut + { + this = Invert(value); + } + } + + //Static Helpers + + public static Matrix CreateTranslation(Vector position) + { + return Matrix( + 1, 0, + 0, 1, + position.X, position.Y + ); + } + + public static Matrix CreateScale(Vector scale) + { + return Matrix( + scale.X, 0, + 0, scale.Y, + 0, 0 + ); + } + + public static Matrix CreateScale(Vector scale, Vector origin) + { + float tx = origin.X * (1 - scale.X); + float ty = origin.Y * (1 - scale.Y); + + return Matrix( + scale.X, 0, + 0, scale.Y, + tx, ty + ); + } + + public static Matrix CreateScale(float scale) + { + return Matrix( + scale, 0, + 0, scale, + 0, 0 + ); + } + + public static Matrix CreateScale(float scale, Vector origin) + { + float tx = origin.X * (1 - scale); + float ty = origin.Y * (1 - scale); + + return Matrix( + scale, 0, + 0, scale, + tx, ty + ); + } + + public static Matrix CreateSkew(float radiansX, float radiansY) + { + float xTan = (float)Math.Tan(radiansX); + float yTan = (float)Math.Tan(radiansY); + + return Matrix( + 1, yTan, + xTan, 1, + 0, 0 + ); + } + + public static Matrix CreateSkew(float radiansX, float radiansY, Vector origin) + { + float xTan = (float)Math.Tan(radiansX); + float yTan = (float)Math.Tan(radiansY); + + float tx = -origin.Y * xTan; + float ty = -origin.X * yTan; + + return Matrix( + 1, yTan, + xTan, 1, + tx, ty + ); + } + + public static Matrix CreateRotation(float radians) + { + let rad = (float)Math.IEEERemainder(radians, Math.PI_f * 2); + + float c, s; + + const float epsilon = 0.001f * (float)Math.PI_f / 180f; // 0.1% of a degree + + if (rad > -epsilon && rad < epsilon) + { + // Exact case for zero rotation. + c = 1; + s = 0; + } + else if (rad > Math.PI_f / 2 - epsilon && rad < Math.PI_f / 2 + epsilon) + { + // Exact case for 90 degree rotation. + c = 0; + s = 1; + } + else if (rad < -Math.PI_f + epsilon || rad > Math.PI_f - epsilon) + { + // Exact case for 180 degree rotation. + c = -1; + s = 0; + } + else if (rad > -Math.PI_f / 2 - epsilon && rad < -Math.PI_f / 2 + epsilon) + { + // Exact case for 270 degree rotation. + c = 0; + s = -1; + } + else + { + // Arbitrary rotation. + c = (float)Math.Cos(rad); + s = (float)Math.Sin(rad); + } + + return Matrix( + c, s, + -s, c, + 0, 0 + ); + } + + public static Matrix CreateRotation(float radians, Vector origin) + { + let rad = (float)Math.IEEERemainder(radians, Math.PI_f * 2); + + float c, s; + + const float epsilon = 0.001f * (float)Math.PI_f / 180f; // 0.1% of a degree + + if (rad > -epsilon && rad < epsilon) + { + // Exact case for zero rotation. + c = 1; + s = 0; + } + else if (rad > Math.PI_f / 2 - epsilon && rad < Math.PI_f / 2 + epsilon) + { + // Exact case for 90 degree rotation. + c = 0; + s = 1; + } + else if (rad < -Math.PI_f + epsilon || rad > Math.PI_f - epsilon) + { + // Exact case for 180 degree rotation. + c = -1; + s = 0; + } + else if (rad > -Math.PI_f / 2 - epsilon && rad < -Math.PI_f / 2 + epsilon) + { + // Exact case for 270 degree rotation. + c = 0; + s = -1; + } + else + { + // Arbitrary rotation. + c = (float)Math.Cos(rad); + s = (float)Math.Sin(rad); + } + + float x = origin.X * (1 - c) + origin.Y * s; + float y = origin.Y * (1 - c) - origin.X * s; + + return Matrix( + c, s, + -s, c, + x, y + ); + } + + /// Calculates the determinant for this matrix. + /// The determinant is calculated by expanding the matrix with a third column whose values are (0,0,1). + public float GetDeterminant() + { + // There isn't actually any such thing as a determinant for a non-square matrix, + // but this 3x2 type is really just an optimization of a 3x3 where we happen to + // know the rightmost column is always (0, 0, 1). So we expand to 3x3 format: + // + // [ M11, M12, 0 ] + // [ M21, M22, 0 ] + // [ M31, M32, 1 ] + // + // Sum the diagonal products: + // (M11 * M22 * 1) + (M12 * 0 * M31) + (0 * M21 * M32) + // + // Subtract the opposite diagonal products: + // (M31 * M22 * 0) + (M32 * 0 * M11) + (1 * M21 * M12) + // + // Collapse out the constants and oh look, this is just a 2x2 determinant! + + return (M11 * M22) - (M21 * M12); + } + + public static Result Invert(Matrix matrix) + { + let det = (matrix.M11 * matrix.M22) - (matrix.M21 * matrix.M12); + + if (Math.Abs(det) < float.Epsilon) + return .Err; + + let invDet = 1.0f / det; + + return Matrix( + matrix.M22 * invDet, + -matrix.M12 * invDet, + + -matrix.M21 * invDet, + matrix.M11 * invDet, + + (matrix.M21 * matrix.M32 - matrix.M31 * matrix.M22) * invDet, + (matrix.M31 * matrix.M12 - matrix.M11 * matrix.M32) * invDet + ); + } + + public static Matrix Lerp(Matrix a, Matrix b, float t) + { + return Matrix( + a.M11 + (b.M11 - a.M11) * t, + a.M12 + (b.M12 - a.M12) * t, + + a.M21 + (b.M21 - a.M21) * t, + a.M22 + (b.M22 - a.M22) * t, + + a.M31 + (b.M31 - a.M31) * t, + a.M32 + (b.M32 - a.M32) * t + ); + } + + public static Matrix Negate(Matrix mat) + { + return Matrix( + -mat.M11, -mat.M12, + -mat.M21, -mat.M22, + -mat.M31, -mat.M32 + ); + } + + public static Matrix Add(Matrix a, Matrix b) + { + return Matrix( + a.M11 + b.M11, a.M12 + b.M12, + a.M21 + b.M21, a.M22 + b.M22, + a.M31 + b.M31, a.M32 + b.M32 + ); + } + + public static Matrix Subtract(Matrix a, Matrix b) + { + return Matrix( + a.M11 - b.M11, a.M12 - b.M12, + a.M21 - b.M21, a.M22 - b.M22, + a.M31 - b.M31, a.M32 - b.M32 + ); + } + + public static Matrix Multiply(Matrix a, Matrix b) + { + return Matrix( + a.M11 * b.M11 + a.M12 * b.M21, + a.M11 * b.M12 + a.M12 * b.M22, + + a.M21 * b.M11 + a.M22 * b.M21, + a.M21 * b.M12 + a.M22 * b.M22, + + a.M31 * b.M11 + a.M32 * b.M21 + b.M31, + a.M31 * b.M12 + a.M32 * b.M22 + b.M32 + ); + } + + public static Matrix Multiply(Matrix a, float scale) + { + return Matrix( + a.M11 * scale, a.M12 * scale, + a.M21 * scale, a.M22 * scale, + a.M31 * scale, a.M32 * scale + ); + } + + // Operators + + public static Matrix operator -(Matrix mat) + { + return Matrix( + -mat.M11, -mat.M12, + -mat.M21, -mat.M22, + -mat.M31, -mat.M32 + ); + } + + public static Matrix operator +(Matrix a, Matrix b) + { + return Matrix( + a.M11 + b.M11, a.M12 + b.M12, + a.M21 + b.M21, a.M22 + b.M22, + a.M31 + b.M31, a.M32 + b.M32 + ); + } + + public static Matrix operator -(Matrix a, Matrix b) + { + return Matrix( + a.M11 - b.M11, a.M12 - b.M12, + a.M21 - b.M21, a.M22 - b.M22, + a.M31 - b.M31, a.M32 - b.M32 + ); + } + + public static Matrix operator *(Matrix a, Matrix b) + { + return Matrix( + a.M11 * b.M11 + a.M12 * b.M21, + a.M11 * b.M12 + a.M12 * b.M22, + + a.M21 * b.M11 + a.M22 * b.M21, + a.M21 * b.M12 + a.M22 * b.M22, + + a.M31 * b.M11 + a.M32 * b.M21 + b.M31, + a.M31 * b.M12 + a.M32 * b.M22 + b.M32 + ); + } + + public static Matrix operator *(Matrix mat, float scale) + { + return Matrix( + mat.M11 * scale, mat.M12 * scale, + mat.M21 * scale, mat.M22 * scale, + mat.M31 * scale, mat.M32 * scale + ); + } + + public static bool operator ==(Matrix a, Matrix b) + { + return (a.M11 == b.M11 && a.M22 == b.M22 && // Check diagonal element first for early out. + a.M12 == b.M12 && + a.M21 == b.M21 && + a.M31 == b.M31 && a.M32 == b.M32); + } + + public static bool operator !=(Matrix a, Matrix b) + { + return (a.M11 != b.M11 || a.M12 != b.M12 || + a.M21 != b.M21 || a.M22 != b.M22 || + a.M31 != b.M31 || a.M32 != b.M32); + } + + public override void ToString(String strBuffer) + { + let str = scope String(); + + str.Append("[ "); + M11.ToString(str); + str.Append(", "); + M12.ToString(str); + str.Append(",\n"); + M21.ToString(str); + str.Append(", "); + M22.ToString(str); + str.Append(",\n"); + M31.ToString(str); + str.Append(", "); + M32.ToString(str); + str.Append(" ]"); + } + } +}