36 shaderRoot(args.shaderPath.empty() ? assetRoot +
"/data" : args.shaderPath),
37 cameraIndex(args.camera_index),
38 fallbackWidth(args.width),
39 fallbackHeight(args.height) {
41 modelPath = resolveModelPath(args.
filename, assetRoot);
42 if (modelPath.empty()) {
43 throw mxvk::Exception(
"opencv_model: args.filename must point to a model file (.obj/.mxmod/.mxmod.z)");
45 setFont(assetRoot +
"/data/font.ttf", 20);
47 if (!capture.open(cameraIndex)) {
48 throw mxvk::Exception(std::format(
"opencv_model: failed to open camera index {}", cameraIndex));
51 capture.set(cv::CAP_PROP_FRAME_WIDTH,
static_cast<double>(fallbackWidth));
52 capture.set(cv::CAP_PROP_FRAME_HEIGHT,
static_cast<double>(fallbackHeight));
53 fps = configureCameraFps();
55 fallbackWidth =
static_cast<int>(capture.get(cv::CAP_PROP_FRAME_WIDTH));
56 fallbackHeight =
static_cast<int>(capture.get(cv::CAP_PROP_FRAME_HEIGHT));
58 std::cout <<
"opencv_model: model='" << modelPath <<
"' capture="
59 << fallbackWidth <<
"x" << fallbackHeight <<
" @ " << fps <<
" fps\n";
61 const std::string vertPath = shaderRoot +
"/model.vert.spv";
62 const std::string fragPath = shaderRoot +
"/model.frag.spv";
63 model.load(
this, modelPath,
"",
"", 1.0f);
64 model.setShaders(
this, vertPath, fragPath);
66 if (!capture.readToModelTexture(model,
true)) {
67 std::cerr <<
"opencv_model: failed to upload initial camera frame\n";
71 if (
device != VK_NULL_HANDLE) {
80 if (
device != VK_NULL_HANDLE) {
88 void event(SDL_Event &e)
override {
89 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
94 if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT) {
96 lastMouseX =
static_cast<int>(e.button.x);
97 lastMouseY =
static_cast<int>(e.button.y);
101 if (e.type == SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT) {
102 mouseDragging =
false;
106 if (e.type == SDL_EVENT_MOUSE_MOTION && mouseDragging) {
107 const int x =
static_cast<int>(e.motion.x);
108 const int y =
static_cast<int>(e.motion.y);
109 const int deltaX = x - lastMouseX;
110 const int deltaY = y - lastMouseY;
112 mouseYawDegrees +=
static_cast<float>(deltaX) * mouseSensitivity;
113 mousePitchDegrees +=
static_cast<float>(deltaY) * mouseSensitivity;
114 mousePitchDegrees = std::clamp(mousePitchDegrees, -80.0f, 80.0f);
121 if (e.type == SDL_EVENT_MOUSE_WHEEL) {
122 const float delta = (e.wheel.y != 0.0f) ? e.wheel.y :
static_cast<float>(e.wheel.integer_y);
123 cameraDistance -= delta * 0.45f;
124 cameraDistance = std::clamp(cameraDistance, 1.8f, 12.0f);
134 const auto now = std::chrono::steady_clock::now();
135 const float deltaSeconds = std::clamp(
136 std::chrono::duration<float>(now - lastUpdateTime).count(),
139 lastUpdateTime = now;
140 updateRotationFromKeyboard(deltaSeconds);
142 if (!capture.readToModelTexture(model,
true)) {
144 if (!capture.open(cameraIndex)) {
147 capture.set(cv::CAP_PROP_FRAME_WIDTH,
static_cast<double>(fallbackWidth));
148 capture.set(cv::CAP_PROP_FRAME_HEIGHT,
static_cast<double>(fallbackHeight));
149 fps = configureCameraFps();
150 if (!capture.readToModelTexture(model,
true)) {
159 const float elapsedSeconds = std::chrono::duration<float>(std::chrono::steady_clock::now() - startTime).count();
161 const float aspect = (extent.height > 0U)
162 ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height)
166 ubo.
model = glm::mat4(1.0f);
167 ubo.
model = glm::rotate(ubo.
model, glm::radians(mousePitchDegrees), glm::vec3(1.0f, 0.0f, 0.0f));
168 ubo.
model = glm::rotate(ubo.
model, glm::radians(mouseYawDegrees), glm::vec3(0.0f, 1.0f, 0.0f));
169 ubo.
model = glm::rotate(ubo.
model, pitchRadians, glm::vec3(1.0f, 0.0f, 0.0f));
170 ubo.
model = glm::rotate(ubo.
model, yawRadians + autoSpinRadians, glm::vec3(0.0f, 1.0f, 0.0f));
171 ubo.
model = glm::scale(ubo.
model, glm::vec3(model.modelRenderScale()));
172 ubo.
model = glm::translate(ubo.
model, model.modelCenterOffset());
173 ubo.
view = glm::lookAt(glm::vec3(0.0f, 0.15f, cameraDistance), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
174 ubo.
proj = glm::perspective(glm::radians(50.0f), aspect, 0.1f, 100.0f);
175 ubo.
proj[1][1] *= -1.0f;
176 ubo.
fx = glm::vec4(elapsedSeconds, 0.32f, 0.18f + 0.12f * std::sin(elapsedSeconds * 1.2f), 0.0f);
178 model.updateUBO(imageIndex, ubo);
179 model.render(cmd, imageIndex,
false);
183 [[nodiscard]]
double configureCameraFps() {
184 static constexpr std::array<double, 3> fpsChoices = {60.0, 30.0, 24.0};
186 for (
const double requestedFps : fpsChoices) {
187 capture.
set(cv::CAP_PROP_FPS, requestedFps);
188 const double reportedFps = capture.
get(cv::CAP_PROP_FPS);
189 if (reportedFps > 0.0 && reportedFps + 0.5 >= requestedFps) {
194 capture.set(cv::CAP_PROP_FPS, fpsChoices.back());
195 const double reportedFps = capture.get(cv::CAP_PROP_FPS);
196 return (reportedFps > 0.0) ? reportedFps : fpsChoices.back();
199 [[nodiscard]]
static std::string resolveModelPath(
const std::string &filename,
const std::string &assetRoot) {
200 if (filename.empty()) {
204 namespace fs = std::filesystem;
205 const fs::path requested(filename);
206 std::error_code ec{};
207 if (fs::exists(requested, ec)) {
208 return fs::weakly_canonical(requested, ec).string();
212 if (requested.is_absolute()) {
216 const fs::path assetPath(assetRoot);
217 const fs::path sourceRoot = assetPath.parent_path().parent_path();
218 const std::array<fs::path, 3> candidates = {
219 assetPath / requested,
220 sourceRoot / requested,
221 sourceRoot /
"models" / requested.filename(),
224 for (
const fs::path &candidate : candidates) {
225 if (fs::exists(candidate, ec)) {
226 return fs::weakly_canonical(candidate, ec).string();
234 [[nodiscard]]
static float wrapAngle(
float angleRadians) {
235 constexpr float TWO_PI = 6.28318530718f;
236 float wrapped = std::fmod(angleRadians, TWO_PI);
237 if (wrapped < 0.0f) {
243 void updateRotationFromKeyboard(
float deltaSeconds) {
244 const bool *keys = SDL_GetKeyboardState(
nullptr);
245 if (keys ==
nullptr) {
249 constexpr float MANUAL_SPEED = glm::radians(120.0f);
250 constexpr float AUTO_SPIN_SPEED = 0.55f;
252 bool usingArrowKeys =
false;
253 if (keys[SDL_SCANCODE_LEFT]) {
254 yawRadians -= MANUAL_SPEED * deltaSeconds;
255 usingArrowKeys =
true;
257 if (keys[SDL_SCANCODE_RIGHT]) {
258 yawRadians += MANUAL_SPEED * deltaSeconds;
259 usingArrowKeys =
true;
261 if (keys[SDL_SCANCODE_UP]) {
262 pitchRadians -= MANUAL_SPEED * deltaSeconds;
263 usingArrowKeys =
true;
265 if (keys[SDL_SCANCODE_DOWN]) {
266 pitchRadians += MANUAL_SPEED * deltaSeconds;
267 usingArrowKeys =
true;
270 if (!usingArrowKeys) {
271 autoSpinRadians += AUTO_SPIN_SPEED * deltaSeconds;
274 yawRadians = wrapAngle(yawRadians);
275 pitchRadians = wrapAngle(pitchRadians);
276 autoSpinRadians = wrapAngle(autoSpinRadians);
279 void updateFpsOverlay() {
281 const auto now = std::chrono::steady_clock::now();
282 const double elapsed = std::chrono::duration<double>(now - fpsSampleTime).count();
283 if (elapsed >= 0.25) {
284 fps =
static_cast<double>(fpsFrameCount) / elapsed;
287 fpsText = std::format(
"FPS: {:.1f}", fps);
290 printText(fpsText, 15, 15, SDL_Color{255, 255, 255, 255});
293 std::string assetRoot{};
294 std::string shaderRoot{};
295 std::string modelPath{};
297 int fallbackWidth = 1280;
298 int fallbackHeight = 720;
299 float yawRadians = 0.0f;
300 float pitchRadians = 0.0f;
301 float autoSpinRadians = 0.0f;
302 bool mouseDragging =
false;
305 float mouseYawDegrees = 0.0f;
306 float mousePitchDegrees = 0.0f;
307 float cameraDistance = 4.2f;
308 float mouseSensitivity = 0.35f;
310 uint32_t fpsFrameCount = 0;
311 std::chrono::steady_clock::time_point fpsSampleTime{std::chrono::steady_clock::now()};
312 std::string fpsText =
"FPS: --";
313 mxvk::VK_Capture capture{};
314 mxvk::VKAbstractModel model{};
315 std::chrono::steady_clock::time_point lastUpdateTime{std::chrono::steady_clock::now()};
316 std::chrono::steady_clock::time_point startTime{std::chrono::steady_clock::now()};
#define opencv_model_ASSET_DIR