commit da7a4011f807975be4ebe3abcfd59c8acc3c8d08 Author: kayomn Date: Mon Feb 27 13:02:30 2023 +0000 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..24a8e87 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.png filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c179fe4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +output/ + diff --git a/assets/GalaxyMap.png b/assets/GalaxyMap.png new file mode 100644 index 0000000..f4b6bd2 --- /dev/null +++ b/assets/GalaxyMap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e08a30e022d7a566c3f2b514f26e552b682e4985c2ab561c191a2758210481f9 +size 6417883 diff --git a/assets/asteroid01.png b/assets/asteroid01.png new file mode 100644 index 0000000..7015ff6 --- /dev/null +++ b/assets/asteroid01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82c88282bb92e989da3cab214f12aa04b62dbb71b2f2caa3930b21e703ba794b +size 2918 diff --git a/assets/asteroid02.png b/assets/asteroid02.png new file mode 100644 index 0000000..0206ec8 --- /dev/null +++ b/assets/asteroid02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed8ddb8f50b5be7d229217fefe8007cf913c4b951f6581384a04996d8e355b18 +size 2834 diff --git a/assets/asteroids.spr b/assets/asteroids.spr new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/assets/asteroids.spr @@ -0,0 +1 @@ + diff --git a/assets/moon.png b/assets/moon.png new file mode 100644 index 0000000..d6650ac --- /dev/null +++ b/assets/moon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0828fd2d8f435623d75da27fd0c766c92e966983df4e3058746fe5018791223 +size 13806 diff --git a/assets/nebula.png b/assets/nebula.png new file mode 100644 index 0000000..276ef29 --- /dev/null +++ b/assets/nebula.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6db884521ef2a60a3b0e43d352c8d33df0c64752b0f5ef7357d7cac0c932b3f +size 104048 diff --git a/assets/spaceshipEngines.png b/assets/spaceshipEngines.png new file mode 100644 index 0000000..664940d --- /dev/null +++ b/assets/spaceshipEngines.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee887d8bc47355a16a5096e3a0519a6a0bde4a0a0dddf790d12e91d712d1b3ea +size 1062 diff --git a/assets/spaceshipFusilage.png b/assets/spaceshipFusilage.png new file mode 100644 index 0000000..ae252ea --- /dev/null +++ b/assets/spaceshipFusilage.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3cef076b3c57e13bb20e63190c031192129514d5110b73f3671e2cf9e98156e +size 1111 diff --git a/assets/stars_bgr.png b/assets/stars_bgr.png new file mode 100644 index 0000000..ae78684 --- /dev/null +++ b/assets/stars_bgr.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4e663f29e5289390367e501dc42fba17cca1beb7b9c684972f658d2a864d641 +size 198380 diff --git a/assets/sun.png b/assets/sun.png new file mode 100644 index 0000000..3618a6e --- /dev/null +++ b/assets/sun.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2976d232810a5fa349b2e10905a4413ba5c38077beb8a181e5148764671d5eb +size 12173 diff --git a/build/.vscode/launch.json b/build/.vscode/launch.json new file mode 100644 index 0000000..5436ec5 --- /dev/null +++ b/build/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/../output/quadrants", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/../output", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [{ + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }], + "preLaunchTask": "Build" + } + ] +} diff --git a/build/.vscode/tasks.json b/build/.vscode/tasks.json new file mode 100644 index 0000000..cb11625 --- /dev/null +++ b/build/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "type": "shell", + "command": "${workspaceFolder}/build.sh", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$gcc", + "presentation": { + "echo": true, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false, + "revealProblems": "onProblem" + } + } + ] +} diff --git a/build/build.sh b/build/build.sh new file mode 100644 index 0000000..036ff43 --- /dev/null +++ b/build/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +module_files=( + "core/core.cpp" + "main.cpp" +) + +object_files="" + +# Module to object compilation stage. +for i in "${module_files[@]}" +do + module_file=$(dirname "${i}") + object_file="../output/${module_file//\//.}.o" + echo "Compiling module: \"${i}\"" + c++ ../source/${i} -c -o ${object_file} -g -Wshadow + object_files="$object_files $object_file" +done + +# Object to executable linkage stage. +c++ -o ../output/quadrants ${object_files} -g -lraylib -lm +rm ${object_files} +cp -a ../assets/. ../output diff --git a/quadrants.code-workspace b/quadrants.code-workspace new file mode 100644 index 0000000..499e988 --- /dev/null +++ b/quadrants.code-workspace @@ -0,0 +1,17 @@ +{ + "folders": [ + { + "path": "./source" + }, + { + "path": "./assets" + }, + { + "path": "./output" + }, + { + "path": "build" + } + ] +} + diff --git a/source/.vscode/c_cpp_properties.json b/source/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..27ea3e8 --- /dev/null +++ b/source/.vscode/c_cpp_properties.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "clang-x64" + } + ], + "version": 4 +} diff --git a/source/.vscode/settings.json b/source/.vscode/settings.json new file mode 100644 index 0000000..2fefe4e --- /dev/null +++ b/source/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "*.h": "c", + "limits": "c" + } +} \ No newline at end of file diff --git a/source/core.hpp b/source/core.hpp new file mode 100644 index 0000000..f6282a8 --- /dev/null +++ b/source/core.hpp @@ -0,0 +1,69 @@ +#include + +#define HALF_PI (PI / 2) + +#define TAU (PI * 2) + +#define V2_ZERO (Vector2){0.0f, 0.0f} + +#define LENGTH_OF(a) ((sizeof(a) / sizeof(*(a))) / (size_t)(!(sizeof(a) % sizeof(*(a))))) + +#define MAX(a, b) ((a > b) ? a : b) + +#define MIN(a, b) ((a < b) ? a : b) + +#define CLAMP(value, min, max) MIN(max, MAX(min, value)) + +#define SIGN(value) ((0 < value) - (value < 0)) + +typedef long unsigned Seed; + +struct XorShifter { + Seed seed; +}; + +template T const wrap(T const value, T const min, T const max) { + return ((value < min) ? max : (value > max) ? min : value); +} + +Vector2 operator +(Vector2, Vector2); + +Vector2 operator +=(Vector2&, Vector2); + +Vector2 operator -(Vector2, Vector2); + +Vector2 operator -=(Vector2&, Vector2); + +Vector2 operator *(Vector2, Vector2); + +Vector2 operator *=(Vector2&, Vector2); + +Vector2 operator *(Vector2, float const); + +Vector2 operator /(Vector2, Vector2); + +Vector2 operator /=(Vector2&, Vector2); + +Vector2 operator /(Vector2, float const); + +bool operator <(Vector2, Vector2); + +bool operator >(Vector2, Vector2); + +float Vector2Distance(Vector2, Vector2); + +float const LengthDirectionX(float const, float const); + +float const LengthDirectionY(float const, float const); + +Vector2 const LengthDirection(Vector2 const, float const); + +float const AngleToPoint(Vector2 const, Vector2 const); + +float const WrapAngle(float const); + +float const AngleDifference(float const, float const); + +XorShifter CreateXorShifter(Seed const); + +long const GetXorShifterValue(XorShifter* const, long const, long const); diff --git a/source/core/core.cpp b/source/core/core.cpp new file mode 100644 index 0000000..ffe0ecc --- /dev/null +++ b/source/core/core.cpp @@ -0,0 +1,2 @@ +#include "math.cpp" +#include "random.cpp" diff --git a/source/core/math.cpp b/source/core/math.cpp new file mode 100644 index 0000000..089a4e6 --- /dev/null +++ b/source/core/math.cpp @@ -0,0 +1,118 @@ +#include +#include + +Vector2 operator +(Vector2 vectorA, Vector2 vectorB) { + return (Vector2){(vectorA.x + vectorB.x), (vectorA.y + vectorB.y)}; +} + +Vector2 operator +=(Vector2& vectorA, Vector2 vectorB) { + vectorA.x += vectorB.x; + vectorA.y += vectorB.y; + + return vectorA; +} + +Vector2 operator -(Vector2 vectorA, float const value) { + return (Vector2){(vectorA.x - value), (vectorA.y - value)}; +} + +Vector2 operator -(Vector2 vectorA, Vector2 vectorB) { + return (Vector2){(vectorA.x - vectorB.x), (vectorA.y - vectorB.y)}; +} + +Vector2 operator -=(Vector2& vectorA, Vector2 vectorB) { + vectorA.x -= vectorB.x; + vectorA.y -= vectorB.y; + + return vectorA; +} + +Vector2 operator *(Vector2 vectorA, float const value) { + return (Vector2){(vectorA.x * value), (vectorA.y * value)}; +} + +Vector2 operator *(Vector2 vectorA, Vector2 vectorB) { + return (Vector2){(vectorA.x * vectorB.x), (vectorA.y * vectorB.y)}; +} + +Vector2 operator *=(Vector2& vectorA, Vector2 vectorB) { + vectorA.x *= vectorB.x; + vectorA.y *= vectorB.y; + + return vectorA; +} + +Vector2 operator /(Vector2 vectorA, float const value) { + return (Vector2){(vectorA.x / value), (vectorA.y / value)}; +} + +Vector2 operator /(Vector2 vectorA, Vector2 vectorB) { + return (Vector2){(vectorA.x / vectorB.x), (vectorA.y / vectorB.y)}; +} + +Vector2 operator /=(Vector2& vectorA, Vector2 vectorB) { + vectorA.x /= vectorB.x; + vectorA.y /= vectorB.y; + + return vectorA; +} + +float Vector2Distance(Vector2 vectorA, Vector2 vectorB) { + float const xDifference = (vectorB.x - vectorA.x); + float const yDifference = (vectorB.y - vectorA.y); + + return sqrt((xDifference * xDifference) + (yDifference * yDifference)); +} + +/** + * Computes and returns the X component of a point translation given its `magnitude` and radian + * `angle`. + */ +float const LengthDirectionX(float const magnitude, float const angle) { + return (magnitude * -cos(angle)); +} + +/** + * Computes and returns the Y component of a point translation given its `magnitude` and radian + * `angle`. + */ +float const LengthDirectionY(float const magnitude, float const angle) { + return (magnitude * -sin(angle)); +} + +Vector2 const LengthDirection(Vector2 const lengths, float const direction) { + return (Vector2){ + LengthDirectionX(lengths.x, direction), + LengthDirectionY(lengths.y, direction) + }; +} + +float const AngleToPoint(Vector2 const originPoint, Vector2 const targetPoint) { + return atan2((originPoint.y - targetPoint.y), (originPoint.x - targetPoint.x)); +} + +/** + * Wraps a radian `angle` by its bounds, with any value less than `-PI` returning `PI` and any + * value greater than `PI` returning `-PI`. + */ +float const WrapAngle(float const angle) { + if (angle > PI) { + return -PI; + } else if (angle < -PI) { + return PI; + } + + return angle; +} + +/** + * Calculates the linear interpolation between `angleStart` to `angleTo` with the given + * `magnitude`, which is expected to be a normalized value representing a percentage of the + * transformation, with `0.0f` being pure `angleStart` and `1.0f` being a very close aproximation + * of `angleTo`. + */ +float const AngleDifference(float const angleStart, float const angleTo) { + float const difference = (angleTo - angleStart); + + return atan2(sin(difference), cos(difference)); +} diff --git a/source/core/random.cpp b/source/core/random.cpp new file mode 100644 index 0000000..f4ca16e --- /dev/null +++ b/source/core/random.cpp @@ -0,0 +1,48 @@ +#include + +/** + * Raw memory type used for representing the maximum random size of a value. + */ +typedef long unsigned Seed; + +/** + * State machine for an instance of XOR shift-based pseudo-random number generation. + */ +typedef struct XorShifter { + /** + * Initial seed state iniailized when creating the pseudo-random number generator, set by + * either manually arranging the memory or by calling [CreateXorShifter(seed_t const)]. Every + * time a new random number is generated, usually by calling + * [GetXorShifterValue(XorShifter* const, long const, long const)], this value will be mutated + * with the new iteration value. + */ + Seed seed; +} XorShifter; + +/** + * Creates a [XorShifter] with the given `seed` as initial entropy. Note that like all pseudo- + * random number generators, the initial seed makes all sequential operations deterministic to the + * value. I.e. running the same program twice with the same seed will yield the same results. To + * get more entropic values either pass in the current UNIX timestamp value of the device or poll + * the device's system-dependant entropy device for a new seed each run. + */ +XorShifter CreateXorShifter(Seed const seed) { + return (XorShifter){seed}; +} + +/** + * Returns the next pseudo-random iteration in the `xorShifter`'s seeded sequence, reduced down a + * range between `min` and `max`, provided the `xorShifter`'s seed is greater than `0`. + */ +long const GetXorShifterValue(XorShifter* const xorShifter, long const min, long const max) { + if (xorShifter->seed) { + Seed value = xorShifter->seed; + value ^= value << 13; + value ^= value >> 17; + value ^= value << 5; + + return labs((long)(xorShifter->seed = value)) % (labs(max - min) + 1) + min; + } + + return 0; +} diff --git a/source/entity.cpp b/source/entity.cpp new file mode 100644 index 0000000..6fd8adc --- /dev/null +++ b/source/entity.cpp @@ -0,0 +1,48 @@ +#include "core.hpp" + +struct QuadrantCoords { + long x; + long y; +}; + +struct Entity { + float orientation; + Vector2 speed; + Vector2 localPosition; + QuadrantCoords quadrantPosition; +}; + +struct SpaceshipEntity { + Entity entity; + float delayedOrientation; + Texture fusilageTexture; + Texture enginesTexture; +}; + +Vector2 const AddEntityForce(Entity& entity, float const direction, float const magnitude) { + constexpr float MAGNITUDE_MAX = 0.1f; + Vector2 const appliedForce = LengthDirection(Vector2{magnitude, magnitude}, direction); + entity.speed = (entity.speed + appliedForce); + + return appliedForce; +} + +void RemoveEntityForce(Entity& entity, float const magnitude) { + constexpr float MIN_DRIFT = 0.001f; + // As LengthDirection deals in vector math, the magnitude will have to be converted from a one- + // dimensional value into a two-dimensional one. Additionally, this magnitude vector needs its + // x and y flipping according to the relative x and y values of the subject Entity. + Vector2 const relativeMagnitude = { + (magnitude * (SIGN(entity.speed.x) * -1)), + (magnitude * (SIGN(entity.speed.y) * -1)) + }; + + // Only deduct the maximum possible deductable speed before the value would inverse on itself. + entity.speed = (entity.speed + Vector2{ + (entity.speed.x < 0.0f) ? MAX(relativeMagnitude.x, entity.speed.x) : + MIN(relativeMagnitude.x, entity.speed.x), + + (entity.speed.y < 0.0f) ? MAX(relativeMagnitude.y, entity.speed.y) : + MIN(relativeMagnitude.y, entity.speed.y) + }); +} diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..6d8f95c --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,164 @@ +#include +#include + +#include "world.cpp" + +Texture SpaceshipFusilageTexture; + +Texture SpaceshipEnginesTexture; + +Texture AsteroidTexture; + +void LoadAssets() { + SpaceshipFusilageTexture = LoadTexture("./spaceshipFusilage.png"); + SpaceshipEnginesTexture = LoadTexture("./spaceshipEngines.png"); +} + +int main(int argc, char const** argv) { + int const viewportWidth = 640; + int const viewportHeight = 360; + Vector2 const viewportOrigin = Vector2{(viewportWidth / 2), (viewportHeight / 2)}; + int unsigned const windowFlags = 0; + int unsigned const rendererFlags = 0; + + InitWindow(viewportWidth, viewportHeight, "Quadrants"); + HideCursor(); + LoadAssets(); + + Texture nebulaTexture = LoadTexture("./nebula.png"); + Texture sunTexture = LoadTexture("./sun.png"); + Texture moonTexture = LoadTexture("./moon.png"); + Texture asteroidTexture = LoadTexture("./asteroid01.png"); + Worldspace world = CreateWorldspace(10); + Camera2D camera = {0}; + camera.target = viewportOrigin; + camera.zoom = 1.0f; + + GenerateWorldspace(world); + SetTargetFPS(60); + + while (!WindowShouldClose()) { + float const scale = (1.0f + (1.0f - camera.zoom)); + Vector2 const mousePos = GetMousePosition(); + int const mouseWheel = GetMouseWheelMove(); + float const lookAtEngageDistance = 32.0f; + float const accel = 0.005f; + + // Relative position checks. + if (Vector2Distance(mousePos, viewportOrigin) > lookAtEngageDistance) { + float const lerpStep = + AngleDifference(world.player.entity.orientation, AngleToPoint(viewportOrigin, mousePos)); + + world.player.delayedOrientation = WrapAngle(world.player.entity.orientation + (lerpStep * 0.0005f)); + world.player.entity.orientation = WrapAngle(world.player.entity.orientation + (lerpStep * 0.2f)); + } + + if (IsKeyDown(KEY_W)) { + AddEntityForce(world.player.entity, world.player.entity.orientation, accel); + } + + if (IsKeyDown(KEY_A)) { + AddEntityForce(world.player.entity, (world.player.entity.orientation - HALF_PI), accel); + } + + if (IsKeyDown(KEY_S)) { + AddEntityForce(world.player.entity, (world.player.entity.orientation + PI), accel); + } + + if (IsKeyDown(KEY_D)) { + AddEntityForce(world.player.entity, (world.player.entity.orientation + HALF_PI), accel); + } + + if (IsKeyDown(KEY_SPACE)) { + RemoveEntityForce(world.player.entity, accel); + } + + camera.target = Vector2{-mousePos.x, -mousePos.y}; + + if (mouseWheel) { + camera.zoom = CLAMP(camera.zoom - (((float)mouseWheel) * 0.05f), 0.5f, 1.5f); + } + + EntityUpdateInfo const updateInfo = UpdateEntity(world.player.entity); + + if (updateInfo.hasMoved) { + Vector2 const nebulaParallaxIncrement = (world.nebulaParallaxScroll + (world.player.entity.speed * 0.005f)); + world.nebulaParallaxScroll.x = wrap(nebulaParallaxIncrement.x, 0.0f, ((float)nebulaTexture.width)); + world.nebulaParallaxScroll.y = wrap(nebulaParallaxIncrement.y, 0.0f, ((float)nebulaTexture.height)); + + if (updateInfo.hasPassedQuadrant) { + GenerateWorldspace(world); + } + } + + BeginDrawing(); + { + ClearBackground(BLACK); + BeginMode2D(camera); + { + Vector2 const quadrantOffsets[] = { + {-QUADRANT_SIZE, -QUADRANT_SIZE}, + {0.0f, -QUADRANT_SIZE}, + {QUADRANT_SIZE, -QUADRANT_SIZE}, + {-QUADRANT_SIZE, 0.0f}, + {0.0f, 0.0f}, + {QUADRANT_SIZE, 0.0f}, + {-QUADRANT_SIZE, QUADRANT_SIZE}, + {0.0f, QUADRANT_SIZE}, + {QUADRANT_SIZE, QUADRANT_SIZE} + }; + + // DrawTextureQuad(nebulaTexture, Vector2{1, 1}, world.nebulaParallaxScroll, Rectangle{0, 0, viewportWidth, viewportHeight}, RED); + + for (size_t i = 0; i < 9; i += 1) { + Worldspace::Quadrant const* quadrant = (&world.loadedQuadrantMap[i]); + Vector2 const quadrantOffset = (viewportOrigin + quadrantOffsets[i]); + + for (size_t j = 0; j < quadrant->starCount; j += 1) { + DrawCircleV(((quadrantOffset + quadrant->stars[j]) - world.player.entity.localPosition), 1.0f, WHITE); + } + + for (size_t j = 0; j < quadrant->orbitalCount; j += 1) { + Worldspace::Orbital const quadrantOrbital = quadrant->orbitals[j]; + + DrawTextureEx( + asteroidTexture, + ((quadrantOffset + quadrantOrbital.localPosition) - world.player.entity.localPosition), + (quadrantOrbital.orientation * RAD2DEG), + quadrantOrbital.scale, + WHITE + ); + } + } + + // Draw player. + DrawTexturePro( + SpaceshipEnginesTexture, + (Rectangle){0, 0, ((float)SpaceshipEnginesTexture.width), ((float)SpaceshipEnginesTexture.height)}, + (Rectangle){viewportOrigin.x, viewportOrigin.y, ((float)SpaceshipEnginesTexture.width), ((float)SpaceshipEnginesTexture.height)}, + (Vector2){(float)(SpaceshipEnginesTexture.width / 2), (float)(SpaceshipEnginesTexture.height / 2)}, + (world.player.delayedOrientation * RAD2DEG), + WHITE + ); + + DrawTexturePro( + SpaceshipFusilageTexture, + (Rectangle){0, 0, (float)(SpaceshipFusilageTexture.width), (float)(SpaceshipFusilageTexture.height)}, + (Rectangle){viewportOrigin.x, viewportOrigin.y, ((float)SpaceshipFusilageTexture.width), ((float)SpaceshipFusilageTexture.height)}, + (Vector2){(float)(SpaceshipFusilageTexture.width / 2), (float)(SpaceshipFusilageTexture.height / 2)}, + (world.player.entity.orientation * RAD2DEG), + WHITE + ); + } + EndMode2D(); + // Draw UI. + DrawCircleLines(((int)mousePos.x), ((int)mousePos.y), 10.0f, WHITE); + DrawText(TextFormat("Scale: %f", scale), 0, 0, 22, WHITE); + } + EndDrawing(); + } + + CloseWindow(); + + return 0; +} diff --git a/source/world.cpp b/source/world.cpp new file mode 100644 index 0000000..6a92a55 --- /dev/null +++ b/source/world.cpp @@ -0,0 +1,153 @@ +#include "entity.cpp" +#include + +constexpr struct { + size_t min; + size_t max; +} QUADRANT_STARS = {100, 300}; + +constexpr struct { + size_t min; + size_t max; +} QUADRANT_ORBITALS = {0, 7}; + +constexpr auto QUADRANT_RANGE = 512; + +constexpr auto QUADRANT_SIZE = (QUADRANT_RANGE * 2); + +struct EntityUpdateInfo { + bool hasPassedQuadrant; + + bool hasMoved; +}; + +struct Worldspace { + enum struct OrbitalType { + SUN, + MOON + }; + + struct Orbital { + OrbitalType type; + Vector2 localPosition; + float orientation; + float scale; + }; + + struct Quadrant { + QuadrantCoords id; + size_t starCount; + size_t orbitalCount; + Vector2 stars[QUADRANT_STARS.max]; + Orbital orbitals[QUADRANT_ORBITALS.max]; + }; + + Seed seed; + SpaceshipEntity player; + Vector2 nebulaParallaxScroll; + Quadrant loadedQuadrantMap[9]; +}; + +Worldspace const CreateWorldspace(Seed const seed) { + Worldspace world = {0}; + world.seed = seed; + world.player.entity.localPosition = Vector2{0, 0}; + + return world; +} + +void GenerateWorldspace(Worldspace& world) { + QuadrantCoords offset = {-1, -1}; + + memset(((void*)world.loadedQuadrantMap), 0, (sizeof(Worldspace::Quadrant) * 9)); + + for (size_t i = 0; i < 3; i += 1) { + for (size_t j = 0; j < 3; j += 1) { + Worldspace::Quadrant* quadrant = (&world.loadedQuadrantMap[j + (i * 3)]); + + QuadrantCoords const quadrantId = { + (world.player.entity.quadrantPosition.x + offset.x), + (world.player.entity.quadrantPosition.y + offset.y) + }; + + quadrant->id = quadrantId; + + XorShifter seededGenerator = CreateXorShifter( + (QUADRANT_SIZE - (quadrantId.x + quadrantId.y)) * world.seed); + + quadrant->starCount = + (size_t const)GetXorShifterValue(&seededGenerator, QUADRANT_STARS.min, QUADRANT_STARS.max); + + quadrant->orbitalCount = (size_t const)GetXorShifterValue( + &seededGenerator, QUADRANT_ORBITALS.min, QUADRANT_ORBITALS.max); + + for (size_t k = 0; k < quadrant->starCount; k += 1) { + quadrant->stars[k] = (Vector2){ + (float const)GetXorShifterValue(&seededGenerator, -QUADRANT_RANGE, QUADRANT_RANGE), + (float const)GetXorShifterValue(&seededGenerator, -QUADRANT_RANGE, QUADRANT_RANGE) + }; + } + + for (size_t k = 0; k < quadrant->orbitalCount; k += 1) { + quadrant->orbitals[k] = (Worldspace::Orbital){ + (Worldspace::OrbitalType)GetXorShifterValue(&seededGenerator, 0, 1), + (Vector2){ + (float const)GetXorShifterValue(&seededGenerator, -QUADRANT_RANGE, QUADRANT_RANGE), + (float const)GetXorShifterValue(&seededGenerator, -QUADRANT_RANGE, QUADRANT_RANGE) + }, + (float const)GetXorShifterValue(&seededGenerator, -PI, PI), + (((float const)GetXorShifterValue(&seededGenerator, 10, 100)) / 100.0f) + }; + } + + offset.x += 1; + } + + offset.x = -1; + offset.y += 1; + } +} + +bool const CheckEntityQuadrantBounds(Entity& entity, int* const xBounds, int* const yBounds) { + int const x = ((entity.localPosition.x > QUADRANT_RANGE) ? + 1 : ((entity.localPosition.x < -QUADRANT_RANGE) ? -1 : 0)); + + int const y = ((entity.localPosition.y > QUADRANT_RANGE) ? + 1 : ((entity.localPosition.y < -QUADRANT_RANGE) ? -1 : 0)); + + if (xBounds) { + *xBounds = x; + } + + if (yBounds) { + *yBounds = y; + } + + return !(x || y); +} + +EntityUpdateInfo const UpdateEntity(Entity& entity) { + int quadrantBoundsCheckX; + int quadrantBoundsCheckY; + EntityUpdateInfo info = {0}; + + if (CheckEntityQuadrantBounds(entity, &quadrantBoundsCheckX, &quadrantBoundsCheckY)) { + entity.localPosition += entity.speed; + info.hasMoved = true; + } else { + if (quadrantBoundsCheckX) { + entity.localPosition.x = ((QUADRANT_RANGE * -quadrantBoundsCheckX) + entity.speed.x); + entity.quadrantPosition.x += quadrantBoundsCheckX; + } + + if (quadrantBoundsCheckY) { + entity.localPosition.y = ((QUADRANT_RANGE * -quadrantBoundsCheckY) + entity.speed.y); + entity.quadrantPosition.y += quadrantBoundsCheckY; + } + + info.hasPassedQuadrant = true; + info.hasMoved = true; + } + + return info; +}