31 bool usingDefaultModel,
32 const std::string &path,
33 const std::string &resource,
34 const std::string &resource_path,
35 const std::string &fragmentShaderPath,
36 const std::string &texture_path,
37 const std::string &title,
42 bool binaryTextureMode,
44 const std::string &fontPath,
45 const std::string &color)
47 assetRoot(path.empty() ? std::string(MODEL_EXAMPLE_ASSET_DIR) : path),
48 binaryTextureMode(binaryTextureMode) {
49 const std::string modelPath = filename;
50 const std::string textureManifestPath = resource.empty() && usingDefaultModel ? assetRoot +
"/data/texture_manifest.txt" : resource;
51 const bool useDefaultTextureBase = resource_path.empty() && (usingDefaultModel || !resource.empty());
52 const std::string textureBasePath = resource_path.empty() ? (useDefaultTextureBase ? assetRoot +
"/data" :
"") : resource_path;
53 const std::string vertPath = assetRoot +
"/data/model.vert.spv";
54 const std::string fragPath = fragmentShaderPath.empty() ? (assetRoot +
"/data/model.frag.spv") : fragmentShaderPath;
55 if (!texture_path.empty())
57 model.load(
this, modelPath, textureManifestPath, textureBasePath, 1.0f);
58 model.setShaders(
this, vertPath, fragPath);
59 model.setBackfaceCulling(!binaryTextureMode);
60 if (binaryTextureMode) {
64 rainConfig.
font_size = std::max(1, fontSize);
65 if (!fontPath.empty()) {
68 rainConfig.
color = color;
69 binaryMatrixTexture = std::make_unique<matrix::Rain>(std::move(rainConfig));
74 if (
device != VK_NULL_HANDLE) {
80 void event(SDL_Event &e)
override {
81 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
86 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_SPACE) {
90 autoSpinEnabled = !autoSpinEnabled;
94 if (e.type == SDL_EVENT_KEY_DOWN && (e.key.key == SDLK_RETURN || e.key.key == SDLK_KP_ENTER)) {
98 skyboxMode = !skyboxMode;
99 model.setBackfaceCulling(!(binaryTextureMode || skyboxMode));
106 if (e.type == SDL_EVENT_KEY_DOWN || e.type == SDL_EVENT_KEY_UP) {
107 const bool pressed = (e.type == SDL_EVENT_KEY_DOWN);
108 if (e.key.key == SDLK_W) {
109 skyboxLookUpKey = pressed;
110 }
else if (e.key.key == SDLK_S) {
111 skyboxLookDownKey = pressed;
112 }
else if (e.key.key == SDLK_A) {
113 skyboxLookLeftKey = pressed;
114 }
else if (e.key.key == SDLK_D) {
115 skyboxLookRightKey = pressed;
116 }
else if (e.key.key == SDLK_UP) {
118 }
else if (e.key.key == SDLK_DOWN) {
119 zoomOutKey = pressed;
123 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
124 mouseDragging =
true;
125 lastMouseX =
static_cast<int>(e.button.x);
126 lastMouseY =
static_cast<int>(e.button.y);
130 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
131 mouseDragging =
false;
135 if (e.type == SDL_EVENT_MOUSE_MOTION && mouseDragging) {
136 const int x =
static_cast<int>(e.motion.x);
137 const int y =
static_cast<int>(e.motion.y);
138 const int deltaX = x - lastMouseX;
139 const int deltaY = y - lastMouseY;
142 skyboxYawDegrees +=
static_cast<float>(deltaX) * mouseSensitivity;
143 skyboxPitchDegrees +=
static_cast<float>(deltaY) * mouseSensitivity;
144 skyboxPitchDegrees = std::fmod(skyboxPitchDegrees, 360.0f);
146 yawDegrees +=
static_cast<float>(deltaX) * mouseSensitivity;
147 pitchDegrees +=
static_cast<float>(deltaY) * mouseSensitivity;
148 pitchDegrees = std::clamp(pitchDegrees, -80.0f, 80.0f);
156 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
157 const float delta = (e.wheel.y != 0.0f) ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
159 const float zoomSpeed = 0.45f;
160 skyboxCameraPosition += skyboxForwardVector() * (delta * zoomSpeed);
162 const float maxSkyboxDistance = 12.0f;
163 const float skyboxDistance = glm::length(skyboxCameraPosition);
164 if (skyboxDistance > maxSkyboxDistance) {
165 skyboxCameraPosition = glm::normalize(skyboxCameraPosition) * maxSkyboxDistance;
168 adjustZoomTarget(delta);
179 const auto now = std::chrono::steady_clock::now();
180 const float deltaSeconds = std::chrono::duration<float>(now - lastFrameTime).count();
181 const float elapsedSeconds = std::chrono::duration<float>(now - startTime).count();
183 if (autoSpinEnabled) {
184 autoSpinRadians += deltaSeconds * autoSpinSpeed;
188 updateSkyboxCamera(deltaSeconds);
190 updateZoom(deltaSeconds);
195 const float aspect = (extent.height > 0U)
196 ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height)
201 const glm::vec3 front = skyboxForwardVector();
202 const glm::vec3 target = skyboxCameraPosition + front;
203 ubo.
model = glm::scale(glm::mat4(1.0f), glm::vec3(model.modelRenderScale() * skyboxScaleMultiplier));
204 ubo.
model = glm::translate(ubo.
model, model.modelCenterOffset());
205 ubo.
view = glm::lookAt(skyboxCameraPosition, target, skyboxUpVector());
206 ubo.
proj = glm::perspective(glm::radians(70.0f), aspect, 0.02f, 100.0f);
208 ubo.
model = glm::rotate(glm::mat4(1.0f), glm::radians(pitchDegrees), glm::vec3(1.0f, 0.0f, 0.0f));
209 ubo.
model = glm::rotate(ubo.
model, glm::radians(yawDegrees) + autoSpinRadians, glm::vec3(0.0f, 1.0f, 0.0f));
210 ubo.
model = glm::scale(ubo.
model, glm::vec3(model.modelRenderScale()));
211 ubo.
model = glm::translate(ubo.
model, model.modelCenterOffset());
212 ubo.
view = glm::lookAt(glm::vec3(0.0f, 0.0f, cameraDistance), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
213 ubo.
proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
215 ubo.
proj[1][1] *= -1.0f;
216 ubo.
fx = glm::vec4(elapsedSeconds, 0.0f, 0.0f, 0.37f);
221 background->clearQueue();
224 model.updateUBO(imageIndex, ubo);
225 if (binaryTextureMode && binaryMatrixTexture !=
nullptr) {
226 binaryMatrixTexture->update(deltaSeconds);
227 const int textureWidth = binaryMatrixTexture->width();
228 const int textureHeight = binaryMatrixTexture->height();
229 const int texturePitch = binaryMatrixTexture->pitch();
230 const void *texturePixels = flippedMatrixTexturePixels(textureWidth, textureHeight, texturePitch);
231 [[maybe_unused]]
const bool textureUpdated = model.updatePrimaryTexture(
232 texturePixels, textureWidth, textureHeight, textureWidth * 4);
234 model.render(cmd, imageIndex,
false);
238 std::string assetRoot;
241 bool binaryTextureMode =
false;
242 std::unique_ptr<matrix::Rain> binaryMatrixTexture{};
243 bool mouseDragging =
false;
244 bool skyboxMode =
false;
245 bool skyboxLookUpKey =
false;
246 bool skyboxLookDownKey =
false;
247 bool skyboxLookLeftKey =
false;
248 bool skyboxLookRightKey =
false;
249 bool autoSpinEnabled =
true;
252 float yawDegrees = 0.0f;
253 float pitchDegrees = 12.0f;
254 float cameraDistance = 4.2f;
255 float targetCameraDistance = 4.2f;
256 glm::vec3 skyboxCameraPosition{0.0f, 0.0f, 0.0f};
257 float skyboxYawDegrees = 180.0f;
258 float skyboxPitchDegrees = 0.0f;
259 float skyboxScaleMultiplier = 1.6f;
260 float mouseSensitivity = 0.35f;
261 float autoSpinSpeed = 0.65f;
262 float autoSpinRadians = 0.0f;
263 std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
264 std::chrono::steady_clock::time_point lastFrameTime = std::chrono::steady_clock::now();
265 std::vector<Uint8> flippedMatrixTexture;
266 bool zoomInKey =
false;
267 bool zoomOutKey =
false;
269 void adjustZoomTarget(
float wheelDelta) {
270 if (wheelDelta == 0.0f) {
274 constexpr float zoomStep = 1.12f;
275 targetCameraDistance *= std::pow(zoomStep, -wheelDelta);
276 targetCameraDistance = std::clamp(targetCameraDistance, 1.8f, 12.0f);
279 void updateZoom(
float deltaSeconds) {
280 const float keyDelta = (zoomInKey ? 1.0f : 0.0f) - (zoomOutKey ? 1.0f : 0.0f);
281 if (keyDelta != 0.0f) {
282 constexpr float keyZoomStep = 1.09f;
283 targetCameraDistance *= std::pow(keyZoomStep, -keyDelta * deltaSeconds * 60.0f);
284 targetCameraDistance = std::clamp(targetCameraDistance, 1.8f, 12.0f);
287 constexpr float zoomResponsiveness = 14.0f;
288 const float smoothing = 1.0f - std::exp(-zoomResponsiveness * deltaSeconds);
289 cameraDistance += (targetCameraDistance - cameraDistance) * smoothing;
292 [[nodiscard]]
const void *flippedMatrixTexturePixels(
int width,
int height,
int pitch) {
293 const auto *pixels =
static_cast<const Uint8 *
>(binaryMatrixTexture !=
nullptr ? binaryMatrixTexture->pixels() :
nullptr);
294 if (pixels ==
nullptr || width <= 0 || height <= 0 || pitch <= 0) {
298 const int rowBytes = width * 4;
299 flippedMatrixTexture.resize(
static_cast<std::size_t
>(rowBytes * height));
300 for (
int y = 0; y < height; ++y) {
301 const Uint8 *src = pixels +
static_cast<std::size_t
>(height - 1 - y) *
static_cast<std::size_t
>(pitch);
302 Uint8 *dst = flippedMatrixTexture.data() +
static_cast<std::size_t
>(y) *
static_cast<std::size_t
>(rowBytes);
303 std::memcpy(dst, src,
static_cast<std::size_t
>(rowBytes));
305 return flippedMatrixTexture.data();
308 void resetSkyboxCamera() {
309 skyboxCameraPosition = glm::vec3(0.0f);
310 skyboxYawDegrees = 180.0f;
311 skyboxPitchDegrees = 0.0f;
314 [[nodiscard]] glm::vec3 skyboxUpVector()
const {
315 const float yawRadians = glm::radians(skyboxYawDegrees);
316 const float upPitchRadians = glm::radians(skyboxPitchDegrees + 90.0f);
317 return glm::normalize(glm::vec3{
318 std::sin(yawRadians) * std::cos(upPitchRadians),
319 std::sin(upPitchRadians),
320 -std::cos(yawRadians) * std::cos(upPitchRadians)});
323 [[nodiscard]] glm::vec3 skyboxForwardVector()
const {
324 const float yawRadians = glm::radians(skyboxYawDegrees);
325 const float pitchRadians = glm::radians(skyboxPitchDegrees);
326 return glm::normalize(glm::vec3{
327 std::sin(yawRadians) * std::cos(pitchRadians),
328 std::sin(pitchRadians),
329 -std::cos(yawRadians) * std::cos(pitchRadians)});
332 [[nodiscard]] glm::vec3 skyboxRightVector()
const {
333 return glm::normalize(glm::cross(skyboxForwardVector(), skyboxUpVector()));
336 void updateSkyboxCamera(
float deltaSeconds) {
337 const float lookSpeed = 85.0f;
338 if (skyboxLookLeftKey) {
339 skyboxYawDegrees -= lookSpeed * deltaSeconds;
341 if (skyboxLookRightKey) {
342 skyboxYawDegrees += lookSpeed * deltaSeconds;
344 if (skyboxLookUpKey) {
345 skyboxPitchDegrees += lookSpeed * deltaSeconds;
347 if (skyboxLookDownKey) {
348 skyboxPitchDegrees -= lookSpeed * deltaSeconds;
350 skyboxPitchDegrees = std::fmod(skyboxPitchDegrees, 360.0f);
351 if (skyboxPitchDegrees < 0.0f) {
352 skyboxPitchDegrees += 360.0f;
355 const float keyZoomDelta = (zoomInKey ? 1.0f : 0.0f) - (zoomOutKey ? 1.0f : 0.0f);
356 if (keyZoomDelta != 0.0f) {
357 constexpr float zoomSpeed = 2.25f;
358 skyboxCameraPosition += skyboxForwardVector() * (keyZoomDelta * zoomSpeed * deltaSeconds);
ModelWindow(const std::string filename, bool usingDefaultModel, const std::string &path, const std::string &resource, const std::string &resource_path, const std::string &fragmentShaderPath, const std::string &texture_path, const std::string &title, int width, int height, bool fullscreen, bool enable_vsync, bool binaryTextureMode, int fontSize, const std::string &fontPath, const std::string &color)