diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/common.hpp | 11 | ||||
-rw-r--r-- | src/main.cpp | 124 | ||||
-rw-r--r-- | src/ogl.cpp | 16 | ||||
-rw-r--r-- | src/ogl.hpp | 10 | ||||
-rw-r--r-- | src/pcg.hpp | 207 | ||||
-rw-r--r-- | src/sandbox.cpp | 89 | ||||
-rw-r--r-- | src/sandbox.hpp | 42 | ||||
-rw-r--r-- | src/ui.cpp | 17 | ||||
-rw-r--r-- | src/ui.hpp | 3 |
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 + * <[email protected]> + * + * 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(); |