mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-19 11:43:53 +00:00
ifcviewer-web: async object pick → click-to-select on web
Desktop pick reads the pick staging buffer back with a blocking
`while(!done) waitTickInstance()` spin. On web that spin is a no-op
(Asyncify is off) and hangs the JS event loop, so click-to-select was
dead on web. This adds an async sibling that uses the spontaneous
map-callback pattern (AllowSpontaneous + the browser microtask loop)
already proven by the HiZ readback — no blocking.
- encodePickReadbackToStaging(x,y,want_normal): the pick-pass render +
copy-texel-to-staging, extracted from pickObjectAt verbatim and shared
by both readbacks (desktop sync path unchanged).
- pickObjectAtAsync(x,y,cb) [web]: encode, then map the staging buffer
with a spontaneous callback that delivers object_id to cb. One pick in
flight at a time (a pick issued mid-map is dropped → cb(0)).
- applyPickToSelection(id, add, remove): routes a pick result through the
selection state machine (replace / Shift-add / Ctrl-remove / empty-click
clear), mirroring the desktop ViewportWindow click semantics. selection_
marks dirty so the next render's uploadSelectionFlagsIfDirty flushes the
highlight.
main_web wires it: a left release under a 4px drag threshold (no orbit) is
a pick at down-position * devicePixelRatio, with Shift/Ctrl modifiers;
the result callback applies selection and requests a frame. Web + desktop
build clean; 107/107 unit tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,9 @@
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
namespace {
|
||||
|
||||
// CSS selector for the host <canvas>; must match shell.html + the
|
||||
@@ -58,8 +61,18 @@ struct AppState {
|
||||
// three-button mice and trackpad (right-drag) users.
|
||||
bool nav_active = false;
|
||||
int nav_button = 0;
|
||||
// Accumulated |movement| since mousedown, in CSS px. A left release under
|
||||
// the click threshold (no real drag) is treated as a pick instead of an
|
||||
// orbit. Captures the down position (canvas-relative CSS px) for the pick.
|
||||
float nav_drag_px = 0.0f;
|
||||
long down_x = 0;
|
||||
long down_y = 0;
|
||||
};
|
||||
|
||||
// Click vs drag threshold (CSS px). Below this total travel a left release is
|
||||
// a pick, above it the gesture was an orbit.
|
||||
constexpr float kClickDragThresholdPx = 4.0f;
|
||||
|
||||
// One global so the JS-side RAF loop can recover state through a
|
||||
// pointer round-trip (set into Module._app_ptr from on_complete).
|
||||
AppState* g_app = nullptr;
|
||||
@@ -76,8 +89,11 @@ int canvasCssHeight() {
|
||||
EM_BOOL onMouseDown(int, const EmscriptenMouseEvent* e, void* user) {
|
||||
auto* app = static_cast<AppState*>(user);
|
||||
if (e->button == 0 || e->button == 1 || e->button == 2) {
|
||||
app->nav_active = true;
|
||||
app->nav_button = e->button;
|
||||
app->nav_active = true;
|
||||
app->nav_button = e->button;
|
||||
app->nav_drag_px = 0.0f;
|
||||
app->down_x = e->targetX; // canvas-relative CSS px
|
||||
app->down_y = e->targetY;
|
||||
}
|
||||
return EM_TRUE;
|
||||
}
|
||||
@@ -88,6 +104,7 @@ EM_BOOL onMouseMove(int, const EmscriptenMouseEvent* e, void* user) {
|
||||
|
||||
const float dx = float(e->movementX);
|
||||
const float dy = float(e->movementY);
|
||||
app->nav_drag_px += std::abs(dx) + std::abs(dy);
|
||||
if (app->nav_button == 0) {
|
||||
app->core.orbitBy(dx, dy);
|
||||
} else {
|
||||
@@ -96,8 +113,27 @@ EM_BOOL onMouseMove(int, const EmscriptenMouseEvent* e, void* user) {
|
||||
return EM_TRUE;
|
||||
}
|
||||
|
||||
EM_BOOL onMouseUp(int, const EmscriptenMouseEvent* /*e*/, void* user) {
|
||||
static_cast<AppState*>(user)->nav_active = false;
|
||||
EM_BOOL onMouseUp(int, const EmscriptenMouseEvent* e, void* user) {
|
||||
auto* app = static_cast<AppState*>(user);
|
||||
const bool was_active = app->nav_active;
|
||||
const int button = app->nav_button;
|
||||
app->nav_active = false;
|
||||
|
||||
// Left release with no real drag → pick the object under the cursor and
|
||||
// route it through selection (Shift add, Ctrl remove, plain replace).
|
||||
// Async readback: the highlight appears a frame after the result lands.
|
||||
if (was_active && button == 0 && app->ready &&
|
||||
app->nav_drag_px <= kClickDragThresholdPx) {
|
||||
const double dpr = emscripten_get_device_pixel_ratio();
|
||||
const int px = int(app->down_x * dpr);
|
||||
const int py = int(app->down_y * dpr);
|
||||
const bool add = e->shiftKey;
|
||||
const bool remove = e->ctrlKey;
|
||||
app->core.pickObjectAtAsync(px, py, [app, add, remove](std::uint32_t id) {
|
||||
app->core.applyPickToSelection(id, add, remove);
|
||||
app->host.requestFrame();
|
||||
});
|
||||
}
|
||||
return EM_TRUE;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user