94 MoonWindow(
const std::string &filename,
const std::string &path,
const std::string &fragment_path,
const std::string &title,
int width,
int height,
bool fullscreen,
bool enable_vsync) :
mxvk::
VK_Window(title, width, height, fullscreen,
MXVK_VALIDATION, enable_vsync), asset_root(path.empty() ? std::string(MOON_ASSET_DIR) : path) {
95 const std::string model_path = filename.empty() ? (asset_root +
"/data/moon.obj") : filename;
96 const std::string texture_base_path = asset_root +
"/data";
97 const std::string vert_path = asset_root +
"/data/model.vert.spv";
98 const std::string frag_path = fragment_path.empty() ? (asset_root +
"/data/model.frag.spv") : fragment_path;
100 model.load(
this, model_path,
"", texture_base_path, 1.0f);
101 model.setShaders(
this, vert_path, frag_path);
103 const std::string pyramid_path = asset_root +
"/data/pyramid.obj";
105 pyramid.load(
this, pyramid_path,
"", texture_base_path, 1.0f);
106 pyramid.setShaders(
this, vert_path, frag_path);
109 std::unique_ptr<SDL_Surface,
decltype(&SDL_DestroySurface)> star_surface(
loadColorKeyedPNG(asset_root +
"/data/star.png"), SDL_DestroySurface);
111 if (star_sprite ==
nullptr) {
114 star_sprite->setDepthTestEnabled(
true);
115 star_sprite->setDepthWriteEnabled(
false);
116 star_sprite->setAlphaDiscardThreshold(0.01f);
121 if (
device != VK_NULL_HANDLE) {
126 pyramid.cleanup(
this);
128 if (star_sprite !=
nullptr) {
129 star_sprite->cleanup();
134 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
139 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE) {
141 auto_spin_enabled = !auto_spin_enabled;
146 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
147 mouse_dragging =
true;
148 last_mouse_x =
static_cast<int>(e.button.x);
149 last_mouse_y =
static_cast<int>(e.button.y);
153 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
154 mouse_dragging =
false;
158 if (e.type == SDL_EVENT_MOUSE_MOTION && mouse_dragging) {
159 const int x =
static_cast<int>(e.motion.x);
160 const int y =
static_cast<int>(e.motion.y);
161 const int delta_x = x - last_mouse_x;
162 const int delta_y = y - last_mouse_y;
164 yaw_degrees +=
static_cast<float>(delta_x) * mouse_sensitivity;
165 pitch_degrees +=
static_cast<float>(delta_y) * mouse_sensitivity;
166 pitch_degrees = std::clamp(pitch_degrees, -80.0f, 80.0f);
173 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
174 const float delta = (e.wheel.y != 0.0f) ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
175 camera_distance -= delta * 0.45f;
176 camera_distance = std::clamp(camera_distance, 1.8f, 12.0f);
184 pyramid.resize(
this);
186 if (star_sprite !=
nullptr) {
187 star_sprite->resize(
this);
192 const auto now = std::chrono::steady_clock::now();
193 const float elapsed_seconds = std::chrono::duration<float>(now - start_time).count();
194 const float delta_seconds = std::chrono::duration<float>(now - last_frame_time).count();
195 last_frame_time = now;
196 if (auto_spin_enabled) {
197 auto_spin_radians += delta_seconds * auto_spin_speed;
201 const float aspect = (extent.height > 0U) ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height) : 1.0f;
203 glm::mat4 moon_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(pitch_degrees), glm::vec3(1.0f, 0.0f, 0.0f));
204 moon_rotation = glm::rotate(moon_rotation, glm::radians(yaw_degrees), glm::vec3(0.0f, 1.0f, 0.0f));
205 moon_rotation = glm::rotate(moon_rotation, auto_spin_radians, glm::vec3(0.0f, 1.0f, 0.0f));
208 ubo.
model = moon_rotation;
209 ubo.
model = glm::scale(ubo.
model, glm::vec3(
model.modelRenderScale()));
211 ubo.
view = glm::lookAt(glm::vec3(0.0f, 0.1f, camera_distance), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
212 ubo.
proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
213 ubo.
proj[1][1] *= -1.0f;
214 ubo.
fx = glm::vec4(elapsed_seconds, 0.0f, 0.0f, 0.37f);
216 model.updateUBO(imageIndex, ubo);
217 model.render(cmd, imageIndex,
false);
219 for (
size_t i = 0; i < pyramids.size(); ++i) {
221 pyramid_ubo.
model = pyramidTransform(moon_rotation, pyramids[i], pyramid_placements[i]);
222 pyramids[i].updateUBO(imageIndex, pyramid_ubo);
223 pyramids[i].render(cmd, imageIndex,
false);
226 if (star_sprite !=
nullptr) {
227 updateStars(elapsed_seconds);
228 star_sprite->updateCamera(imageIndex, ubo.
view, ubo.
proj);
230 const float twinkle = 0.72f + 0.28f * std::sin((elapsed_seconds * star.twinkle_speed) + star.twinkle_phase);
231 const float depth_fade = std::clamp((-star.position.z - 8.0f) / 42.0f, 0.2f, 1.0f);
232 const float alpha = std::clamp(star.brightness * twinkle * depth_fade, 0.08f, 0.95f);
233 const float size = star.size * 1.8f * (0.72f + (1.0f - depth_fade) * 0.38f) * twinkle;
234 star_sprite->drawSprite(star.position, glm::vec2(size), glm::vec4(star.color, alpha));
236 star_sprite->render(cmd, imageIndex);
237 star_sprite->clearQueue();
245 stars.push_back(makeStar(
true));
249 void updateStars(
float elapsed_seconds) {
250 const float delta_seconds = std::clamp(elapsed_seconds - last_star_update_seconds, 0.0f, 0.05f);
251 last_star_update_seconds = elapsed_seconds;
253 for (StarParticle &star : stars) {
254 star.position.z += star.speed * delta_seconds;
255 star.position.x += std::sin(elapsed_seconds * 0.26f + star.twinkle_phase) * star.drift * delta_seconds;
256 star.position.y += std::cos(elapsed_seconds * 0.19f + star.twinkle_phase) * star.drift * delta_seconds;
257 if (star.position.z > -6.0f) {
258 star = makeStar(
false);
263 [[nodiscard]] StarParticle makeStar(
bool randomize_depth) {
265 star.position.x = randomFloat(-26.0f, 26.0f);
266 star.position.y = randomFloat(-16.0f, 16.0f);
267 star.position.z = randomize_depth ? randomFloat(-66.0f, -8.0f) : randomFloat(-66.0f, -52.0f);
268 star.speed = randomFloat(0.16f, 1.25f);
269 star.brightness = randomFloat(0.22f, 1.0f);
270 star.twinkle_speed = randomFloat(1.5f, 5.5f);
271 star.twinkle_phase = randomFloat(0.0f, 6.28318530718f);
272 star.drift = randomFloat(0.01f, 0.09f);
274 const float density = randomFloat(0.0f, 1.0f);
275 if (density < 0.58f) {
276 star.size = randomFloat(0.030f, 0.075f);
277 star.brightness *= randomFloat(0.55f, 0.85f);
278 }
else if (density < 0.88f) {
279 star.size = randomFloat(0.075f, 0.150f);
280 star.brightness *= randomFloat(0.75f, 1.0f);
282 star.size = randomFloat(0.150f, 0.320f);
283 star.brightness *= randomFloat(0.95f, 1.20f);
286 const float tint = randomFloat(0.0f, 1.0f);
288 star.color = glm::vec3(0.70f, 0.82f, 1.0f);
289 }
else if (tint < 0.48f) {
290 star.color = glm::vec3(0.86f, 0.92f, 1.0f);
291 }
else if (tint < 0.72f) {
292 star.color = glm::vec3(1.0f, 0.95f, 0.82f);
293 }
else if (tint < 0.90f) {
294 star.color = glm::vec3(1.0f, 0.86f, 0.66f);
296 star.color = glm::vec3(1.0f, 1.0f, 1.0f);
301 [[nodiscard]]
float randomFloat(
float min_value,
float max_value) {
302 std::uniform_real_distribution<float> dist(min_value, max_value);
303 return dist(random_engine);
306 [[nodiscard]]
static glm::mat4 surfaceBasis(
const glm::vec3 &surface_normal,
float yaw_degrees) {
307 const glm::vec3 normal = glm::normalize(surface_normal);
308 const glm::vec3 reference = (std::abs(normal.y) > 0.92f) ? glm::vec3(1.0f, 0.0f, 0.0f) : glm::vec3(0.0f, 1.0f, 0.0f);
309 glm::vec3 tangent = glm::normalize(glm::cross(reference, normal));
310 glm::vec3 bitangent = glm::normalize(glm::cross(normal, tangent));
312 const float yaw = glm::radians(yaw_degrees);
313 tangent = glm::normalize((std::cos(yaw) * tangent) + (std::sin(yaw) * bitangent));
314 bitangent = glm::normalize(glm::cross(normal, tangent));
316 glm::mat4 basis(1.0f);
317 basis[0] = glm::vec4(tangent, 0.0f);
318 basis[1] = glm::vec4(normal, 0.0f);
319 basis[2] = glm::vec4(bitangent, 0.0f);
323 [[nodiscard]]
static glm::mat4 pyramidTransform(
const glm::mat4 &moon_rotation,
const mxvk::VKAbstractModel &pyramid,
const PyramidPlacement &placement) {
324 constexpr float MOON_RADIUS = 1.28f;
325 const glm::vec3 normal = glm::normalize(placement.surface_normal);
326 const float surface_offset = MOON_RADIUS + (placement.scale * 0.55f);
328 glm::mat4 transform = moon_rotation;
329 transform = glm::translate(transform, normal * surface_offset);
330 transform *= surfaceBasis(normal, placement.yaw_degrees);
331 transform = glm::scale(transform, glm::vec3(pyramid.
modelRenderScale() * placement.scale));
336 std::string asset_root;
337 mxvk::VKAbstractModel model{};
338 std::array<mxvk::VKAbstractModel, 7> pyramids{};
339 mxvk::VK_Sprite3D *star_sprite =
nullptr;
340 std::vector<StarParticle> stars{};
341 std::default_random_engine random_engine{1337U};
342 const std::array<PyramidPlacement, 7> pyramid_placements{{
343 {glm::vec3(0.18f, 0.98f, 0.08f), 0.055f, 12.0f},
344 {glm::vec3(-0.45f, 0.74f, 0.50f), 0.075f, 51.0f},
345 {glm::vec3(0.58f, 0.62f, 0.53f), 0.048f, 138.0f},
346 {glm::vec3(-0.76f, 0.43f, -0.20f), 0.062f, 204.0f},
347 {glm::vec3(0.70f, 0.22f, -0.58f), 0.085f, 296.0f},
348 {glm::vec3(-0.12f, -0.18f, 0.98f), 0.045f, 33.0f},
349 {glm::vec3(0.30f, -0.50f, -0.81f), 0.070f, 250.0f},
351 std::chrono::steady_clock::time_point start_time{std::chrono::steady_clock::now()};
352 bool mouse_dragging =
false;
353 bool auto_spin_enabled =
true;
354 int last_mouse_x = 0;
355 int last_mouse_y = 0;
356 float yaw_degrees = 0.0f;
357 float pitch_degrees = 8.0f;
358 float camera_distance = 4.3f;
359 float mouse_sensitivity = 0.35f;
360 float auto_spin_speed = 0.45f;
361 float auto_spin_radians = 0.0f;
362 float last_star_update_seconds = 0.0f;
363 std::chrono::steady_clock::time_point last_frame_time{std::chrono::steady_clock::now()};
364 static constexpr size_t STAR_COUNT = 1800;