95 const std::string &path,
96 const std::string &fragment_path,
97 const std::string &title,
100 bool fullscreen,
bool enable_vsync)
102 asset_root(path.empty() ? std::string(MOON_ASSET_DIR) : path) {
103 const std::string model_path = filename.empty() ? (asset_root +
"/data/moon.obj") : filename;
104 const std::string texture_base_path = asset_root +
"/data";
105 const std::string vert_path = asset_root +
"/data/model.vert.spv";
106 const std::string frag_path = fragment_path.empty() ? (asset_root +
"/data/model.frag.spv") : fragment_path;
108 model.load(
this, model_path,
"", texture_base_path, 1.0f);
109 model.setShaders(
this, vert_path, frag_path);
111 const std::string pyramid_path = asset_root +
"/data/pyramid.obj";
113 pyramid.load(
this, pyramid_path,
"", texture_base_path, 1.0f);
114 pyramid.setShaders(
this, vert_path, frag_path);
117 std::unique_ptr<SDL_Surface,
decltype(&SDL_DestroySurface)> star_surface(
loadColorKeyedPNG(asset_root +
"/data/star.png"), SDL_DestroySurface);
119 if (star_sprite ==
nullptr) {
122 star_sprite->setDepthTestEnabled(
true);
123 star_sprite->setDepthWriteEnabled(
false);
124 star_sprite->setAlphaDiscardThreshold(0.01f);
129 if (
device != VK_NULL_HANDLE) {
134 pyramid.cleanup(
this);
136 if (star_sprite !=
nullptr) {
137 star_sprite->cleanup();
142 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
147 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE) {
149 auto_spin_enabled = !auto_spin_enabled;
154 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
155 mouse_dragging =
true;
156 last_mouse_x =
static_cast<int>(e.button.x);
157 last_mouse_y =
static_cast<int>(e.button.y);
161 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
162 mouse_dragging =
false;
166 if (e.type == SDL_EVENT_MOUSE_MOTION && mouse_dragging) {
167 const int x =
static_cast<int>(e.motion.x);
168 const int y =
static_cast<int>(e.motion.y);
169 const int delta_x = x - last_mouse_x;
170 const int delta_y = y - last_mouse_y;
172 yaw_degrees +=
static_cast<float>(delta_x) * mouse_sensitivity;
173 pitch_degrees +=
static_cast<float>(delta_y) * mouse_sensitivity;
174 pitch_degrees = std::clamp(pitch_degrees, -80.0f, 80.0f);
181 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
182 const float delta = (e.wheel.y != 0.0f) ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
183 camera_distance -= delta * 0.45f;
184 camera_distance = std::clamp(camera_distance, 1.8f, 12.0f);
192 pyramid.resize(
this);
194 if (star_sprite !=
nullptr) {
195 star_sprite->resize(
this);
200 const auto now = std::chrono::steady_clock::now();
201 const float elapsed_seconds = std::chrono::duration<float>(now - start_time).count();
202 const float delta_seconds = std::chrono::duration<float>(now - last_frame_time).count();
203 last_frame_time = now;
204 if (auto_spin_enabled) {
205 auto_spin_radians += delta_seconds * auto_spin_speed;
209 const float aspect = (extent.height > 0U)
210 ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height)
213 glm::mat4 moon_rotation = glm::rotate(glm::mat4(1.0f), glm::radians(pitch_degrees), glm::vec3(1.0f, 0.0f, 0.0f));
214 moon_rotation = glm::rotate(moon_rotation, glm::radians(yaw_degrees), glm::vec3(0.0f, 1.0f, 0.0f));
215 moon_rotation = glm::rotate(moon_rotation, auto_spin_radians, glm::vec3(0.0f, 1.0f, 0.0f));
218 ubo.
model = moon_rotation;
219 ubo.
model = glm::scale(ubo.
model, glm::vec3(model.modelRenderScale()));
220 ubo.
model = glm::translate(ubo.
model, model.modelCenterOffset());
221 ubo.
view = glm::lookAt(glm::vec3(0.0f, 0.1f, camera_distance), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
222 ubo.
proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
223 ubo.
proj[1][1] *= -1.0f;
224 ubo.
fx = glm::vec4(elapsed_seconds, 0.0f, 0.0f, 0.37f);
226 model.updateUBO(imageIndex, ubo);
227 model.render(cmd, imageIndex,
false);
229 for (
size_t i = 0; i < pyramids.size(); ++i) {
231 pyramid_ubo.
model = pyramidTransform(moon_rotation, pyramids[i], pyramid_placements[i]);
232 pyramids[i].updateUBO(imageIndex, pyramid_ubo);
233 pyramids[i].render(cmd, imageIndex,
false);
236 if (star_sprite !=
nullptr) {
237 updateStars(elapsed_seconds);
238 star_sprite->updateCamera(imageIndex, ubo.
view, ubo.
proj);
240 const float twinkle = 0.72f + 0.28f * std::sin((elapsed_seconds * star.twinkle_speed) + star.twinkle_phase);
241 const float depth_fade = std::clamp((-star.position.z - 8.0f) / 42.0f, 0.2f, 1.0f);
242 const float alpha = std::clamp(star.brightness * twinkle * depth_fade, 0.08f, 0.95f);
243 const float size = star.size * 1.8f * (0.72f + (1.0f - depth_fade) * 0.38f) * twinkle;
244 star_sprite->drawSprite(star.position, glm::vec2(size), glm::vec4(star.color, alpha));
246 star_sprite->render(cmd, imageIndex);
247 star_sprite->clearQueue();
255 stars.push_back(makeStar(
true));
259 void updateStars(
float elapsed_seconds) {
260 const float delta_seconds = std::clamp(elapsed_seconds - last_star_update_seconds, 0.0f, 0.05f);
261 last_star_update_seconds = elapsed_seconds;
263 for (StarParticle &star : stars) {
264 star.position.z += star.speed * delta_seconds;
265 star.position.x += std::sin(elapsed_seconds * 0.26f + star.twinkle_phase) * star.drift * delta_seconds;
266 star.position.y += std::cos(elapsed_seconds * 0.19f + star.twinkle_phase) * star.drift * delta_seconds;
267 if (star.position.z > -6.0f) {
268 star = makeStar(
false);
273 [[nodiscard]] StarParticle makeStar(
bool randomize_depth) {
275 star.position.x = randomFloat(-26.0f, 26.0f);
276 star.position.y = randomFloat(-16.0f, 16.0f);
277 star.position.z = randomize_depth ? randomFloat(-66.0f, -8.0f) : randomFloat(-66.0f, -52.0f);
278 star.speed = randomFloat(0.16f, 1.25f);
279 star.brightness = randomFloat(0.22f, 1.0f);
280 star.twinkle_speed = randomFloat(1.5f, 5.5f);
281 star.twinkle_phase = randomFloat(0.0f, 6.28318530718f);
282 star.drift = randomFloat(0.01f, 0.09f);
284 const float density = randomFloat(0.0f, 1.0f);
285 if (density < 0.58f) {
286 star.size = randomFloat(0.030f, 0.075f);
287 star.brightness *= randomFloat(0.55f, 0.85f);
288 }
else if (density < 0.88f) {
289 star.size = randomFloat(0.075f, 0.150f);
290 star.brightness *= randomFloat(0.75f, 1.0f);
292 star.size = randomFloat(0.150f, 0.320f);
293 star.brightness *= randomFloat(0.95f, 1.20f);
296 const float tint = randomFloat(0.0f, 1.0f);
298 star.color = glm::vec3(0.70f, 0.82f, 1.0f);
299 }
else if (tint < 0.48f) {
300 star.color = glm::vec3(0.86f, 0.92f, 1.0f);
301 }
else if (tint < 0.72f) {
302 star.color = glm::vec3(1.0f, 0.95f, 0.82f);
303 }
else if (tint < 0.90f) {
304 star.color = glm::vec3(1.0f, 0.86f, 0.66f);
306 star.color = glm::vec3(1.0f, 1.0f, 1.0f);
311 [[nodiscard]]
float randomFloat(
float min_value,
float max_value) {
312 std::uniform_real_distribution<float> dist(min_value, max_value);
313 return dist(random_engine);
316 [[nodiscard]]
static glm::mat4 surfaceBasis(
const glm::vec3 &surface_normal,
float yaw_degrees) {
317 const glm::vec3 normal = glm::normalize(surface_normal);
318 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);
319 glm::vec3 tangent = glm::normalize(glm::cross(reference, normal));
320 glm::vec3 bitangent = glm::normalize(glm::cross(normal, tangent));
322 const float yaw = glm::radians(yaw_degrees);
323 tangent = glm::normalize((std::cos(yaw) * tangent) + (std::sin(yaw) * bitangent));
324 bitangent = glm::normalize(glm::cross(normal, tangent));
326 glm::mat4 basis(1.0f);
327 basis[0] = glm::vec4(tangent, 0.0f);
328 basis[1] = glm::vec4(normal, 0.0f);
329 basis[2] = glm::vec4(bitangent, 0.0f);
333 [[nodiscard]]
static glm::mat4 pyramidTransform(
const glm::mat4 &moon_rotation,
334 const mxvk::VKAbstractModel &pyramid,
335 const PyramidPlacement &placement) {
336 constexpr float MOON_RADIUS = 1.28f;
337 const glm::vec3 normal = glm::normalize(placement.surface_normal);
338 const float surface_offset = MOON_RADIUS + (placement.scale * 0.55f);
340 glm::mat4 transform = moon_rotation;
341 transform = glm::translate(transform, normal * surface_offset);
342 transform *= surfaceBasis(normal, placement.yaw_degrees);
343 transform = glm::scale(transform, glm::vec3(pyramid.
modelRenderScale() * placement.scale));
348 std::string asset_root;
349 mxvk::VKAbstractModel model{};
350 std::array<mxvk::VKAbstractModel, 7> pyramids{};
351 mxvk::VK_Sprite3D *star_sprite =
nullptr;
352 std::vector<StarParticle> stars{};
353 std::default_random_engine random_engine{1337U};
354 const std::array<PyramidPlacement, 7> pyramid_placements{{
355 {glm::vec3(0.18f, 0.98f, 0.08f), 0.055f, 12.0f},
356 {glm::vec3(-0.45f, 0.74f, 0.50f), 0.075f, 51.0f},
357 {glm::vec3(0.58f, 0.62f, 0.53f), 0.048f, 138.0f},
358 {glm::vec3(-0.76f, 0.43f, -0.20f), 0.062f, 204.0f},
359 {glm::vec3(0.70f, 0.22f, -0.58f), 0.085f, 296.0f},
360 {glm::vec3(-0.12f, -0.18f, 0.98f), 0.045f, 33.0f},
361 {glm::vec3(0.30f, -0.50f, -0.81f), 0.070f, 250.0f},
363 std::chrono::steady_clock::time_point start_time{std::chrono::steady_clock::now()};
364 bool mouse_dragging =
false;
365 bool auto_spin_enabled =
true;
366 int last_mouse_x = 0;
367 int last_mouse_y = 0;
368 float yaw_degrees = 0.0f;
369 float pitch_degrees = 8.0f;
370 float camera_distance = 4.3f;
371 float mouse_sensitivity = 0.35f;
372 float auto_spin_speed = 0.45f;
373 float auto_spin_radians = 0.0f;
374 float last_star_update_seconds = 0.0f;
375 std::chrono::steady_clock::time_point last_frame_time{std::chrono::steady_clock::now()};
376 static constexpr size_t STAR_COUNT = 1800;