summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Kosarek <mattkae@protonmail.com>2021-04-10 21:54:43 -0400
committerMatthew Kosarek <mattkae@protonmail.com>2021-04-10 21:54:43 -0400
commit88a08ee48cbbae086ddbfeaff0679bfe4fe6ce47 (patch)
treeb62ec45eeb42b76ea1dae72d3cd8cea614c0194f
parent756b9fdefc1a28ac46aa4b76676c7ffb57c9e11a (diff)
Added a transpiler that will make it so that we no longer need any JavaScript of Jquery in our App
-rw-r--r--frontend/2d/_collisions/pill_line.html120
-rw-r--r--frontend/2d/_collisions/pill_line.html.content26
-rw-r--r--frontend/2d/_collisions/pill_line/main.cpp6
-rw-r--r--frontend/index.html69
-rw-r--r--frontend/index.html.content31
-rwxr-xr-xfrontend/run_transpiler.sh1
-rw-r--r--frontend/transpiler/List.h222
-rw-r--r--frontend/transpiler/Logger.cpp123
-rw-r--r--frontend/transpiler/Logger.h43
-rw-r--r--frontend/transpiler/MathHelper.h33
-rw-r--r--frontend/transpiler/MyString.cpp392
-rw-r--r--frontend/transpiler/MyString.h76
-rwxr-xr-xfrontend/transpiler/build.sh1
-rw-r--r--frontend/transpiler/pages.txt14
-rwxr-xr-xfrontend/transpiler/transpilerbin0 -> 38744 bytes
-rw-r--r--frontend/transpiler/transpiler.cpp249
16 files changed, 1342 insertions, 64 deletions
diff --git a/frontend/2d/_collisions/pill_line.html b/frontend/2d/_collisions/pill_line.html
index 5152ba6..4cbefde 100644
--- a/frontend/2d/_collisions/pill_line.html
+++ b/frontend/2d/_collisions/pill_line.html
@@ -1,46 +1,78 @@
<!DOCTYPE html>
<html lang="en">
- <head>
- <meta charset="utf-8">
- <script src="/scripts/jquery-3.5.1.min.js"></script>
- <script src="/index.js"></script>
- <link rel="stylesheet" href="/index.css">
- <link rel="shortcut icon" href="/favicon/favicon.ico" type="image/x-icon">
- <script src="./pill_line/dist/output.js"></script>
-
- <title>Physics for Games</title>
- </head>
- <body>
- <header>
- <h1>Physics for Games</h1>
- </header>
- <main>
- <nav></nav>
- <section>
- <h1>Pill-Line</h1>
- <article>
- <p>
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
- </p>
- <div class="opengl_canvas_container">
- <canvas id="gl_canvas" width="640" height="480"></canvas>
- <button id="gl_canvas_play" class="play_button">
- Play
- </button>
- <button id="gl_canvas_stop" class="stop_button">
- Stop
- </button>
- </div>
- <footer id="references">
- <h2>References</h2>
- <ul>
- <li><a href="http://csharphelper.com/blog/2017/08/calculate-where-a-line-segment-and-an-ellipse-intersect-in-c/#intersection_code">Line Segment-Ellipse Intersection</a></li>
- <li><a href="https://www.maa.org/external_archive/joma/Volume8/Kalman/TransformedEqn.html">Translated Ellipse Equation</a></li>
- <li><a href="https://www.maa.org/external_archive/joma/Volume8/Kalman/General.html">General Ellipse Equation</a></li>
- </ul>
- </footer>
- </article>
- </section>
- </main>
- </body>
-</html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="/index.css">
+ <title>Physics for Games</title>
+ <link rel="shortcut icon" href="favicon/favicon.ico" type="image/x-icon">
+ </head>
+ <body>
+ <header>
+ <h1>Physics for Games</h1>
+ </header>
+ <main>
+ <nav>
+ <ul class="outer-tree">
+ <li><a href="/">Introduction</a></li>
+ <li>
+ <span>&#127936;<span>2D</span></span>
+ <ul class="inner-tree">
+ <li><label>Rigidbody</label></li>
+ <li><a href="/2d/_rigidbody/part_1.html">Linear Forces</a></li>
+ <li><a href="/2d/_rigidbody/part_2.html">Rotational Forces</a></li>
+ <li><a href="/2d/_rigidbody/part_3.html">Collision Forces</a></li>
+ <li><label>Collisions</label></li>
+ <li><a href="/2d/_collisions/circle_line.html">Circle-Line</a></li>
+ <li><a href="/2d/_collisions/rectangle_line.html">Rectangle-Line</a></li>
+ <li><a href="/2d/_collisions/pill_line.html">Pill-Line</a></li>
+ </ul>
+ </li>
+ <li>
+ <span>&#127776;<span>3D</span></span>
+ <ul class="inner-tree">
+ </ul>
+ </li>
+ <li>
+ <span>&#128295;<span>WebAssembly</span></span>
+ <ul class="inner-tree">
+ <li><a href="/intro/intro.html">Introduction</a></li>
+ </ul>
+ </li>
+ <li>
+ <span>&#128712;<span>About</span></span>
+ <ul class="inner-tree">
+ <li><a href="/roadmap.html">Roadmap</a></li>
+ </ul>
+ </li>
+ </ul>
+ </nav>
+ <script src="./pill_line/dist/output.js"></script>
+ <section>
+ <h1>Pill-Line</h1>
+ <article>
+ <p>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ </p>
+ <div class="opengl_canvas_container">
+ <canvas id="gl_canvas" width="640" height="480"></canvas>
+ <button id="gl_canvas_play" class="play_button">
+ Play
+ </button>
+ <button id="gl_canvas_stop" class="stop_button">
+ Stop
+ </button>
+ </div>
+ <footer id="references">
+ <h2>References</h2>
+ <ul>
+ <li><a href="http://csharphelper.com/blog/2017/08/calculate-where-a-line-segment-and-an-ellipse-intersect-in-c/#intersection_code">Line Segment-Ellipse Intersection</a></li>
+ <li><a href="https://www.maa.org/external_archive/joma/Volume8/Kalman/TransformedEqn.html">Translated Ellipse Equation</a></li>
+ <li><a href="https://www.maa.org/external_archive/joma/Volume8/Kalman/General.html">General Ellipse Equation</a></li>
+ </ul>
+ </footer>
+ </article>
+ </section>
+ <li>
+  </main>
+ </body>
+</html>
diff --git a/frontend/2d/_collisions/pill_line.html.content b/frontend/2d/_collisions/pill_line.html.content
new file mode 100644
index 0000000..fb2994c
--- /dev/null
+++ b/frontend/2d/_collisions/pill_line.html.content
@@ -0,0 +1,26 @@
+ <script src="./pill_line/dist/output.js"></script>
+ <section>
+ <h1>Pill-Line</h1>
+ <article>
+ <p>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ </p>
+ <div class="opengl_canvas_container">
+ <canvas id="gl_canvas" width="640" height="480"></canvas>
+ <button id="gl_canvas_play" class="play_button">
+ Play
+ </button>
+ <button id="gl_canvas_stop" class="stop_button">
+ Stop
+ </button>
+ </div>
+ <footer id="references">
+ <h2>References</h2>
+ <ul>
+ <li><a href="http://csharphelper.com/blog/2017/08/calculate-where-a-line-segment-and-an-ellipse-intersect-in-c/#intersection_code">Line Segment-Ellipse Intersection</a></li>
+ <li><a href="https://www.maa.org/external_archive/joma/Volume8/Kalman/TransformedEqn.html">Translated Ellipse Equation</a></li>
+ <li><a href="https://www.maa.org/external_archive/joma/Volume8/Kalman/General.html">General Ellipse Equation</a></li>
+ </ul>
+ </footer>
+ </article>
+ </section>
diff --git a/frontend/2d/_collisions/pill_line/main.cpp b/frontend/2d/_collisions/pill_line/main.cpp
index 5aa0059..76dc7d4 100644
--- a/frontend/2d/_collisions/pill_line/main.cpp
+++ b/frontend/2d/_collisions/pill_line/main.cpp
@@ -11,6 +11,7 @@
#include <cmath>
// Side note: It is Eastertime, so I chose this easter color palette. Enjoy: https://htmlcolors.com/palette/144/easter
+
struct Rigidbody {
Vector2 force = { 0, 0 };
Vector2 velocity = { 0, 0 };
@@ -170,6 +171,7 @@ void load();
void update(float32 time, void* userData);
void unload();
IntersectionResult getIntersection(Pill* pill, LineSegment* segment);
+void resolveCollision(Rigidbody* first, Rigidbody* second, IntersectionResult* ir);
// Global Variables
WebglContext context;
@@ -259,6 +261,10 @@ IntersectionResult getIntersection(Pill* pill, LineSegment* segment) {
return result;
}
+void resolveCollision(Rigidbody* first, Rigidbody* second, IntersectionResult* ir) {
+
+}
+
void update(float32 deltaTimeSeconds, void* userData) {
// Input
diff --git a/frontend/index.html b/frontend/index.html
index 61d145e..73b3b21 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,21 +1,51 @@
<!DOCTYPE html>
<html lang="en">
- <head>
- <meta charset="utf-8">
- <script src="scripts/jquery-3.5.1.min.js"></script>
- <script src="index.js"></script>
- <link rel="stylesheet" href="index.css">
-
- <title>Physics for Games</title>
- <link rel="shortcut icon" href="favicon/favicon.ico" type="image/x-icon">
- </head>
- <body>
- <header>
- <h1>Physics for Games</h1>
- </header>
- <main>
- <nav>
- </nav>
+ <head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="/index.css">
+ <title>Physics for Games</title>
+ <link rel="shortcut icon" href="favicon/favicon.ico" type="image/x-icon">
+ </head>
+ <body>
+ <header>
+ <h1>Physics for Games</h1>
+ </header>
+ <main>
+ <nav>
+ <ul class="outer-tree">
+ <li><a href="/">Introduction</a></li>
+ <li>
+ <span>&#127936;<span>2D</span></span>
+ <ul class="inner-tree">
+ <li><label>Rigidbody</label></li>
+ <li><a href="/2d/_rigidbody/part_1.html">Linear Forces</a></li>
+ <li><a href="/2d/_rigidbody/part_2.html">Rotational Forces</a></li>
+ <li><a href="/2d/_rigidbody/part_3.html">Collision Forces</a></li>
+ <li><label>Collisions</label></li>
+ <li><a href="/2d/_collisions/circle_line.html">Circle-Line</a></li>
+ <li><a href="/2d/_collisions/rectangle_line.html">Rectangle-Line</a></li>
+ <li><a href="/2d/_collisions/pill_line.html">Pill-Line</a></li>
+ </ul>
+ </li>
+ <li>
+ <span>&#127776;<span>3D</span></span>
+ <ul class="inner-tree">
+ </ul>
+ </li>
+ <li>
+ <span>&#128295;<span>WebAssembly</span></span>
+ <ul class="inner-tree">
+ <li><a href="/intro/intro.html">Introduction</a></li>
+ </ul>
+ </li>
+ <li>
+ <span>&#128712;<span>About</span></span>
+ <ul class="inner-tree">
+ <li><a href="/roadmap.html">Roadmap</a></li>
+ </ul>
+ </li>
+ </ul>
+ </nav>
<section>
<h1>Introduction: Rigid Body Physics</h1>
<article>
@@ -46,7 +76,6 @@
n' References page of the website.
</p>
</article>
- </section>
- </main>
- </body>
-</html> \ No newline at end of file
+ </section> </main>
+ </body>
+</html>
diff --git a/frontend/index.html.content b/frontend/index.html.content
new file mode 100644
index 0000000..fa393b4
--- /dev/null
+++ b/frontend/index.html.content
@@ -0,0 +1,31 @@
+ <section>
+ <h1>Introduction: Rigid Body Physics</h1>
+ <article>
+ <p>
+ You're most likely here because you have some interest in the world of rigid body physics. Maybe you have some knowledge of rendering via OpenGL or Vulkan,
+ and you want to begin watching your up-until-now static scene come to life. Well, you're in the right place! In the course of this tutorial series I will walk
+ you through the entirety of a 2D rigid body physics system entirely in the web. All of this information will be extendable to other languages, but we will use
+ JavaScript and WebGL in these blog posts. Additionally, much of the information presented here can be extended to 3 dimensions, but 3D carries some complications
+ with it, that we will discuss in future blog posts.
+ </p>
+ <p>
+ In implementing a rigidy body physics system, we're primarily interested in two sub-fields of physics, namely <b>dynamics</b> and <b>kinematics</b>. Although I'm
+ far as can be from being an expert in either of these fields, I will explain - from a programmer's persepctive - what they mean to me:
+ <ul>
+ <li>
+ <b>Kinematics</b> is the study of how an object's movement changes over time. These are the classic position, velocity, and acceleration equations
+ that you're most likely familiar with from high school or college physics.
+ </li>
+ <li>
+ <b>Dynamics</b> is the study of whats <i>causes</i> kinematic movement. These are the classic force and momentum equations that you may already be familiar
+ with as well.
+ </li>
+ </ul>
+ </p>
+ <p>
+ Finally, I must provide a disclaimer that all of rigid body systems are very math-y. You will need to know a decent amount of vector calculus and linear algebra to really understand
+ what's going on here. I am going to assume that you have this knowledge. If you don't already have this knowledge, I will try and provide some resources on the Books
+ n' References page of the website.
+ </p>
+ </article>
+ </section> \ No newline at end of file
diff --git a/frontend/run_transpiler.sh b/frontend/run_transpiler.sh
new file mode 100755
index 0000000..4d51ba6
--- /dev/null
+++ b/frontend/run_transpiler.sh
@@ -0,0 +1 @@
+cd transpiler && ./build.sh && cd .. && ./transpiler/transpiler \ No newline at end of file
diff --git a/frontend/transpiler/List.h b/frontend/transpiler/List.h
new file mode 100644
index 0000000..00a466a
--- /dev/null
+++ b/frontend/transpiler/List.h
@@ -0,0 +1,222 @@
+#pragma once
+#include <cstdlib>
+#include <cstring>
+#include "Logger.h"
+
+#define FOREACH(list) \
+ for (size_t idx = 0; idx < list.numElements; idx++) \
+ if (auto value = list.getValue(idx)) \
+
+
+template <typename T>
+struct List {
+ T* data = nullptr;
+ size_t capacity = 0;
+ size_t numElements = 0;
+ bool growDynamically = true;
+
+ void allocate(size_t size);
+ void add(T* element);
+ void add(T& element);
+ void add(T&& element);
+ bool grow(size_t newSize);
+ void set(T* value, size_t index);
+ void remove(size_t index);
+ void clear();
+ void deallocate();
+ bool isEmpty() {
+ return data == nullptr || numElements == 0;
+ }
+ T* getValue(int index) const;
+ T& operator[](int idx) const;
+ void binarySort(int (*f)(T *first, T* second));
+ void setFromArray(T* arry, int size) {
+ allocate(size);
+ memcpy(data, arry, size * sizeof(T));
+ numElements = size;
+ }
+ void remove(int index) {
+ if (index >= numElements) {
+ logger_error("Cannot remove element at index: %d", index);
+ return;
+ }
+
+
+ if (index == numElements - 1) {
+ numElements--;
+ return;
+ }
+
+ memmove(data[index], data[index + 1], sizeof(T) * (numElements - index));
+ numElements--;
+ }
+};
+
+template <typename T>
+void List<T>::allocate(size_t size) {
+ if (size == 0 || size == capacity) {
+ numElements = 0;
+ return;
+ }
+
+ if (data != nullptr) {
+ deallocate();
+ }
+
+ data = static_cast<T*>(malloc(sizeof(T) * size));
+ capacity = size;
+ numElements = 0;
+}
+
+template <typename T>
+bool List<T>::grow(size_t newSize) {
+ if (!growDynamically) {
+ return false;
+ }
+
+ if (newSize == 0) {
+ return false;
+ }
+
+ T* newData = static_cast<T*>(malloc(sizeof(T) * newSize));
+
+ if (data != nullptr) {
+ memcpy(newData, data, numElements * sizeof(T));
+ delete data;
+ }
+
+ data = newData;
+ capacity = newSize;
+ return true;
+}
+
+template <typename T>
+void List<T>::set(T* value, size_t index) {
+ if (index >= capacity && !grow(index * 2)) {
+ return;
+ }
+
+ memcpy(&data[index], value, sizeof(T));
+}
+
+template <typename T>
+void List<T>::add(T* element) {
+ if (data == nullptr) {
+ allocate(2);
+ }
+
+ if (element == nullptr) {
+ logger_error("Element not defined");
+ return;
+ }
+
+ size_t newNumElements = numElements + 1;
+ if (newNumElements > capacity) {
+ if (!grow(2 * capacity)) {
+ logger_error("Trying to add to list but unable to grow the array");
+ return;
+ }
+ }
+
+ memcpy(&data[numElements], element, sizeof(T));
+ numElements = newNumElements;
+}
+
+template <typename T>
+void List<T>::add(T& element) {
+ if (data == nullptr) {
+ allocate(2);
+ }
+
+ size_t newNumElements = numElements + 1;
+ if (newNumElements > capacity) {
+ if (!grow(2 * capacity)) {
+ logger_error("Trying to add to list but unable to grow the array");
+ return;
+ }
+ }
+
+ memcpy(&data[numElements], &element, sizeof(T));
+ numElements = newNumElements;
+}
+
+template <typename T>
+void List<T>::add(T&& element) {
+ if (data == nullptr) {
+ allocate(2);
+ }
+
+ size_t newNumElements = numElements + 1;
+ if (newNumElements > capacity) {
+ if (!grow(2 * capacity)) {
+ logger_error("Trying to add to list but unable to grow the array");
+ return;
+ }
+ }
+
+ memcpy(&data[numElements], &element, sizeof(T));
+ numElements = newNumElements;
+}
+
+template <typename T>
+void List<T>::remove(size_t idx) {
+ if (idx >= numElements) {
+ logger_error("Index is outside of the list: %d >= %d", idx, numElements);
+ return;
+ }
+
+ for (; idx < numElements - 1; idx++) {
+ data[idx] = data[idx + 1];
+ }
+
+ numElements--;
+}
+
+template <typename T>
+void List<T>::deallocate() {
+ if (data != nullptr) {
+ free(data);
+ data = nullptr;
+ }
+
+ capacity = 0;
+ numElements = 0;
+}
+
+template <typename T>
+void List<T>::clear() {
+ numElements = 0;
+}
+
+template <typename T>
+T* List<T>::getValue(int idx) const {
+ return &data[idx];
+}
+
+template <typename T>
+T& List<T>::operator[](int idx) const {
+ return data[idx];
+}
+
+template <typename T>
+void List<T>::binarySort(int (*f)(T *first, T* second)) {
+ if (data == nullptr) {
+ return;
+ }
+
+ for (size_t idx = 0; idx < numElements - 1; idx++) {
+ int minIdx = idx;
+ T firstValue = data[idx];
+
+ for (int innerIdx = idx + 1; innerIdx < numElements; innerIdx++) {\
+ T secondValue = data[innerIdx];
+ if (f(&firstValue, &secondValue) > 0) {
+ minIdx= innerIdx;
+ }
+ }
+
+ T temp = data[minIdx];
+ memmove(&data[minIdx], &data[idx], sizeof(T));
+ memmove(&data[idx], &temp, sizeof(T));
+ }
+}
diff --git a/frontend/transpiler/Logger.cpp b/frontend/transpiler/Logger.cpp
new file mode 100644
index 0000000..1068d88
--- /dev/null
+++ b/frontend/transpiler/Logger.cpp
@@ -0,0 +1,123 @@
+#include "Logger.h"
+#include <chrono>
+#include <cstdarg>
+#include <cstdio>
+
+namespace Logger {
+ LogLevel gLogLevel = LogLevel_Debug;
+ FILE* gFilePointer = NULL;
+
+ void initialize(LoggerOptions options) {
+ setLevel(options.level);
+ if (options.logToFile) {
+#ifdef WIN32
+ fopen_s(&gFilePointer, options.filePath, "a");
+#else
+ gFilePointer = fopen(options.filePath, "a");
+#endif
+ }
+ }
+
+ void setLevel(LogLevel level) {
+ gLogLevel = level;
+ }
+
+ LogLevel getLevel() {
+ return gLogLevel;
+ }
+
+ void printHeader(const char* levelStr, const char* fileName, int lineNumber) {
+ time_t t = time(0);
+ tm now;
+#ifdef WIN32
+ localtime_s(&now, &t);
+#else
+ now = *localtime(&t);
+#endif
+
+ printf("%s:%d [%d-%d-%d %d:%d:%d] %s: ", fileName, lineNumber, (now.tm_year + 1900), (now.tm_mon + 1), now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, levelStr);
+ if (gFilePointer != NULL) {
+ fprintf(gFilePointer, "[%d-%d-%d %d:%d:%d] %s: ", (now.tm_year + 1900), (now.tm_mon + 1), now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, levelStr);
+ }
+ }
+
+ void logInternal(const char* file, int lineNumber,LogLevel level, const char* format, va_list args) {
+ if (level < gLogLevel) {
+ return;
+ }
+
+
+ const char* levelStr;
+ switch (level) {
+ case LogLevel_Debug:
+ levelStr = "Debug";
+ break;
+ case LogLevel_Info:
+ levelStr = "Info";
+ break;
+ case LogLevel_Warn:
+ levelStr = "Warning";
+ break;
+ case LogLevel_Error:
+ levelStr = "Error";
+ break;
+ default:
+ levelStr = "Unknown";
+ break;
+ }
+
+ if (gFilePointer != NULL) {
+ va_list fileArgs;
+ va_copy(fileArgs, args);
+ vfprintf(gFilePointer, format, fileArgs);
+ fprintf(gFilePointer, "\n");
+ }
+
+ printHeader(levelStr, file, lineNumber);
+
+ vprintf(format, args);
+ printf("\n");
+ }
+
+ void doLog(const char* file, int lineNumber,LogLevel level, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ logInternal(file, lineNumber, level, format, args);
+ va_end(args);
+ }
+
+ void doDebug(const char* file, int lineNumber,const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ logInternal(file, lineNumber, LogLevel_Debug, format, args);
+ va_end(args);
+ }
+
+ void doInfo(const char* file, int lineNumber,const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ logInternal(file, lineNumber, LogLevel_Info, format, args);
+ va_end(args);
+ }
+
+ void doWarning(const char* file, int lineNumber,const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ logInternal(file, lineNumber, LogLevel_Warn, format, args);
+ va_end(args);
+ }
+
+ void doError(const char* file, int lineNumber,const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ logInternal(file, lineNumber, LogLevel_Error, format, args);
+ va_end(args);
+ }
+
+ void free() {
+ if (gFilePointer) {
+ fclose(gFilePointer);
+ gFilePointer = NULL;
+ }
+ }
+}
diff --git a/frontend/transpiler/Logger.h b/frontend/transpiler/Logger.h
new file mode 100644
index 0000000..7596b6f
--- /dev/null
+++ b/frontend/transpiler/Logger.h
@@ -0,0 +1,43 @@
+#ifndef LOGGER_H
+#define LOGGER_H
+
+#include <cstring>
+
+enum LogLevel {
+ LogLevel_Debug = 0,
+ LogLevel_Info = 1,
+ LogLevel_Warn = 2,
+ LogLevel_Error = 3
+};
+
+struct LoggerOptions {
+ LogLevel level = LogLevel_Debug;
+ bool logToFile = false;
+ const char* filePath = "debug.log";
+};
+
+namespace Logger {
+ void initialize(LoggerOptions options);
+ void setLevel(LogLevel level);
+ LogLevel getLevel();
+ void doLog(const char* file, int lineNumber, LogLevel level, const char* format, ...);
+ void doDebug(const char* file, int lineNumber, const char* format, ...);
+ void doInfo(const char* file, int lineNumber, const char* format, ...);
+ void doWarning(const char* file, int lineNumber, const char* format, ...);
+ void doError(const char* file, int lineNumber, const char* format, ...);
+ void free();
+};
+
+#if WIN32
+#define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
+#else
+#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
+#endif
+
+#define logger_log(level, format, ...) Logger::doLog(__FILENAME__, __LINE__, level, format, ## __VA_ARGS__)
+#define logger_debug(format, ...) Logger::doDebug(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+#define logger_info(format, ...) Logger::doInfo(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+#define logger_warning(format, ...) Logger::doWarning(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+#define logger_error(format, ...) Logger::doError(__FILENAME__, __LINE__, format, ## __VA_ARGS__)
+
+#endif
diff --git a/frontend/transpiler/MathHelper.h b/frontend/transpiler/MathHelper.h
new file mode 100644
index 0000000..b9c9cb6
--- /dev/null
+++ b/frontend/transpiler/MathHelper.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#define PI 3.14159265358979323846f
+
+#define MIN(a,b) (((a)<(b))?(a):(b))
+#define MAX(a,b) (((a)>(b))?(a):(b))
+#define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower)))
+#define DTOR (PI / 180.0f)
+#define SWAP(x, y, T) do { T SWAP = x; x = y; y = SWAP; } while (0)
+
+namespace MathHelper {
+ inline float radiansToDegrees(float radians) {
+ return radians * 180.f / PI;
+ }
+
+ inline float degreesToRadians(float degrees) {
+ return degrees * PI / 180.f;
+ }
+
+ inline int nearestPowerOfTwo(int n) {
+ int v = n;
+
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+ v++; // next power of 2
+
+ return v;
+ }
+}
diff --git a/frontend/transpiler/MyString.cpp b/frontend/transpiler/MyString.cpp
new file mode 100644
index 0000000..1cb3d43
--- /dev/null
+++ b/frontend/transpiler/MyString.cpp
@@ -0,0 +1,392 @@
+#include "MyString.h"
+#include "MathHelper.h"
+#include <cstdlib>
+#include <cstdio>
+#include <iostream>
+#include <cstdarg>
+#include <cmath>
+
+int StringBuffer::add(const char* str) {
+ int spaceNeeded = strlen(str);
+ int spaceAvailable = BUFFER_SIZE - pointer;
+
+ int amountToCopy = 0;
+ if (spaceAvailable >= spaceNeeded) {
+ amountToCopy = spaceNeeded;
+ } else {
+ amountToCopy = spaceAvailable;
+ }
+
+ memcpy(&buffer[pointer], str, sizeof(char) * amountToCopy);
+ pointer += amountToCopy;
+ buffer[pointer] = '\0';
+ return amountToCopy;
+}
+
+void StringBuffer::reset() {
+ buffer[0] = '\0';
+ pointer = 0;
+}
+
+bool StringBuffer::isFull() {
+ return pointer == BUFFER_SIZE;
+}
+
+StringBuffer* StringBuilder::getCurrentBuffer() {
+ if (bufferPointer == 0) {
+ if (!defaultBuffer.isFull()) {
+ return &defaultBuffer;
+ }
+
+ StringBuffer newBuffer;
+ dynamicBuffer.allocate(2);
+ bufferPointer++;
+ dynamicBuffer.add(&newBuffer);
+ }
+
+ if (dynamicBuffer[bufferPointer - 1].isFull()) {
+ StringBuffer newBuffer;
+ dynamicBuffer.add(&newBuffer);
+ bufferPointer++;
+ }
+
+ return &dynamicBuffer[bufferPointer - 1];
+}
+
+StringBuffer* StringBuilder::getBufferAtIdx(int index) {
+ if (index == 0) {
+ return &defaultBuffer;
+ }
+
+ index = index - 1;
+ if (index < static_cast<int>(dynamicBuffer.numElements)) {
+ return &dynamicBuffer[index];
+ }
+
+ return nullptr;
+}
+
+const StringBuffer* StringBuilder::getBufferAtIdxConst(int index) const {
+ if (index == 0) {
+ return &defaultBuffer;
+ }
+
+ index = index - 1;
+ if (index < static_cast<int>(dynamicBuffer.numElements)) {
+ return &dynamicBuffer[index];
+ }
+
+ return nullptr;
+}
+
+String StringBuilder::toString() {
+ int strSize = (bufferPointer + 1) * StringBuffer::BUFFER_SIZE;
+ String retval;
+
+ if (strSize > String::SSO_SIZE) {
+ retval.dynamicBuffer = new char[strSize + 1];
+ memcpy(retval.dynamicBuffer, defaultBuffer.buffer, sizeof(char) * StringBuffer::BUFFER_SIZE);
+ FOREACH(dynamicBuffer) {
+ memcpy(&retval.dynamicBuffer[(idx + 1) * StringBuffer::BUFFER_SIZE], value->buffer, sizeof(char) * StringBuffer::BUFFER_SIZE);
+ }
+ retval.isSSO = false;
+ } else {
+ memcpy(retval.defaultBuffer, defaultBuffer.buffer, sizeof(char) * strSize);
+ retval.isSSO = true;
+ }
+
+ retval.capacity = strSize;
+ retval.length = length;
+ retval.getValue()[retval.length] = '\0';
+ return retval;
+}
+
+void StringBuilder::addStr(String* str) {
+ addStr(str->getValue());
+}
+
+void StringBuilder::addStr(const char* str) {
+ int amountLeft = strlen(str);
+ length += amountLeft;
+ int ptr = 0;
+
+ do {
+ StringBuffer* currentBuffer = getCurrentBuffer();
+ int amtAdded = currentBuffer->add(&str[ptr]);
+ amountLeft = amountLeft - amtAdded;
+ ptr += amtAdded;
+ } while (amountLeft > 0);
+}
+
+void StringBuilder::addChar(char c) {
+ char wrapper[2] = { c, '\0' };
+ addStr(wrapper);
+}
+
+const int DEFAULT_NUMBER_BUFFER_SIZE = 32;
+
+void StringBuilder::addInt(int value) {
+ char buffer[DEFAULT_NUMBER_BUFFER_SIZE];
+ sprintf(buffer, "%d", value);
+ addStr(buffer);
+}
+
+// Pulled from here: https://stackoverflow.com/questions/7228438/convert-double-float-to-string
+inline int n_tu(int number, int count) {
+ int result = 1;
+ while(count-- > 0)
+ result *= number;
+
+ return result;
+}
+
+void StringBuilder::addFloat(float value) {
+ char buffer[DEFAULT_NUMBER_BUFFER_SIZE];
+ sprintf(buffer, "%f", value);
+ addStr(buffer);
+}
+
+void StringBuilder::replace(const char* strToReplace, const char* replaceStr) {
+
+}
+
+int StringBuilder::indexOf(const char* str) {
+ return -1;
+}
+
+void StringBuilder::removeAt(int index, int count) {
+ if (index >= length) {
+ printf("Index is larger than the length");
+ return;
+ }
+
+ if (count <= 0) {
+ printf("Count was <= zero");
+ return;
+ }
+
+ int newLength = length - count;
+ if (newLength == 0) {
+ clear();
+ return;
+ }
+
+ // Easiest way to do this is to move the entire string to the left, starting at index by count
+ int currentIndex = index;
+ int copyIndex = index + count;
+
+ while (currentIndex < newLength) {
+ int bufferPos = floor(static_cast<float>(currentIndex) / static_cast<float>(StringBuffer::BUFFER_SIZE));
+ int pointerIdx = currentIndex % StringBuffer::BUFFER_SIZE;
+ StringBuffer* buffer = getBufferAtIdx(bufferPos);
+
+ char newChar = '\0';
+ int copyBufferPos = floor(static_cast<float>(copyIndex) / static_cast<float>(StringBuffer::BUFFER_SIZE));
+ StringBuffer* copyBuffer = getBufferAtIdx(copyBufferPos);
+ if (copyBuffer != nullptr) {
+ int bufferPointerIdx = copyIndex % StringBuffer::BUFFER_SIZE;
+ if (bufferPointerIdx < copyBuffer->pointer) {
+ newChar = copyBuffer->buffer[bufferPointerIdx];
+ }
+ }
+
+ buffer->buffer[pointerIdx] = newChar;
+
+ currentIndex++;
+ copyIndex++;
+ }
+
+ // Brute force update the buffer that we're currently using
+ length = newLength;
+ int newNumBuffers = ceil(static_cast<float>(length) / static_cast<float>(StringBuffer::BUFFER_SIZE)); // This is how many buffers we should have
+ if (newNumBuffers <= 1) {
+ dynamicBuffer.numElements = 0;
+ bufferPointer = 0;
+ } else {
+ dynamicBuffer.numElements = newNumBuffers - 1;
+ bufferPointer = newNumBuffers - 1;
+ }
+
+ StringBuffer* lastBuffer = getBufferAtIdx(bufferPointer);
+ if (lastBuffer->pointer != StringBuffer::BUFFER_SIZE) {
+ lastBuffer->pointer = length % StringBuffer::BUFFER_SIZE;
+ }
+ lastBuffer->buffer[lastBuffer->pointer] = '\0';
+}
+
+void StringBuilder::format(const char* str, ...) {
+ va_list args;
+ va_start(args, str);
+
+ while (*str != '\0') {
+ if (*str == '%') {
+ str++;
+ switch (*str) {
+ case 'd':
+ addInt(va_arg(args, int));
+ break;
+ case 'f':
+ addFloat(static_cast<float>(va_arg(args, double)));
+ break;
+ case 's':
+ addStr(va_arg(args, char*));
+ default:
+ break;
+ }
+ } else {
+ addChar(*str);
+ }
+
+ str++;
+ }
+
+ va_end(args);
+}
+
+void StringBuilder::clear() {
+ bufferPointer = 0;
+ length = 0;
+ defaultBuffer.reset();
+
+ if (!dynamicBuffer.isEmpty()) {
+ FOREACH(dynamicBuffer) {
+ value->reset();
+ }
+ }
+
+ dynamicBuffer.clear();
+}
+
+void StringBuilder::free() {
+ dynamicBuffer.deallocate();
+}
+
+char StringBuilder::getCharAtIdx(int index) const {
+ int bufferPos = floor(static_cast<float>(index) / static_cast<float>(StringBuffer::BUFFER_SIZE));
+ int pointerIdx = index % StringBuffer::BUFFER_SIZE;
+ const StringBuffer* buffer = getBufferAtIdxConst(bufferPos);
+ if (buffer != nullptr && pointerIdx < buffer->pointer) {
+ return buffer->buffer[pointerIdx];
+ }
+
+ return '\0';
+}
+
+void StringBuilder::insert(char c, int index) {
+ if (index >= length) {
+ addChar(c);
+ return;
+ }
+
+ // Move all of the characters forward by one
+ char currentChar = c;
+ for (int cIdx = index; cIdx < length + 1; cIdx++) {
+ int bufferPos = floor(static_cast<float>(cIdx) / static_cast<float>(StringBuffer::BUFFER_SIZE));
+ int pointerIdx = cIdx % StringBuffer::BUFFER_SIZE;
+ StringBuffer* buffer = getBufferAtIdx(bufferPos);
+ if (buffer == nullptr) {
+ buffer = getCurrentBuffer(); // Get a new buffer if this one is filled up
+ }
+
+ char nextCurrent = buffer->buffer[pointerIdx];
+ buffer->buffer[pointerIdx] = currentChar;
+ currentChar = nextCurrent;
+ }
+
+ // Move the final buffer forward
+ StringBuffer* finalBuffer = getCurrentBuffer();
+ finalBuffer->pointer++;
+ finalBuffer->buffer[finalBuffer->pointer] = '\0';
+ length = length + 1;
+}
+
+String::String() {
+}
+
+String::String(const char* str) {
+ set(str);
+}
+
+void String::operator =(const char* str) {
+ set(str);
+}
+
+char* String::getValue() {
+ return isSSO ? defaultBuffer : dynamicBuffer;
+}
+
+const char* String::getValueConst() const {
+ return isSSO ? defaultBuffer : dynamicBuffer;
+}
+
+void String::set(const char* str) {
+ int inLen = strlen(str);
+
+ if (inLen <= SSO_SIZE) {
+ isSSO = true;
+ memcpy(defaultBuffer, str, inLen);
+ defaultBuffer[inLen] = '\0';
+ } else {
+ if (capacity >= inLen) {
+ memcpy(dynamicBuffer, str, inLen);
+ } else {
+ free();
+ capacity = MathHelper::nearestPowerOfTwo(inLen + 1);
+ dynamicBuffer = new char[capacity];
+ memcpy(dynamicBuffer, str, inLen);
+ }
+ isSSO = false;
+ dynamicBuffer[inLen] = '\0';
+ }
+
+ length = inLen;
+}
+
+void String::free() {
+ capacity = 0;
+ length = 0;
+ isSSO = true;
+ if (dynamicBuffer != nullptr) {
+ delete dynamicBuffer;
+ }
+
+ defaultBuffer[0] = '\0';
+}
+
+int String::toInteger() {
+ return atoi(getValue());
+}
+
+float String::toFloat() {
+ return static_cast<float>(atof(getValue()));
+}
+
+int String::indexOf(char c) {
+ char* buffer = getValue();
+ int idx = 0;
+ while (buffer[idx] != '\0') {
+ if (buffer[idx] == c) {
+ return idx;
+ }
+ idx++;
+ }
+
+ return -1;
+}
+
+StringView String::substring(int start, int end) {
+ StringView retval;
+ if (start >= length) {
+ retval.error = true;
+ return retval;
+ }
+
+ if (end >= length) {
+ end = length - 1;
+ }
+
+ char* value = getValue();
+ retval.value = &value[start];
+ retval.length = end - start;
+ return retval;
+}
diff --git a/frontend/transpiler/MyString.h b/frontend/transpiler/MyString.h
new file mode 100644
index 0000000..d05d84a
--- /dev/null
+++ b/frontend/transpiler/MyString.h
@@ -0,0 +1,76 @@
+#pragma once
+#include "List.h"
+
+struct StringView {
+ bool error = false;
+ char* value = nullptr;
+ size_t length = 0;
+};
+
+struct String {
+ const static int SSO_SIZE = 31;
+
+ char defaultBuffer[String::SSO_SIZE + 1] = { '\0' };
+ char* dynamicBuffer = nullptr;
+
+ int length = 0;
+ int capacity = 0;
+ bool isSSO = true;
+
+ String();
+ String(const char* str);
+ char* getValue();
+ const char* getValueConst() const;
+ void operator =(const char* str);
+ void set(const char* str);
+ void free();
+ int toInteger();
+ float toFloat();
+ int indexOf(char c);
+ inline bool equals(const String& other) { return strcmp(getValueConst(), other.getValueConst()) == 0; };
+ inline bool equalsCstr(const char* str) { return strcmp(getValueConst(), str) == 0; };
+ StringView substring(int start, int end);
+};
+
+struct StringBuffer {
+ const static int BUFFER_SIZE = 31;
+
+ int pointer = 0;
+ char buffer[StringBuffer::BUFFER_SIZE + 1]; // Leave space for trailing escape character
+
+ /*
+ * Appends the string to the buffer
+ * @param str
+ * @returns number of characters copied
+ */
+ int add(const char* str);
+ bool isFull();
+ void reset();
+};
+
+struct StringBuilder {
+ int bufferPointer = 0;
+ int length = 0;
+
+ StringBuffer defaultBuffer;
+ List<StringBuffer> dynamicBuffer;
+
+ StringBuffer* getCurrentBuffer();
+ StringBuffer* getBufferAtIdx(int index);
+ const StringBuffer* getBufferAtIdxConst(int index) const;
+ void addStr(String* str);
+ void addStr(const char* str);
+ void addChar(char c);
+ void format(const char* str, ...);
+ void addInt(int value);
+ void addFloat(float value);
+ void replace(const char* strToReplace, const char* replaceStr);
+ void removeAt(int index, int count);
+ int indexOf(char c);
+ int indexOf(const char* str);
+ String toString();
+ void clear();
+ char getCharAtIdx(int index) const;
+ void insert(char c, int index);
+ void free();
+}; \ No newline at end of file
diff --git a/frontend/transpiler/build.sh b/frontend/transpiler/build.sh
new file mode 100755
index 0000000..6ca478e
--- /dev/null
+++ b/frontend/transpiler/build.sh
@@ -0,0 +1 @@
+g++ -o transpiler transpiler.cpp Logger.cpp MyString.cpp \ No newline at end of file
diff --git a/frontend/transpiler/pages.txt b/frontend/transpiler/pages.txt
new file mode 100644
index 0000000..caa420c
--- /dev/null
+++ b/frontend/transpiler/pages.txt
@@ -0,0 +1,14 @@
+&#127936; "2D"
+>"Rigidbody"
+>>/2d/_rigidbody/part_1.html "Linear Forces"
+>>/2d/_rigidbody/part_2.html "Rotational Forces"
+>>/2d/_rigidbody/part_3.html "Collision Forces"
+>"Collisions"
+>>/2d/_collisions/circle_line.html "Circle-Line"
+>>/2d/_collisions/rectangle_line.html "Rectangle-Line"
+>>/2d/_collisions/pill_line.html "Pill-Line"
+&#127776; "3D"
+&#128295; "WebAssembly"
+>>/intro/intro.html "Introduction"
+&#128712; "About"
+>>/roadmap.html "Roadmap"
diff --git a/frontend/transpiler/transpiler b/frontend/transpiler/transpiler
new file mode 100755
index 0000000..2f2d00b
--- /dev/null
+++ b/frontend/transpiler/transpiler
Binary files differ
diff --git a/frontend/transpiler/transpiler.cpp b/frontend/transpiler/transpiler.cpp
new file mode 100644
index 0000000..4771314
--- /dev/null
+++ b/frontend/transpiler/transpiler.cpp
@@ -0,0 +1,249 @@
+#include "MyString.h"
+#include "List.h"
+#include "Logger.h"
+#include <cstdio>
+#include <vector>
+#include <cstring>
+
+struct InnerCategory {
+ bool isLabel = false;
+ String label;
+ String path;
+
+ void toCode(StringBuilder& sb) {
+ if (isLabel) {
+ sb.format("\t\t\t\t\t<li><label>%s</label></li>\n", label.getValue());
+ } else {
+ sb.format("\t\t\t\t\t<li><a href=\"%s\">%s</a></li>\n", path.getValue(), label.getValue());
+ }
+ }
+
+ void free() {
+ label.free();
+ path.free();
+ }
+};
+
+struct OuterCategory {
+ String outerName;
+ String outerSymbol;
+ List<InnerCategory> items;
+
+ void toCode(StringBuilder& sb) {
+ sb.addStr("\t\t\t<li>\n");
+ sb.format("\t\t\t\t<span>%s<span>%s</span></span>\n", outerSymbol.getValue(), outerName.getValue());
+ sb.addStr("\t\t\t\t<ul class=\"inner-tree\">\n");
+
+ FOREACH(items) {
+ value->toCode(sb);
+ }
+
+ sb.addStr("\t\t\t\t</ul>\n");
+ sb.addStr("\t\t\t</li>\n");
+ }
+
+ void reset() {
+ outerName.free();
+ outerSymbol.free();
+ items.clear();
+ }
+
+ void free() {
+ outerName.free();
+ outerSymbol.free();
+
+ FOREACH(items) {
+ value->free();
+ }
+ items.deallocate();
+ }
+};
+
+int main() {
+ logger_info("Running transpiler");
+ FILE* pagesFile = fopen("transpiler/pages.txt", "r+");
+
+ char * cLine = NULL;
+ size_t len = 0;
+ ssize_t read;
+
+ StringBuilder sb;
+ StringBuilder otherSb;
+
+ sb.addStr("\t\t<nav>\n\t\t<ul class=\"outer-tree\">\n");
+ sb.addStr("\t\t\t<li><a href=\"/\">Introduction</a></li>\n");
+
+ bool isFirst = true;
+ OuterCategory oc;
+ List<String> servedFiles;
+
+ servedFiles.add("./index.html");
+ while ((read = getline(&cLine, &len, pagesFile)) != -1) {
+ char* line = cLine;
+
+ int indent = 0;
+ while (line[0] == '>') {
+ line++;
+ indent++;
+ }
+
+ switch (indent) {
+ case 0: {
+ if (!isFirst) {
+ oc.toCode(sb);
+ }
+ oc.reset();
+
+ isFirst = false;
+ // Outer tree item
+ while (line[0] != ' ') {
+ otherSb.addChar(line[0]);
+ line++;
+ }
+ line++;
+
+ oc.outerSymbol = otherSb.toString();
+ otherSb.clear();
+
+ while (line[0] != '\n') {
+ if (line[0] != '\"') {
+ otherSb.addChar(line[0]);
+ }
+ line++;
+ }
+
+ oc.outerName = otherSb.toString();
+ logger_info(oc.outerName.getValue());
+ otherSb.clear();
+
+ break;
+ }
+ case 1: {
+ InnerCategory ic;
+ ic.isLabel = true;
+ while (line[0] != '\n') {
+ if (line[0] != '\"') {
+ otherSb.addChar(line[0]);
+ }
+ line++;
+ }
+ ic.label = otherSb.toString();
+ otherSb.clear();
+ oc.items.add(ic);
+
+ break;
+ }
+ case 2: {
+ InnerCategory ic;
+ ic.isLabel = false;
+
+ while (line[0] != ' ') {
+ otherSb.addChar(line[0]);
+ line++;
+ }
+ line++;
+
+ ic.path = otherSb.toString();
+ otherSb.insert('.', 0);
+ String pathCopy = otherSb.toString();
+ servedFiles.add(pathCopy);
+ otherSb.clear();
+
+
+ while (line[0] != '\n') {
+ if (line[0] != '\"') {
+ otherSb.addChar(line[0]);
+ }
+ line++;
+ }
+
+ ic.label = otherSb.toString();
+ otherSb.clear();
+ oc.items.add(ic);
+
+ break;
+ }
+ }
+ }
+
+ oc.toCode(sb);
+ sb.addStr("\t\t</ul>\n\t\t</nav>\n");
+ oc.reset();
+ fclose(pagesFile);
+
+ String navbarRetval = sb.toString();
+ sb.clear();
+
+ logger_info("Creating served files");
+
+ // Create the header
+ FOREACH(servedFiles) {
+ otherSb.clear();
+ otherSb.format("%s.content", value->getValue());
+ String contentFileName = otherSb.toString();
+ FILE* contentFile = fopen(contentFileName.getValue(), "r+");
+ if (contentFile == NULL) {
+ logger_warning("Could not output file, most likely unimplemented: %s", contentFileName.getValue());
+ continue;
+ }
+
+ logger_info("Outputting file: %s", contentFileName.getValue());
+
+ // Header
+ sb.addStr("<!DOCTYPE html>\n"
+ "<html lang=\"en\">\n"
+ "\t<head>\n"
+ "\t\t<meta charset=\"utf-8\">\n"
+ "\t\t<link rel=\"stylesheet\" href=\"/index.css\">\n"
+ "\t\t<title>Physics for Games</title>\n"
+ "\t\t<link rel=\"shortcut icon\" href=\"favicon/favicon.ico\" type=\"image/x-icon\">\n"
+ "\t</head>\n"
+ "\t<body>\n"
+ "\t\t<header>\n"
+ "\t\t\t<h1>Physics for Games</h1>\n"
+ "\t\t</header>\n"
+ "\t\t<main>\n");
+
+ // Write navigation bar
+ sb.addStr(navbarRetval.getValue());
+
+ // Read body
+
+ fseek(contentFile, 0, SEEK_END);
+ long fsize = ftell(contentFile);
+ fseek(contentFile, 0, SEEK_SET);
+
+ char* bodyContent = new char[fsize + 1];
+ fread(bodyContent, 1, fsize, contentFile);
+ sb.addStr(bodyContent);
+ delete [] bodyContent;
+
+ contentFileName.free();
+ fclose(contentFile);
+
+ // Footer
+ sb.addStr("\t\t</main>\n"
+ "\t</body>\n"
+ "</html>\n");
+
+ FILE* outFile = fopen(value->getValue(), "w+");
+ String outStr = sb.toString();
+ fwrite(outStr.getValue(), 1, outStr.length, outFile);
+ outStr.free();
+ fclose(outFile);
+ sb.clear();
+ }
+
+ logger_info("Outputting files to their proper directories...");
+ sb.free();
+ navbarRetval.free();
+ otherSb.free();
+
+ FOREACH(servedFiles) {
+ value->free();
+ }
+
+ servedFiles.deallocate();
+
+ return 0;
+} \ No newline at end of file