11#include <glm/ext/matrix_clip_space.hpp>
12#include <glm/ext/matrix_transform.hpp>
45 void init(
mxvk::VK_Window *window,
const std::string &assetRoot,
const std::string &fragmentShaderPath) {
46 if (snowSprite !=
nullptr) {
50 snowSprite = window->
createSprite3D(assetRoot +
"/data/snowflake.png",
"", fragmentShaderPath);
52 snowSprite->setDepthWriteEnabled(
false);
53 snowSprite->setAlphaDiscardThreshold(0.1f);
57 snowflakes.push_back(makeFlake());
62 windTime += deltaSeconds * 1.2f;
63 const float windOffsetX = std::sin(windTime) * 0.12f;
64 const float windOffsetZ = std::cos(windTime * 0.7f) * 0.04f;
65 constexpr float twoPi = 6.28318530717958647692f;
67 for (
auto &flake : snowflakes) {
68 flake.y -= flake.speed * deltaSeconds;
69 flake.x += windOffsetX * deltaSeconds;
70 flake.z += windOffsetZ * deltaSeconds;
74 flake.rotation += (flake.rotationSpeed + std::sin(windTime + flake.rotationAngle) *
75 flake.rotationSpeedIntensity * 0.5f) *
77 if (flake.rotation > twoPi) {
78 flake.rotation -= twoPi;
79 }
else if (flake.rotation < -twoPi) {
80 flake.rotation += twoPi;
83 if (flake.y < -3.8f) {
90 void drawLayer(VkCommandBuffer cmd, uint32_t imageIndex,
const glm::mat4 &view,
const glm::mat4 &proj,
bool frontLayer) {
91 if (snowSprite ==
nullptr) {
95 snowSprite->updateCamera(imageIndex, view, proj);
96 for (
const SnowFlake &flake : snowflakes) {
97 const bool isFront = flake.z >= 0.0f;
98 if (isFront != frontLayer) {
102 snowSprite->drawSprite(glm::vec3(flake.x, flake.y, flake.z),
103 glm::vec2(flake.size, flake.size),
104 glm::vec4(1.0f, 1.0f, 1.0f, 1.0f),
108 snowSprite->render(cmd, imageIndex);
109 snowSprite->clearQueue();
113 static float randomSignedCoordinate() {
114 return static_cast<float>(std::rand() % 900 - 450) / 100.0f;
117 static float randomDepth() {
119 const float depth =
modelDepthClearance +
static_cast<float>(std::rand() % 1000) * (depthRange / 999.0f);
120 return (std::rand() % 2 == 0) ? -depth : depth;
123 static SnowFlake makeFlake() {
125 flake.x = randomSignedCoordinate();
126 flake.y =
static_cast<float>(std::rand() % 760 - 380) / 100.0f;
127 flake.z = randomDepth();
128 flake.intensity = 1.0f;
129 flake.size = 0.06f +
static_cast<float>(std::rand() % 180) / 1600.0f;
130 flake.speed = 0.25f +
static_cast<float>(std::rand() % 100) / 600.0f;
131 constexpr float pi = 3.14159265358979323846f;
132 flake.angle =
static_cast<float>(std::rand() % 360) * (pi / 180.0f);
133 flake.rotation =
static_cast<float>(std::rand() % 360) * (pi / 180.0f);
134 flake.rotationAngle =
static_cast<float>(std::rand() % 360) * (pi / 180.0f);
135 flake.rotationDirection = (std::rand() % 2) ? 1.0f : -1.0f;
136 flake.rotationIntensity =
static_cast<float>(std::rand() % 100) / 100.0f;
137 flake.rotationSpeed = flake.rotationDirection * (0.8f + flake.rotationIntensity * 2.0f) +
138 ((
static_cast<float>(std::rand() % 100) / 1000.0f) - 0.05f);
139 flake.rotationSize = 0.01f +
static_cast<float>(std::rand() % 100) / 4000.0f;
140 flake.rotationSpeedSize = (
static_cast<float>(std::rand() % 100) / 2000.0f) - 0.025f;
141 flake.rotationSpeedIntensity =
static_cast<float>(std::rand() % 100) / 100.0f;
145 std::vector<SnowFlake> snowflakes;
146 mxvk::VK_Sprite3D *snowSprite =
nullptr;
147 float windTime = 0.0f;
152 ModelWindow(
const std::string &filename,
const std::string &path,
const std::string &title,
int width,
int height,
bool fullscreen,
bool enable_vsync)
154 assetRoot((path.empty() || path ==
".") ? std::string(tux_example_ASSET_DIR) : path) {
155 const std::string shaderRoot = assetRoot +
"/data";
156 const std::string modelPath = filename.empty() ? (assetRoot +
"/data/tux.obj") : filename;
157 const std::string vertPath = shaderRoot +
"/model.vert.spv";
158 const std::string fragPath = shaderRoot +
"/model.frag.spv";
159 const std::string backgroundVertPath = shaderRoot +
"/background.vert.spv";
160 const std::string backgroundFragPath = shaderRoot +
"/background.frag.spv";
161 const std::string snowflakeFragPath = shaderRoot +
"/snowflake.frag.spv";
162 const std::string backgroundPath = assetRoot +
"/data/ant-bg.png";
164 setFont(assetRoot +
"/data/font.ttf", 24);
165 background =
createSprite(backgroundPath, backgroundVertPath, backgroundFragPath);
166 snow.init(
this, assetRoot, snowflakeFragPath);
167 model.load(
this, modelPath,
"", assetRoot +
"/data", 1.0f);
168 model.setShaders(
this, vertPath, fragPath);
172 if (
device != VK_NULL_HANDLE) {
179 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
186 printText(
"Tux Example", 15, 15, {255, 255, 255, 255});
194 const auto now = std::chrono::steady_clock::now();
195 const float elapsedSeconds = std::chrono::duration<float>(now - start).count();
199 background->setShaderParams(elapsedSeconds, 0.0f, 0.0f, 0.0f);
200 background->drawSpriteRect(0, 0,
static_cast<int>(extent.width),
static_cast<int>(extent.height));
203 background->clearQueue();
206 const float deltaSeconds = std::chrono::duration<float>(now - lastSnowUpdate).count();
207 lastSnowUpdate = now;
208 snow.update(deltaSeconds);
210 const float aspect = (extent.height > 0U)
211 ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height)
215 ubo.
model = glm::rotate(glm::mat4(1.0f), elapsedSeconds * autoSpinSpeed, glm::vec3(0.0f, 1.0f, 0.0f));
216 ubo.
model = glm::scale(ubo.
model, glm::vec3(model.modelRenderScale()));
217 ubo.
model = glm::translate(ubo.
model, model.modelCenterOffset());
218 ubo.
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 4.2f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
219 ubo.
proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
220 ubo.
proj[1][1] *= -1.0f;
222 model.updateUBO(imageIndex, ubo);
224 snow.drawLayer(cmd, imageIndex, ubo.
view, ubo.
proj,
false);
225 model.render(cmd, imageIndex,
false);
226 snow.drawLayer(cmd, imageIndex, ubo.
view, ubo.
proj,
true);
230 std::string assetRoot;
234 std::chrono::steady_clock::time_point start{std::chrono::steady_clock::now()};
235 std::chrono::steady_clock::time_point lastSnowUpdate{std::chrono::steady_clock::now()};
236 float autoSpinSpeed = 0.65f;
241int main(
int argc,
char **argv) {
247 std::cerr << std::format(
"mxvk: Exception: {}\n", e.
text());
250 std::cerr << std::format(
"mxvk: Argument Exception: {}\n", e.
text());
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Exception thrown by Argz::proc() on unrecognised or malformed options.
void proc() override
Execute one processing/update step.
void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t imageIndex) override
Optional hook for derived classes to record extra draw commands.
void onSwapchainRecreated() override
Called after swapchain and render resources are recreated.
void event(SDL_Event &e) override
Handle one SDL event.
ModelWindow(const std::string &filename, const std::string &path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync)
void init(mxvk::VK_Window *window, const std::string &assetRoot, const std::string &fragmentShaderPath)
void drawLayer(VkCommandBuffer cmd, uint32_t imageIndex, const glm::mat4 &view, const glm::mat4 &proj, bool frontLayer)
static constexpr int MAX_SNOWFLAKES
static constexpr float modelDepthClearance
void update(float deltaSeconds)
Convenience wrapper that owns mesh, textures, descriptors, and pipeline state.
void setDepthTestEnabled(bool enabled)
Enable or disable depth testing for the 3D sprite pipeline.
Main Vulkan window wrapper for MXVK.
VkPipelineLayout sprite_pipeline_layout
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
void loop()
Run the main event/render loop.
VK_Sprite3D * createSprite3D(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a world-space billboard sprite from a PNG file.
VK_Sprite * createSprite(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a sprite from a PNG file and register it with this window.
VkPipeline sprite_pipeline
void exit()
Request loop termination.
VK_Window()=default
Construct an empty window object.
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
void printText(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for rendering during the current frame.
High-level model wrapper integrated with MXVK dynamic rendering.
Utilities for loading and saving PNG images.
Plain data structure returned by proc_args() with all common libmx2 CLI options.
bool fullscreen
Whether fullscreen mode was requested.
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
int height
Viewport height in pixels (default: 720).
std::string filename
Optional input filename (--filename).
std::string path
Asset root; proc_args() defaults it to the executable directory.
int width
Viewport width in pixels (default: 1280).
float rotationSpeedIntensity