Initial commit

This commit is contained in:
kayomn 2023-02-27 13:02:30 +00:00
commit da7a4011f8
25 changed files with 742 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.png filter=lfs diff=lfs merge=lfs -text

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
output/

BIN
assets/GalaxyMap.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/asteroid01.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/asteroid02.png (Stored with Git LFS) Normal file

Binary file not shown.

1
assets/asteroids.spr Normal file
View File

@ -0,0 +1 @@

BIN
assets/moon.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/nebula.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/spaceshipEngines.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/spaceshipFusilage.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/stars_bgr.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/sun.png (Stored with Git LFS) Normal file

Binary file not shown.

23
build/.vscode/launch.json vendored Normal file
View File

@ -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"
}
]
}

24
build/.vscode/tasks.json vendored Normal file
View File

@ -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"
}
}
]
}

23
build/build.sh Normal file
View File

@ -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

17
quadrants.code-workspace Normal file
View File

@ -0,0 +1,17 @@
{
"folders": [
{
"path": "./source"
},
{
"path": "./assets"
},
{
"path": "./output"
},
{
"path": "build"
}
]
}

16
source/.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}

6
source/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"files.associations": {
"*.h": "c",
"limits": "c"
}
}

69
source/core.hpp Normal file
View File

@ -0,0 +1,69 @@
#include <raylib.h>
#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<typename T> 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);

2
source/core/core.cpp Normal file
View File

@ -0,0 +1,2 @@
#include "math.cpp"
#include "random.cpp"

118
source/core/math.cpp Normal file
View File

@ -0,0 +1,118 @@
#include <math.h>
#include <raylib.h>
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));
}

48
source/core/random.cpp Normal file
View File

@ -0,0 +1,48 @@
#include <stdlib.h>
/**
* 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;
}

48
source/entity.cpp Normal file
View File

@ -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)
});
}

164
source/main.cpp Normal file
View File

@ -0,0 +1,164 @@
#include <string.h>
#include <stdio.h>
#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;
}

153
source/world.cpp Normal file
View File

@ -0,0 +1,153 @@
#include "entity.cpp"
#include <string.h>
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;
}