#include "StarCellularLiquid.hpp" #include "StarRandom.hpp" #include "StarRenderer.hpp" #include "StarRootLoader.hpp" #include "StarLiquidsDatabase.hpp" #include "StarAssets.hpp" #include "StarTextPainter.hpp" #include "StarMainApplication.hpp" using namespace Star; constexpr int LiquidArrayWidth = 60; constexpr int LiquidArrayHeight = 60; constexpr int LiquidArraySlowBorder = 0; constexpr int LiquidArraySlowTickLimit = 200; constexpr float LiquidBottomDrainPercentage = 0.5f; class LiquidTest : public Application { protected: struct LiquidStore { bool collision; bool endless; Maybe liquid; float level; float pressure; }; void startup(StringList const& cmdLineArgs) override { RootLoader rootLoader({{}, {}, {}, LogLevel::Info, false, {}}); m_root = rootLoader.initOrDie(cmdLineArgs).first; m_liquidArray.resize(Array2S(LiquidArrayWidth, LiquidArrayHeight), LiquidStore()); m_paused = false; m_step = false; m_update = 0; struct CellWorld : CellularLiquidWorld { LiquidTest& parent; CellWorld(LiquidTest& parent) : parent(parent) {} float drainLevel(Vec2I const& location) const override { return location[1] == 0 ? LiquidBottomDrainPercentage : 0.0f; } CellularLiquidCell cell(Vec2I const& location) const override { if (location[0] < 0 || location[1] < 0 || location[0] >= (int)parent.m_liquidArray.size(0) || location[1] >= (int)parent.m_liquidArray.size(1)) return {}; auto const& store = parent.m_liquidArray(location[0], location[1]); if (store.collision) return CellularLiquidCollisionCell(); if (store.endless) return CellularLiquidSourceCell{*store.liquid, store.pressure}; else return CellularLiquidFlowCell{store.liquid, store.level, store.pressure}; } void setFlow(Vec2I const& location, CellularLiquidFlowCell const& flow) override { if (location[0] < 0 || location[1] < 0 || location[0] >= (int)parent.m_liquidArray.size(0) || location[1] >= (int)parent.m_liquidArray.size(1)) return; auto& store = parent.m_liquidArray(location[0], location[1]); if (!store.endless && !store.collision) { store.liquid = flow.liquid; store.level = flow.level; store.pressure = flow.pressure; } } void liquidInteraction(Vec2I const& a, size_t aLiquid, Vec2I const& b, size_t bLiquid) override { if (a[0] < 0 || a[1] < 0 || a[0] >= (int)parent.m_liquidArray.size(0) || a[1] >= (int)parent.m_liquidArray.size(1)) return; if (b[0] < 0 || b[1] < 0 || b[0] >= (int)parent.m_liquidArray.size(0) || b[1] >= (int)parent.m_liquidArray.size(1)) return; auto& aCell = parent.m_liquidArray(a[0], a[1]); auto& bCell = parent.m_liquidArray(b[0], b[1]); if (aLiquid == 0 && bLiquid == 2 && !aCell.endless) bCell = LiquidStore{true, false, {}, 0.0f, 0.0f}; if (aLiquid == 2 && bLiquid == 0 && !bCell.endless) aCell = LiquidStore{true, false, {}, 0.0f, 0.0f}; if (aLiquid == 0 && bLiquid == 1 && !aCell.endless) aCell.liquid = 1; if (aLiquid == 1 && bLiquid == 0 && !bCell.endless) bCell.liquid = 1; } }; m_liquidEngine = make_shared>( Root::singleton().liquidsDatabase()->liquidEngineParameters(), make_shared(*this)); auto assets = Root::singleton().assets(); m_liquids = {{0, 0, 255, 255}, {0, 255, 0, 255}, {255, 0, 0, 255}}; m_currentLiquid = 0; m_liquidEngine->setLiquidTickDelta(0, 2); m_liquidEngine->setLiquidTickDelta(1, 3); m_liquidEngine->setLiquidTickDelta(2, 5); m_liquidEngine->setProcessingLimit(LiquidArraySlowTickLimit); m_liquidEngine->setNoProcessingLimitRegions({RectI(0, 0, LiquidArrayWidth, LiquidArrayHeight).trimmed(LiquidArraySlowBorder)}); } void renderInit(RendererPtr renderer) override { Application::renderInit(renderer); m_textPainter = make_shared(m_root->assets()->font("/hobo.ttf")->clone(), renderer); m_textPainter->setFontSize(16); } void update() override { if (m_mouseButtonHeld) { Vec2I cell = screenToCell(m_mousePos); auto& mouseCell = m_liquidArray(cell[0], cell[1]); if (*m_mouseButtonHeld == MouseButton::Left) { mouseCell = LiquidStore{true, false, {}, 0.0f, 0.0f}; } else if (*m_mouseButtonHeld == MouseButton::Middle) { mouseCell = LiquidStore{false, true, m_currentLiquid, 1.0f, 5.0f}; } else if (*m_mouseButtonHeld == MouseButton::Right) { mouseCell = LiquidStore{false, false, {}, 0.0f, 0.0f}; } m_liquidEngine->visitLocation(cell); } if (!m_paused || m_step) { m_liquidEngine->update(); ++m_update; } m_step = false; } void render() override { for (int x = 0; x < LiquidArrayWidth; ++x) { for (int y = 0; y < LiquidArrayHeight; ++y) { auto const& cell = m_liquidArray(x, y); RectF screenRect = cellScreenRect({x, y}); Vec4B color; if (cell.collision) color = {255, 255, 255, 255}; if (cell.endless) color = {255, 0, 255, 255}; else if (cell.liquid) color = liquidColor(*cell.liquid, cell.level); renderer()->render(renderFlatRect(screenRect, color)); } } Vec2I mouseCellPos = screenToCell(m_mousePos); auto const& mouseCell = m_liquidArray(mouseCellPos[0], mouseCellPos[1]); String hoverText = strf( "fps: %s\nactive water cells: %s\nactive acid cells: %s\nactive lava cells: %s\ncell: %s\npaused: %s\ncurrent " "liquid: %d\ncell collision: %s\ncell liquid: %d\ncell level: %s\ncell pressure: %s\n", appController()->renderFps(), m_liquidEngine->activeCells(0), m_liquidEngine->activeCells(1), m_liquidEngine->activeCells(2), mouseCellPos, m_paused, m_currentLiquid, mouseCell.collision, mouseCell.liquid, mouseCell.level, mouseCell.pressure); m_textPainter->renderText(hoverText, TextPositioning(Vec2F(0, renderer()->screenSize()[1]), HorizontalAnchor::LeftAnchor, VerticalAnchor::TopAnchor)); } void processInput(InputEvent const& event) override { if (auto keyDown = event.ptr()) { if (keyDown->key == Key::P) m_paused = !m_paused; if (keyDown->key == Key::S) m_step = true; } else if (auto mouseDown = event.ptr()) { m_mouseButtonHeld = mouseDown->mouseButton; } else if (auto mouseWheel = event.ptr()) { if (mouseWheel->mouseWheel == MouseWheel::Up) m_currentLiquid = pmod((int)m_currentLiquid + 1, m_liquids.size()); else m_currentLiquid = pmod((int)m_currentLiquid - 1, m_liquids.size()); } else if (event.is()) { m_mouseButtonHeld.reset(); } else if (auto mme = event.ptr()) { m_mousePos = mme->mousePosition; } } private: typedef MultiArray LiquidArray; Vec2F cellScreenDimensions() const { Vec2F windowSize(renderer()->screenSize()); return {windowSize[0] / LiquidArrayWidth, windowSize[1] / LiquidArrayHeight}; } RectF cellScreenRect(Vec2I const& c) const { Vec2F cdim = cellScreenDimensions(); Vec2F ll = Vec2F(cdim[0] * c[0], cdim[1] * c[1]); return RectF::withSize(ll, cdim); } Vec2I screenToCell(Vec2I const& screen) const { Vec2F cdim = cellScreenDimensions(); Vec2I cpos = Vec2I::floor(Vec2F(screen[0] / cdim[0], screen[1] / cdim[1])); return Vec2I(clamp(cpos[0], 0, LiquidArrayWidth - 1), clamp(cpos[1], 0, LiquidArrayHeight - 1)); } Vec4B liquidColor(size_t liquid, float level) { Vec4B color = m_liquids.at(liquid); return Color::v4fToByte(Color::v4bToFloat(color) * level); } RootUPtr m_root; LiquidArray m_liquidArray; shared_ptr> m_liquidEngine; List m_liquids; bool m_paused; bool m_step; uint64_t m_update; Vec2I m_mousePos; RendererPtr m_renderer; TextPainterPtr m_textPainter; Maybe m_mouseButtonHeld; size_t m_currentLiquid; }; STAR_MAIN_APPLICATION(LiquidTest);