aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common.hpp11
-rw-r--r--src/main.cpp124
-rw-r--r--src/ogl.cpp16
-rw-r--r--src/ogl.hpp10
-rw-r--r--src/pcg.hpp207
-rw-r--r--src/sandbox.cpp89
-rw-r--r--src/sandbox.hpp42
-rw-r--r--src/ui.cpp17
-rw-r--r--src/ui.hpp3
9 files changed, 519 insertions, 0 deletions
diff --git a/src/common.hpp b/src/common.hpp
new file mode 100644
index 0000000..1c64c34
--- /dev/null
+++ b/src/common.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+// clang-format off
+#define MSG_INFO "[INFO] "
+#define MSG_WARN "[WARN] "
+#define MSG_ERROR "[ERRO] "
+// clang-format on
+
+struct Pt {
+ int x, y;
+};
diff --git a/src/main.cpp b/src/main.cpp
index e69de29..38f6961 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -0,0 +1,124 @@
+#include "common.hpp"
+#include "ui.hpp"
+
+#include <imgui.h>
+#include <imgui_impl_glfw.h>
+#include <imgui_impl_opengl3.h>
+#include <imgui_impl_opengl3_loader.h>
+#include <cstdio>
+#include <stdexcept>
+
+// Different packaging defines this inconsistently, e.g. Xrepo but not most Linux distros
+// We need it, so define just in case
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+#include <GLFW/glfw3native.h>
+
+static void glfw_error_handler(int err, const char* desc) {
+ fprintf(stderr, MSG_ERROR "GLFW error %d: %s", err, desc);
+}
+
+static const char* imgui_glsl_version = nullptr;
+
+struct GlfwInit {
+ GlfwInit() {
+ if (!glfwInit()) {
+ throw std::runtime_error("failed to init GLFW");
+ }
+ glfwSetErrorCallback(&glfw_error_handler);
+
+// Decide GL+GLSL versions
+#if defined(IMGUI_IMPL_OPENGL_ES2)
+ // GL ES 2.0 + GLSL 100
+ imgui_glsl_version = "#version 100";
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
+#elif defined(__APPLE__)
+ // GL 3.2 + GLSL 150
+ imgui_glsl_version = "#version 150";
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
+#else
+ // GL 3.0 + GLSL 130
+ imgui_glsl_version = "#version 130";
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+#endif
+ }
+
+ ~GlfwInit() {
+ glfwTerminate();
+ }
+};
+
+struct GlfwWindow {
+ GLFWwindow* _h;
+
+ GlfwWindow(int width, int height, const char* title) {
+ _h = glfwCreateWindow(width, height, title, nullptr, nullptr);
+ if (_h == nullptr)
+ throw std::runtime_error("failed to create GLFW window");
+ }
+
+ ~GlfwWindow() {
+ glfwDestroyWindow(_h);
+ }
+};
+
+struct ImGuiInit {
+ ImGuiInit(GLFWwindow* window) {
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+
+ ImGui_ImplGlfw_InitForOpenGL(window, true);
+ ImGui_ImplOpenGL3_Init("#version 130");
+ }
+
+ ~ImGuiInit() {
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplGlfw_Shutdown();
+
+ ImGui::DestroyContext();
+ }
+};
+
+int main() {
+ GlfwInit i_glfw;
+
+ GlfwWindow i_window(1280, 730, "Sand Physics");
+ GLFWwindow* window = i_window._h;
+
+ glfwMakeContextCurrent(window);
+ // V-sync to make timing easier, we don't really care about latency
+ glfwSwapInterval(1);
+
+ ImGuiInit i_imgui(window);
+
+ ImVec4 bg_col(1.0f, 1.0f, 1.0f, 1.0f);
+ bg_col.x *= bg_col.w;
+ bg_col.y *= bg_col.w;
+ bg_col.z *= bg_col.w;
+
+ while (!glfwWindowShouldClose(window)) {
+ glfwPollEvents();
+
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
+
+ ShowEverything();
+
+ ImGui::Render();
+ int display_w, display_h;
+ glfwGetFramebufferSize(window, &display_w, &display_h);
+ glViewport(0, 0, display_w, display_h);
+ glClearColor(bg_col.x * bg_col.w, bg_col.y * bg_col.w, bg_col.z * bg_col.w, bg_col.w);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+
+ glfwSwapBuffers(window);
+ }
+} \ No newline at end of file
diff --git a/src/ogl.cpp b/src/ogl.cpp
new file mode 100644
index 0000000..a7b2dfa
--- /dev/null
+++ b/src/ogl.cpp
@@ -0,0 +1,16 @@
+#include "ogl.hpp"
+
+#include <imgui_impl_opengl3_loader.h>
+
+OglImage::OglImage() {
+ glGenTextures(1, &glHandle);
+}
+
+OglImage::~OglImage() {
+ glDeleteTextures(1, &glHandle);
+}
+
+void OglImage::upload(const char* data, int w, int h) {
+ glBindTexture(GL_TEXTURE_2D, glHandle);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+}
diff --git a/src/ogl.hpp b/src/ogl.hpp
new file mode 100644
index 0000000..126b21f
--- /dev/null
+++ b/src/ogl.hpp
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <cstdint>
+
+struct OglImage {
+ uint32_t glHandle;
+ OglImage();
+ ~OglImage();
+ void upload(const char* data, int w, int h);
+};
diff --git a/src/pcg.hpp b/src/pcg.hpp
new file mode 100644
index 0000000..8e74d11
--- /dev/null
+++ b/src/pcg.hpp
@@ -0,0 +1,207 @@
+/*
+ * Tiny self-contained version of the PCG Random Number Generation for C++
+ * put together from pieces of the much larger C/C++ codebase.
+ * Wenzel Jakob, February 2015
+ *
+ * The PCG random number generator was developed by Melissa O'Neill
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * For additional information about the PCG random number generation scheme,
+ * including its license and other licensing options, visit
+ *
+ * http://www.pcg-random.org
+ */
+
+#pragma once
+
+#define PCG32_DEFAULT_STATE 0x853c49e6748fea9bULL
+#define PCG32_DEFAULT_STREAM 0xda3e39cb94b95bdbULL
+#define PCG32_MULT 0x5851f42d4c957f2dULL
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstdint>
+
+/// PCG32 Pseudorandom number generator
+struct Pcg32 {
+ /// Initialize the pseudorandom number generator with default seed
+ Pcg32() : state(PCG32_DEFAULT_STATE), inc(PCG32_DEFAULT_STREAM) {}
+
+ /// Initialize the pseudorandom number generator with the \ref seed() function
+ Pcg32(uint64_t initstate, uint64_t initseq = 1u) { seed(initstate, initseq); }
+
+ /**
+ * \brief Seed the pseudorandom number generator
+ *
+ * Specified in two parts: a state initializer and a sequence selection
+ * constant (a.k.a. stream id)
+ */
+ void seed(uint64_t initstate, uint64_t initseq = 1) {
+ state = 0U;
+ inc = (initseq << 1u) | 1u;
+ next_u32();
+ state += initstate;
+ next_u32();
+ }
+
+ /// Generate a uniformly distributed unsigned 32-bit random number
+ uint32_t next_u32() {
+ uint64_t oldstate = state;
+ state = oldstate * PCG32_MULT + inc;
+ uint32_t xorshifted = (uint32_t)(((oldstate >> 18u) ^ oldstate) >> 27u);
+ uint32_t rot = (uint32_t)(oldstate >> 59u);
+ return (xorshifted >> rot) | (xorshifted << ((~rot + 1u) & 31));
+ }
+
+ /// Generate a uniformly distributed number, r, where 0 <= r < bound
+ uint32_t next_u32(uint32_t bound) {
+ // To avoid bias, we need to make the range of the RNG a multiple of
+ // bound, which we do by dropping output less than a threshold.
+ // A naive scheme to calculate the threshold would be to do
+ //
+ // uint32_t threshold = 0x100000000ull % bound;
+ //
+ // but 64-bit div/mod is slower than 32-bit div/mod (especially on
+ // 32-bit platforms). In essence, we do
+ //
+ // uint32_t threshold = (0x100000000ull-bound) % bound;
+ //
+ // because this version will calculate the same modulus, but the LHS
+ // value is less than 2^32.
+
+ uint32_t threshold = (~bound + 1u) % bound;
+
+ // Uniformity guarantees that this loop will terminate. In practice, it
+ // should usually terminate quickly; on average (assuming all bounds are
+ // equally likely), 82.25% of the time, we can expect it to require just
+ // one iteration. In the worst case, someone passes a bound of 2^31 + 1
+ // (i.e., 2147483649), which invalidates almost 50% of the range. In
+ // practice, bounds are typically small and only a tiny amount of the range
+ // is eliminated.
+ for (;;) {
+ uint32_t r = next_u32();
+ if (r >= threshold)
+ return r % bound;
+ }
+ }
+
+ /// Generate a single precision floating point value on the interval [0, 1)
+ float next_f32() {
+ /* Trick from MTGP: generate an uniformly distributed
+ single precision number in [1,2) and subtract 1. */
+ union {
+ uint32_t u;
+ float f;
+ } x;
+ x.u = (next_u32() >> 9) | 0x3f800000u;
+ return x.f - 1.0f;
+ }
+
+ /**
+ * \brief Generate a double precision floating point value on the interval [0, 1)
+ *
+ * \remark Since the underlying random number generator produces 32 bit output,
+ * only the first 32 mantissa bits will be filled (however, the resolution is still
+ * finer than in \ref nextFloat(), which only uses 23 mantissa bits)
+ */
+ double next_f64() {
+ /* Trick from MTGP: generate an uniformly distributed
+ double precision number in [1,2) and subtract 1. */
+ union {
+ uint64_t u;
+ double d;
+ } x;
+ x.u = ((uint64_t)next_u32() << 20) | 0x3ff0000000000000ULL;
+ return x.d - 1.0;
+ }
+
+ /**
+ * \brief Multi-step advance function (jump-ahead, jump-back)
+ *
+ * The method used here is based on Brown, "Random Number Generation
+ * with Arbitrary Stride", Transactions of the American Nuclear
+ * Society (Nov. 1994). The algorithm is very similar to fast
+ * exponentiation.
+ */
+ void advance(int64_t delta_) {
+ uint64_t
+ cur_mult = PCG32_MULT,
+ cur_plus = inc,
+ acc_mult = 1u,
+ acc_plus = 0u;
+
+ /* Even though delta is an unsigned integer, we can pass a signed
+ integer to go backwards, it just goes "the long way round". */
+ uint64_t delta = (uint64_t)delta_;
+
+ while (delta > 0) {
+ if (delta & 1) {
+ acc_mult *= cur_mult;
+ acc_plus = acc_plus * cur_mult + cur_plus;
+ }
+ cur_plus = (cur_mult + 1) * cur_plus;
+ cur_mult *= cur_mult;
+ delta /= 2;
+ }
+ state = acc_mult * state + acc_plus;
+ }
+
+ /**
+ * \brief Draw uniformly distributed permutation and permute the
+ * given STL container
+ *
+ * From: Knuth, TAoCP Vol. 2 (3rd 3d), Section 3.4.2
+ */
+ template <typename Iterator>
+ void shuffle(Iterator begin, Iterator end) {
+ for (Iterator it = end - 1; it > begin; --it)
+ std::iter_swap(it, begin + next_u32((uint32_t)(it - begin + 1)));
+ }
+
+ /// Compute the distance between two PCG32 pseudorandom number generators
+ int64_t operator-(const Pcg32& other) const {
+ assert(inc == other.inc);
+
+ uint64_t
+ cur_mult = PCG32_MULT,
+ cur_plus = inc,
+ cur_state = other.state,
+ the_bit = 1u,
+ distance = 0u;
+
+ while (state != cur_state) {
+ if ((state & the_bit) != (cur_state & the_bit)) {
+ cur_state = cur_state * cur_mult + cur_plus;
+ distance |= the_bit;
+ }
+ assert((state & the_bit) == (cur_state & the_bit));
+ the_bit <<= 1;
+ cur_plus = (cur_mult + 1ULL) * cur_plus;
+ cur_mult *= cur_mult;
+ }
+
+ return (int64_t)distance;
+ }
+
+ /// Equality operator
+ bool operator==(const Pcg32& other) const { return state == other.state && inc == other.inc; }
+
+ /// Inequality operator
+ bool operator!=(const Pcg32& other) const { return state != other.state || inc != other.inc; }
+
+ uint64_t state; // RNG state. All values are possible.
+ uint64_t inc; // Controls which RNG sequence (stream) is selected. Must *always* be odd.
+};
diff --git a/src/sandbox.cpp b/src/sandbox.cpp
new file mode 100644
index 0000000..f71be9b
--- /dev/null
+++ b/src/sandbox.cpp
@@ -0,0 +1,89 @@
+#include "sandbox.hpp"
+#include "common.hpp"
+
+#include <utility>
+
+// Copied from IM_COL32
+#define COL_U32(r, g, b, a) (((uint32_t)(A) << IM_COL32_A_SHIFT) | ((uint32_t)(B) << IM_COL32_B_SHIFT) | ((uint32_t)(G) << IM_COL32_G_SHIFT) | ((uint32_t)(R) << IM_COL32_R_SHIFT))
+
+static constexpr uint32_t MAT_COLOR_LUT[] = {
+ 0xffffff'ff, // AIR
+ 0xababab'ff, // SOLID
+ 0xfffca8'ff, // SAND
+ 0x435bf7'ff, // WATER
+};
+
+uint32_t Tile::get_color() const {
+ return MAT_COLOR_LUT[std::to_underlying(mat)];
+}
+
+Sandbox::Sandbox(int w, int h)
+ : _bitmap(w * h)
+ , _a(w * h)
+ // TODO random seed
+ , _pcg()
+ , _solid_tile{ Tile::SOLID }
+ , width{ w }
+ , height{ h } //
+{
+}
+
+static void simulate_sand_tile(Sandbox& self, int x, int y) {
+ auto& at0 = self.gs(x, y);
+ switch (at0.mat) {
+ case Tile::AIR: break;
+
+ case Tile::SOLID: break;
+
+ case Tile::SAND: {
+ auto& below = self.gs(x, y - 1);
+ if (below.mat == Tile::AIR) {
+ below = at0;
+ below.updated = true;
+ at0 = {};
+ at0.updated = true;
+ } else if (below.mat == Tile::WATER) {
+ Pt neighs[]{ Pt(x - 1, y), Pt(x + 1, y), Pt(x, y + 1), Pt(x, y - 1) };
+ int max_pressure = 0;
+ for (auto [x1, y1] : neighs) {
+ auto& neigh = self.gs(x1, y1);
+ if (neigh.mat == Tile::WATER) {
+ auto p = neigh.pressure;
+ max_pressure = max_pressure > p ? max_pressure : p;
+ }
+ }
+ } else {
+ // Try going to a side
+ // TODO
+ }
+ } break;
+
+ case Tile::WATER: {
+ // TODO pressure system
+ } break;
+ }
+}
+
+void Sandbox::simulate_step() {
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ simulate_sand_tile(*this, x, y);
+ }
+ }
+ ++ncycle;
+}
+
+Tile& Sandbox::gs(int x, int y) {
+ if (x < 0 || x >= width || y < 0 || y >= height)
+ return _solid_tile;
+ return _a[y * width + x];
+}
+
+void Sandbox::set_sand(int x, int y, Tile sand) {
+ _a[y * width + x] = std::move(sand);
+ _bitmap[y * width + x] = sand.get_color();
+}
+
+// std::vector<uint32_t> Sandbox::to_bitmap() const {
+// // TODO
+// }
diff --git a/src/sandbox.hpp b/src/sandbox.hpp
new file mode 100644
index 0000000..c6453c7
--- /dev/null
+++ b/src/sandbox.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "pcg.hpp"
+
+#include <cstdint>
+#include <vector>
+
+const int MBIT_DISPLACABLE = 1;
+
+struct Tile {
+ enum Material : unsigned char {
+ AIR = 0,
+ SOLID,
+ SAND,
+ WATER,
+ };
+
+ Material mat;
+ uint8_t pressure = 0;
+ bool updated = false;
+
+ uint32_t get_color() const;
+};
+
+struct Sandbox {
+ std::vector<uint32_t> _bitmap;
+ std::vector<Tile> _a;
+ Pcg32 _pcg;
+ Tile _solid_tile;
+ int width, height;
+ int ncycle = 0;
+
+ Sandbox(int w, int h);
+
+ void simulate_step();
+
+ Tile& gs(int x, int y);
+ void set_sand(int x, int y, Tile sand);
+ void shift_sand(int x, int y);
+
+ // std::vector<uint32_t> to_bitmap() const;
+};
diff --git a/src/ui.cpp b/src/ui.cpp
new file mode 100644
index 0000000..bc88394
--- /dev/null
+++ b/src/ui.cpp
@@ -0,0 +1,17 @@
+#include "ui.hpp"
+#include "ogl.hpp"
+#include "sandbox.hpp"
+
+#include <imgui.h>
+
+void ShowEverything() {
+ ImGui::Begin("Sandbox");
+ constexpr int kWidth = 40;
+ constexpr int kHeight = 100;
+ static bool running = false;
+ static Sandbox sandbox(40, 100);
+ static OglImage gl;
+ sandbox.simulate_step();
+ gl.upload(reinterpret_cast<const char*>(sandbox._bitmap.data()), kWidth, kHeight);
+ ImGui::End();
+}
diff --git a/src/ui.hpp b/src/ui.hpp
new file mode 100644
index 0000000..9f0508b
--- /dev/null
+++ b/src/ui.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+void ShowEverything();